| | |
| | | package com.ruoyi.sales.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
| | |
| | | import com.ruoyi.account.service.AccountIncomeService; |
| | | import com.ruoyi.approve.service.IApproveProcessService; |
| | | import com.ruoyi.approve.vo.ApproveProcessVO; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.fasterxml.jackson.databind.node.ObjectNode; |
| | | import com.ruoyi.approve.pojo.ApproveProcess; |
| | | import com.ruoyi.common.enums.ApproveTypeEnum; |
| | | import com.ruoyi.basic.mapper.CustomerMapper; |
| | |
| | | import com.ruoyi.common.exception.base.BaseException; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.EnumUtil; |
| | | import com.ruoyi.common.utils.OrderUtils; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | |
| | | import com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto; |
| | | import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper; |
| | | import com.ruoyi.quality.mapper.QualityInspectMapper; |
| | | import com.ruoyi.quality.mapper.QualityInspectParamMapper; |
| | | import com.ruoyi.quality.mapper.QualityTestStandardMapper; |
| | | import com.ruoyi.quality.mapper.QualityTestStandardParamMapper; |
| | | import com.ruoyi.quality.pojo.QualityInspect; |
| | | import com.ruoyi.quality.pojo.QualityInspectParam; |
| | | import com.ruoyi.quality.pojo.QualityTestStandard; |
| | | import com.ruoyi.quality.pojo.QualityTestStandardParam; |
| | | import com.ruoyi.sales.dto.*; |
| | | import com.ruoyi.sales.mapper.*; |
| | | import com.ruoyi.sales.pojo.*; |
| | |
| | | private final ProductionProductOutputMapper productionProductOutputMapper; |
| | | private final ProductionProductInputMapper productionProductInputMapper; |
| | | private final QualityInspectMapper qualityInspectMapper; |
| | | private final QualityInspectParamMapper qualityInspectParamMapper; |
| | | private final QualityTestStandardMapper qualityTestStandardMapper; |
| | | private final QualityTestStandardParamMapper qualityTestStandardParamMapper; |
| | | private final RedisTemplate<String, String> redisTemplate; |
| | | |
| | | private final ISalesLedgerProductProcessService salesLedgerProductProcessService; |
| | |
| | | product.setRegisterDate(LocalDateTime.now()); |
| | | // 发货信息 |
| | | ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>().eq(ShippingInfo::getSalesLedgerProductId, product.getId()).orderByDesc(ShippingInfo::getCreateTime).last("limit 1")); |
| | | product.setShippingCarNumber(shippingInfo.getShippingCarNumber()); |
| | | product.setShippingDate(shippingInfo.getShippingDate()); |
| | | if (shippingInfo != null) { |
| | | product.setShippingCarNumber(shippingInfo.getShippingCarNumber()); |
| | | product.setShippingDate(shippingInfo.getShippingDate()); |
| | | product.setShippingStatus(shippingInfo.getStatus()); |
| | | } |
| | | } |
| | |
| | | if (selectedProduct.getProductModelId() == null) { |
| | | throw new ServiceException("入库失败,产品规格未维护,无法入库"); |
| | | } |
| | | BigDecimal orderQty = selectedProduct.getQuantity() == null ? BigDecimal.ZERO : selectedProduct.getQuantity(); |
| | | BigDecimal qualifiedStocked = selectedProduct.getStockedQuantity() == null ? BigDecimal.ZERO : selectedProduct.getStockedQuantity(); |
| | | BigDecimal inboundQty = inboundQtyByLineId.getOrDefault(selectedProduct.getId(), BigDecimal.ZERO); |
| | | if (inboundQty.compareTo(BigDecimal.ZERO) > 0 && qualifiedStocked.add(inboundQty).compareTo(orderQty) > 0) { |
| | | throw new ServiceException("入库失败,合格入库数量之和不能大于订单数量"); |
| | | } |
| | | } |
| | | String approveUserIds = resolveApproveUserIds(dto.getApproveUserIds(), salesLedger.getId(), INBOUND_BIZ_TYPE_SCAN_QUALIFIED); |
| | | if (StringUtils.isEmpty(approveUserIds)) { |
| | |
| | | } |
| | | BigDecimal oldStocked = dbProduct.getStockedQuantity() == null ? BigDecimal.ZERO : dbProduct.getStockedQuantity(); |
| | | BigDecimal newStocked = oldStocked.add(inboundThisLine); |
| | | BigDecimal orderQty = dbProduct.getQuantity() == null ? BigDecimal.ZERO : dbProduct.getQuantity(); |
| | | if (newStocked.compareTo(orderQty) > 0) { |
| | | throw new ServiceException("入库失败,合格入库数量之和不能大于订单数量"); |
| | | } |
| | | |
| | | StockInventoryDto stockInventoryDto = new StockInventoryDto(); |
| | | stockInventoryDto.setRecordId(dbProduct.getId()); |
| | |
| | | stockInventoryDto.setSalesLedgerProductId(dbProduct.getId()); |
| | | stockInventoryService.addstockInventory(stockInventoryDto); |
| | | |
| | | BigDecimal orderQty = dbProduct.getQuantity() == null ? BigDecimal.ZERO : dbProduct.getQuantity(); |
| | | int lineStockStatus; |
| | | if (newStocked.compareTo(BigDecimal.ZERO) <= 0) { |
| | | lineStockStatus = 0; |
| | |
| | | if (selectedProduct.getProductModelId() == null) { |
| | | throw new ServiceException("不合格入库失败,产品规格未维护,无法入库"); |
| | | } |
| | | BigDecimal orderQty = selectedProduct.getQuantity() == null ? BigDecimal.ZERO : selectedProduct.getQuantity(); |
| | | BigDecimal qualifiedStocked = selectedProduct.getStockedQuantity() == null ? BigDecimal.ZERO : selectedProduct.getStockedQuantity(); |
| | | BigDecimal unqualifiedStocked = selectedProduct.getUnqualifiedStockedQuantity() == null ? BigDecimal.ZERO : selectedProduct.getUnqualifiedStockedQuantity(); |
| | | BigDecimal inboundQty = inboundQtyByLineId.getOrDefault(selectedProduct.getId(), BigDecimal.ZERO); |
| | | BigDecimal remainForUnqualified = orderQty.subtract(qualifiedStocked); |
| | | if (remainForUnqualified.compareTo(BigDecimal.ZERO) <= 0 && inboundQty.compareTo(BigDecimal.ZERO) > 0) { |
| | | throw new ServiceException("不合格入库失败,该产品已无可入不合格库数量"); |
| | | } |
| | | if (inboundQty.compareTo(BigDecimal.ZERO) > 0 && unqualifiedStocked.add(inboundQty).compareTo(orderQty) > 0) { |
| | | throw new ServiceException("不合格入库失败,不合格入库数量之和不能大于订单数量"); |
| | | } |
| | | if (inboundQty.compareTo(BigDecimal.ZERO) > 0 && unqualifiedStocked.add(inboundQty).compareTo(remainForUnqualified) > 0) { |
| | | throw new ServiceException("不合格入库失败,不合格入库数量不能大于订单数量减去合格入库数量"); |
| | | } |
| | | } |
| | | String approveUserIds = resolveApproveUserIds(dto.getApproveUserIds(), salesLedger.getId(), INBOUND_BIZ_TYPE_SCAN_UNQUALIFIED); |
| | | if (StringUtils.isEmpty(approveUserIds)) { |
| | |
| | | if (dbProduct.getProductModelId() == null) { |
| | | throw new ServiceException("不合格入库失败,产品规格未维护,无法入库"); |
| | | } |
| | | stockUtils.addUnStock(ledgerId, dbProduct.getId(), dbProduct.getProductModelId(), inboundThisLine, StockInUnQualifiedRecordTypeEnum.SALES_SCAN_UNSTOCK_IN.getCode(), dbProduct.getId()); |
| | | BigDecimal orderQty = dbProduct.getQuantity() == null ? BigDecimal.ZERO : dbProduct.getQuantity(); |
| | | BigDecimal qualifiedStocked = dbProduct.getStockedQuantity() == null ? BigDecimal.ZERO : dbProduct.getStockedQuantity(); |
| | | BigDecimal oldUnStocked = dbProduct.getUnqualifiedStockedQuantity() == null ? BigDecimal.ZERO : dbProduct.getUnqualifiedStockedQuantity(); |
| | | dbProduct.setUnqualifiedStockedQuantity(oldUnStocked.add(inboundThisLine)); |
| | | BigDecimal newUnStocked = oldUnStocked.add(inboundThisLine); |
| | | BigDecimal remainForUnqualified = orderQty.subtract(qualifiedStocked); |
| | | if (remainForUnqualified.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("不合格入库失败,该产品已无可入不合格库数量"); |
| | | } |
| | | if (newUnStocked.compareTo(orderQty) > 0) { |
| | | throw new ServiceException("不合格入库失败,不合格入库数量之和不能大于订单数量"); |
| | | } |
| | | if (newUnStocked.compareTo(remainForUnqualified) > 0) { |
| | | throw new ServiceException("不合格入库失败,不合格入库数量不能大于订单数量减去合格入库数量"); |
| | | } |
| | | stockUtils.addUnStock(ledgerId, dbProduct.getId(), dbProduct.getProductModelId(), inboundThisLine, StockInUnQualifiedRecordTypeEnum.SALES_SCAN_UNSTOCK_IN.getCode(), dbProduct.getId()); |
| | | dbProduct.setUnqualifiedStockedQuantity(newUnStocked); |
| | | dbProduct.fillRemainingQuantity(); |
| | | salesLedgerProductMapper.updateById(dbProduct); |
| | | } |
| | |
| | | if (salesLedger.getDeliveryStatus() != null && salesLedger.getDeliveryStatus() == 5) { |
| | | throw new ServiceException("出库失败,该销售订单已发货"); |
| | | } |
| | | if (salesLedger.getDeliveryStatus() != null && salesLedger.getDeliveryStatus() == 2) { |
| | | throw new ServiceException("出库失败,该销售订单已发起发货审批,请先完成审批"); |
| | | } |
| | | if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) { |
| | | throw new ServiceException("销售订单出库失败,出库产品不能为空"); |
| | | } |
| | | String scanShippingNo = OrderUtils.countTodayByCreateTime(shippingInfoMapper, "SCAN"); |
| | | int saleType = SaleEnum.SALE.getCode(); |
| | | Map<Long, BigDecimal> outboundQtyByLineId = new LinkedHashMap<>(); |
| | | for (SalesLedgerProduct line : dto.getSalesLedgerProductList()) { |
| | |
| | | if (dbProduct.getProductModelId() == null) { |
| | | throw new ServiceException("出库失败,产品规格未维护,无法出库"); |
| | | } |
| | | BigDecimal orderQty = defaultDecimal(dbProduct.getQuantity()); |
| | | BigDecimal prevShipped = defaultDecimal(dbProduct.getShippedQuantity()); |
| | | if (prevShipped.add(outboundThisLine).compareTo(orderQty) > 0) { |
| | | throw new ServiceException("出库失败,本次出库后累计发货数量不能大于该产品订单数量"); |
| | | } |
| | | stockUtils.assertQualifiedAvailable(dbProduct.getProductModelId(), outboundThisLine); |
| | | |
| | | StockInventoryDto stockInventoryDto = new StockInventoryDto(); |
| | |
| | | }); |
| | | boolean allLinesFull = ledgerAllProducts.stream().allMatch(p -> Objects.equals(p.getProductStockStatus(), 2)); |
| | | salesLedger.setStockStatus(allLinesFull ? 2 : (anyInbound ? 1 : 0)); |
| | | List<SalesLedgerProduct> saleLines = ledgerAllProducts.stream() |
| | | .filter(p -> Objects.equals(p.getType(), saleType)) |
| | | .collect(Collectors.toList()); |
| | | boolean allDelivered = !saleLines.isEmpty() && saleLines.stream().allMatch(p -> { |
| | | BigDecimal q = defaultDecimal(p.getQuantity()); |
| | | BigDecimal s = defaultDecimal(p.getShippedQuantity()); |
| | | return q.compareTo(BigDecimal.ZERO) <= 0 || s.compareTo(q) >= 0; |
| | | }); |
| | | if (allDelivered) { |
| | | salesLedger.setDeliveryStatus(5); |
| | | } else { |
| | | boolean anyLineShipped = saleLines.stream() |
| | | .anyMatch(p -> defaultDecimal(p.getShippedQuantity()).compareTo(BigDecimal.ZERO) > 0); |
| | | if (anyLineShipped) { |
| | | salesLedger.setDeliveryStatus(6); |
| | | } |
| | | } |
| | | baseMapper.updateById(salesLedger); |
| | | syncShippingLedgerAfterQualifiedScan(ledgerId, scanShippingNo); |
| | | } |
| | | |
| | | /** |
| | | * 扫码合格出库后同步发货台账记录 |
| | | */ |
| | | private void syncShippingLedgerAfterQualifiedScan(Long ledgerId, String shippingBatchNo) { |
| | | if (shippingBatchNo == null) { |
| | | shippingBatchNo = OrderUtils.countTodayByCreateTime(shippingInfoMapper, "SCAN"); |
| | | } |
| | | int saleType = SaleEnum.SALE.getCode(); |
| | | List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList( |
| | | Wrappers.<SalesLedgerProduct>lambdaQuery() |
| | | .eq(SalesLedgerProduct::getSalesLedgerId, ledgerId) |
| | | .eq(SalesLedgerProduct::getType, saleType)); |
| | | Date now = new Date(); |
| | | for (SalesLedgerProduct p : products) { |
| | | if (p.getShippedQuantity() == null || p.getShippedQuantity().compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | ShippingInfo row = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>() |
| | | .eq(ShippingInfo::getSalesLedgerProductId, p.getId()) |
| | | .orderByDesc(ShippingInfo::getId) |
| | | .last("LIMIT 1")); |
| | | BigDecimal lineQty = defaultDecimal(p.getQuantity()); |
| | | BigDecimal shipped = defaultDecimal(p.getShippedQuantity()); |
| | | boolean lineFullyShipped = lineQty.compareTo(BigDecimal.ZERO) <= 0 || shipped.compareTo(lineQty) >= 0; |
| | | String lineShipStatus = lineFullyShipped ? "已发货" : "部分发货"; |
| | | |
| | | if (row == null) { |
| | | ShippingInfo insert = new ShippingInfo(); |
| | | insert.setSalesLedgerId(ledgerId); |
| | | insert.setSalesLedgerProductId(p.getId()); |
| | | insert.setShippingNo(shippingBatchNo); |
| | | insert.setType("扫码出库"); |
| | | insert.setStatus(lineShipStatus); |
| | | insert.setShippingDate(now); |
| | | shippingInfoMapper.insert(insert); |
| | | } else { |
| | | if (!StringUtils.hasText(row.getType())) { |
| | | row.setType("扫码出库"); |
| | | } |
| | | row.setStatus(lineShipStatus); |
| | | row.setShippingDate(now); |
| | | if (!StringUtils.hasText(row.getShippingNo())) { |
| | | row.setShippingNo(shippingBatchNo); |
| | | } |
| | | shippingInfoMapper.updateById(row); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | if (dbProduct.getProductModelId() == null) { |
| | | throw new ServiceException("导入失败,订单编号[" + orderNo + "]产品规格未维护,无法补录出库"); |
| | | } |
| | | // 历史已发货补录:直接写入入库+出库记录 |
| | | stockUtils.addStock( |
| | | ledger.getId(), |
| | | dbProduct.getId(), |
| | | dbProduct.getProductModelId(), |
| | | allocQty, |
| | | StockInQualifiedRecordTypeEnum.SALE_STOCK_IN.getCode(), |
| | | dbProduct.getId() |
| | | ); |
| | | stockUtils.substractStock( |
| | | ledger.getId(), |
| | | dbProduct.getId(), |
| | | dbProduct.getProductModelId(), |
| | | allocQty, |
| | | StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), |
| | | dbProduct.getId() |
| | | ); |
| | | BigDecimal oldShipped = defaultDecimal(dbProduct.getShippedQuantity()); |
| | | BigDecimal newShipped = oldShipped.add(allocQty); |
| | | dbProduct.setStockedQuantity(defaultDecimal(dbProduct.getQuantity())); |
| | | dbProduct.setShippedQuantity(newShipped); |
| | | dbProduct.setApproveStatus(3); |
| | | updateProductStockStatus(dbProduct); |
| | | dbProduct.fillRemainingQuantity(); |
| | | updateProductShipStatus(dbProduct); |
| | |
| | | throw new ServiceException("导入失败,订单编号[" + orderNo + "]存在重复发货记录,请勿重复导入"); |
| | | } |
| | | shippingInfoMapper.insert(shippingInfo); |
| | | createShippingQualityInspect(ledger, dbProduct, row, allocQty); |
| | | } |
| | | } |
| | | |
| | |
| | | BigDecimal shipped = defaultDecimal(p.getShippedQuantity()); |
| | | return shipped.compareTo(qty) >= 0; |
| | | }); |
| | | boolean anyInbound = CollectionUtils.isNotEmpty(latestProducts) && latestProducts.stream().anyMatch(p -> defaultDecimal(p.getStockedQuantity()).compareTo(BigDecimal.ZERO) > 0); |
| | | boolean allInbound = CollectionUtils.isNotEmpty(latestProducts) && latestProducts.stream().allMatch(p -> { |
| | | BigDecimal qty = defaultDecimal(p.getQuantity()); |
| | | BigDecimal stocked = defaultDecimal(p.getStockedQuantity()); |
| | | return qty.compareTo(BigDecimal.ZERO) <= 0 || stocked.compareTo(qty) >= 0; |
| | | }); |
| | | if (allShipped && rowList.get(0).getReportDate() != null) { |
| | | ledger.setDeliveryDate(DateUtils.toLocalDate(rowList.get(0).getReportDate())); |
| | | } |
| | | ledger.setStockStatus(allInbound ? 2 : (anyInbound ? 1 : 0)); |
| | | ledger.setDeliveryStatus(allShipped ? 5 : 1); |
| | | salesLedgerMapper.updateById(ledger); |
| | | } |
| | |
| | | String subCategory = StringUtils.hasText(row.getProductSubCategory()) ? row.getProductSubCategory().trim() : ""; |
| | | return ledgerId + "|" + subCategory + "|" + shippingNo + "|" + dateStr + "|" + defaultDecimal(row.getQuantity()); |
| | | } |
| | | |
| | | private void createShippingQualityInspect(SalesLedger ledger, SalesLedgerProduct dbProduct, SalesShippingImportDto row, BigDecimal inspectQty) { |
| | | if (ledger == null || dbProduct == null || inspectQty == null || inspectQty.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return; |
| | | } |
| | | Date checkDate = row.getReportDate() != null ? row.getReportDate() : new Date(); |
| | | QualityInspect qualityInspect = new QualityInspect(); |
| | | qualityInspect.setInspectType(2); |
| | | qualityInspect.setCheckTime(checkDate); |
| | | qualityInspect.setCustomer(StringUtils.hasText(ledger.getCustomerName()) ? ledger.getCustomerName() : row.getCustomerName()); |
| | | qualityInspect.setCheckName(StringUtils.hasText(row.getCreator()) ? row.getCreator().trim() : null); |
| | | qualityInspect.setProductId(dbProduct.getProductId()); |
| | | qualityInspect.setProductName(dbProduct.getProductCategory()); |
| | | qualityInspect.setModel(dbProduct.getSpecificationModel()); |
| | | qualityInspect.setUnit(resolveInspectUnit(dbProduct)); |
| | | qualityInspect.setQuantity(inspectQty); |
| | | qualityInspect.setCheckResult("合格"); |
| | | qualityInspect.setInspectState(1); |
| | | qualityInspect.setApprovalStatus(1); |
| | | qualityInspect.setProductModelId(dbProduct.getProductModelId()); |
| | | |
| | | QualityTestStandard selectedStandard = null; |
| | | if (dbProduct.getProductId() != null) { |
| | | List<QualityTestStandard> standards = qualityTestStandardMapper.getQualityTestStandardByProductId(dbProduct.getProductId(), 2, null); |
| | | if (CollectionUtils.isNotEmpty(standards)) { |
| | | selectedStandard = standards.get(0); |
| | | qualityInspect.setTestStandardId(selectedStandard.getId()); |
| | | } |
| | | } |
| | | qualityInspectMapper.insert(qualityInspect); |
| | | |
| | | if (selectedStandard == null || selectedStandard.getId() == null) { |
| | | return; |
| | | } |
| | | List<QualityTestStandardParam> standardParams = qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery().eq(QualityTestStandardParam::getTestStandardId, selectedStandard.getId())); |
| | | if (CollectionUtils.isEmpty(standardParams)) { |
| | | return; |
| | | } |
| | | List<QualityInspectParam> inspectParams = standardParams.stream().map(item -> { |
| | | QualityInspectParam param = new QualityInspectParam(); |
| | | param.setInspectId(qualityInspect.getId()); |
| | | param.setParameterItem(item.getParameterItem()); |
| | | param.setUnit(item.getUnit()); |
| | | param.setStandardValue(item.getStandardValue()); |
| | | param.setControlValue(item.getControlValue()); |
| | | param.setTestValue("无瑕疵"); |
| | | return param; |
| | | }).collect(Collectors.toList()); |
| | | inspectParams.forEach(qualityInspectParamMapper::insert); |
| | | } |
| | | |
| | | private String resolveInspectUnit(SalesLedgerProduct dbProduct) { |
| | | if (dbProduct == null) { |
| | | return null; |
| | | } |
| | | if (StringUtils.hasText(dbProduct.getUnit())) { |
| | | return dbProduct.getUnit(); |
| | | } |
| | | if (dbProduct.getProductModelId() == null) { |
| | | return null; |
| | | } |
| | | ProductModel productModel = productModelMapper.selectById(dbProduct.getProductModelId()); |
| | | if (productModel == null || !StringUtils.hasText(productModel.getUnit())) { |
| | | return null; |
| | | } |
| | | return productModel.getUnit(); |
| | | } |
| | | |
| | | private static final String SCAN_SHIP_REMARK_PREFIX = "SCAN_SHIP_DELIVERY_JSON:"; |
| | | |
| | | private static final class ScanShipPayload { |
| | | private String shippingNo; |
| | | private Long ledgerId; |
| | | private String car; |
| | | private String express; |
| | | private String shipType; |
| | | private Map<Long, BigDecimal> linesQty = new LinkedHashMap<>(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void scanShipApply(SalesScanShipDto dto) { |
| | | if (dto == null || dto.getSalesLedgerId() == null) { |
| | | throw new ServiceException("扫码发货失败,订单不能为空"); |
| | | } |
| | | if (StringUtils.isEmpty(dto.getApproveUserIds())) { |
| | | throw new ServiceException("请选择审批人"); |
| | | } |
| | | if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) { |
| | | throw new ServiceException("请填写发货产品行"); |
| | | } |
| | | String shipType = StringUtils.hasText(dto.getShipType()) ? dto.getShipType().trim() : "货车"; |
| | | if ("货车".equals(shipType)) { |
| | | if (!StringUtils.hasText(dto.getShippingCarNumber())) { |
| | | throw new ServiceException("请填写车牌号"); |
| | | } |
| | | } else if ("快递".equals(shipType)) { |
| | | if (!StringUtils.hasText(dto.getExpressNumber())) { |
| | | throw new ServiceException("请填写快递单号"); |
| | | } |
| | | } |
| | | SalesLedger salesLedger = baseMapper.selectById(dto.getSalesLedgerId()); |
| | | if (salesLedger == null) { |
| | | throw new ServiceException("销售订单不存在"); |
| | | } |
| | | if (salesLedger.getDeliveryStatus() != null && salesLedger.getDeliveryStatus() == 5) { |
| | | throw new ServiceException("该销售订单已发货"); |
| | | } |
| | | if (salesLedger.getDeliveryStatus() != null && salesLedger.getDeliveryStatus() == 2) { |
| | | throw new ServiceException("该销售订单已发起发货审批,请先完成审批"); |
| | | } |
| | | List<SalesLedgerProduct> notStocked = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>() |
| | | .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId()) |
| | | .eq(SalesLedgerProduct::getType, 1) |
| | | .ne(SalesLedgerProduct::getProductStockStatus, 2)); |
| | | if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(notStocked)) { |
| | | throw new ServiceException("发货失败,该销售订单存在未入库产品,请先完成全部入库后再发货"); |
| | | } |
| | | int saleType = SaleEnum.SALE.getCode(); |
| | | Map<Long, BigDecimal> shipQtyByLineId = new LinkedHashMap<>(); |
| | | for (SalesLedgerProduct line : dto.getSalesLedgerProductList()) { |
| | | if (line == null || line.getId() == null) { |
| | | throw new ServiceException("产品信息不完整"); |
| | | } |
| | | BigDecimal q = line.getStockedQuantity(); |
| | | if (q == null) { |
| | | throw new ServiceException("发货数量不能为空"); |
| | | } |
| | | if (q.compareTo(BigDecimal.ZERO) < 0) { |
| | | throw new ServiceException("发货数量不能为负数"); |
| | | } |
| | | shipQtyByLineId.merge(line.getId(), q, BigDecimal::add); |
| | | } |
| | | boolean anyPositive = shipQtyByLineId.values().stream().anyMatch(v -> v.compareTo(BigDecimal.ZERO) > 0); |
| | | if (!anyPositive) { |
| | | throw new ServiceException("请至少填写一行大于 0 的发货数量"); |
| | | } |
| | | Long ledgerId = salesLedger.getId(); |
| | | for (Map.Entry<Long, BigDecimal> entry : shipQtyByLineId.entrySet()) { |
| | | if (entry.getValue().compareTo(BigDecimal.ZERO) == 0) { |
| | | continue; |
| | | } |
| | | SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(entry.getKey()); |
| | | if (dbProduct == null) { |
| | | throw new ServiceException("销售产品不存在"); |
| | | } |
| | | if (!Objects.equals(dbProduct.getSalesLedgerId(), ledgerId) || !Objects.equals(dbProduct.getType(), saleType)) { |
| | | throw new ServiceException("销售产品与订单不匹配"); |
| | | } |
| | | if (dbProduct.getProductModelId() == null) { |
| | | throw new ServiceException("产品规格未维护,无法发货"); |
| | | } |
| | | BigDecimal orderQty = defaultDecimal(dbProduct.getQuantity()); |
| | | BigDecimal prevShipped = defaultDecimal(dbProduct.getShippedQuantity()); |
| | | if (prevShipped.add(entry.getValue()).compareTo(orderQty) > 0) { |
| | | throw new ServiceException("累计发货数量不能大于该产品订单数量"); |
| | | } |
| | | stockUtils.assertQualifiedAvailable(dbProduct.getProductModelId(), entry.getValue()); |
| | | } |
| | | String shNo = OrderUtils.countTodayByCreateTime(shippingInfoMapper, "SH"); |
| | | Map<Long, BigDecimal> positiveLines = new LinkedHashMap<>(); |
| | | for (Map.Entry<Long, BigDecimal> e : shipQtyByLineId.entrySet()) { |
| | | if (e.getValue().compareTo(BigDecimal.ZERO) > 0) { |
| | | positiveLines.put(e.getKey(), e.getValue()); |
| | | } |
| | | } |
| | | for (Map.Entry<Long, BigDecimal> e : positiveLines.entrySet()) { |
| | | ShippingInfo si = new ShippingInfo(); |
| | | si.setSalesLedgerId(ledgerId); |
| | | si.setSalesLedgerProductId(e.getKey()); |
| | | si.setShippingNo(shNo); |
| | | si.setStatus("待审核"); |
| | | si.setType(shipType); |
| | | if ("货车".equals(shipType)) { |
| | | si.setShippingCarNumber(dto.getShippingCarNumber().trim()); |
| | | } |
| | | if ("快递".equals(shipType)) { |
| | | si.setExpressNumber(dto.getExpressNumber().trim()); |
| | | } |
| | | shippingInfoMapper.insert(si); |
| | | } |
| | | String remarkJson = buildScanShipRemarkJson(shNo, ledgerId, dto, positiveLines); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | ApproveProcessVO approveProcessVO = new ApproveProcessVO(); |
| | | approveProcessVO.setApproveType(7); |
| | | approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId()); |
| | | approveProcessVO.setApproveReason("发货审批:" + salesLedger.getSalesContractNo()); |
| | | approveProcessVO.setApproveRemark(remarkJson); |
| | | approveProcessVO.setApproveUserIds(dto.getApproveUserIds().trim()); |
| | | approveProcessVO.setApproveUser(loginUser.getUserId()); |
| | | approveProcessVO.setApproveTime(LocalDate.now().toString()); |
| | | approveProcessVO.setTempFileIds(dto.getTempFileIds()); |
| | | try { |
| | | approveProcessService.addApprove(approveProcessVO); |
| | | } catch (Exception e) { |
| | | throw new ServiceException("发起发货审批失败: " + e.getMessage()); |
| | | } |
| | | salesLedger.setDeliveryStatus(2); |
| | | baseMapper.updateById(salesLedger); |
| | | } |
| | | |
| | | private String buildScanShipRemarkJson(String shippingNo, Long ledgerId, SalesScanShipDto dto, Map<Long, BigDecimal> lines) { |
| | | try { |
| | | ObjectMapper om = new ObjectMapper(); |
| | | ObjectNode root = om.createObjectNode(); |
| | | root.put("shippingNo", shippingNo); |
| | | root.put("ledgerId", ledgerId); |
| | | root.put("car", dto.getShippingCarNumber() == null ? "" : dto.getShippingCarNumber().trim()); |
| | | root.put("express", dto.getExpressNumber() == null ? "" : dto.getExpressNumber().trim()); |
| | | root.put("shipType", dto.getShipType() == null ? "货车" : dto.getShipType().trim()); |
| | | ObjectNode linesNode = om.createObjectNode(); |
| | | for (Map.Entry<Long, BigDecimal> e : lines.entrySet()) { |
| | | linesNode.put(String.valueOf(e.getKey()), e.getValue().stripTrailingZeros().toPlainString()); |
| | | } |
| | | root.set("lines", linesNode); |
| | | return SCAN_SHIP_REMARK_PREFIX + om.writeValueAsString(root); |
| | | } catch (Exception e) { |
| | | throw new ServiceException("构建发货审批参数失败"); |
| | | } |
| | | } |
| | | |
| | | private ScanShipPayload parseScanShipPayload(String remark) { |
| | | if (!StringUtils.hasText(remark) || !remark.startsWith(SCAN_SHIP_REMARK_PREFIX)) { |
| | | return null; |
| | | } |
| | | try { |
| | | String json = remark.substring(SCAN_SHIP_REMARK_PREFIX.length()); |
| | | ObjectMapper om = new ObjectMapper(); |
| | | JsonNode n = om.readTree(json); |
| | | ScanShipPayload p = new ScanShipPayload(); |
| | | p.shippingNo = n.path("shippingNo").asText(null); |
| | | p.ledgerId = n.path("ledgerId").asLong(0L); |
| | | p.car = n.path("car").asText(""); |
| | | p.express = n.path("express").asText(""); |
| | | p.shipType = n.path("shipType").asText("货车"); |
| | | JsonNode lines = n.path("lines"); |
| | | p.linesQty = new LinkedHashMap<>(); |
| | | if (lines.isObject()) { |
| | | Iterator<String> it = lines.fieldNames(); |
| | | while (it.hasNext()) { |
| | | String k = it.next(); |
| | | p.linesQty.put(Long.valueOf(k), new BigDecimal(lines.get(k).asText())); |
| | | } |
| | | } |
| | | return p; |
| | | } catch (Exception e) { |
| | | log.warn("解析扫码发货审批备注失败: {}", e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void onScanShipDeliveryApproveOutcome(ApproveProcess approveProcess, Integer outcomeStatus) { |
| | | if (approveProcess == null) { |
| | | return; |
| | | } |
| | | ScanShipPayload ctx = parseScanShipPayload(approveProcess.getApproveRemark()); |
| | | if (ctx == null || ctx.ledgerId == null || ctx.ledgerId <= 0 || !StringUtils.hasText(ctx.shippingNo)) { |
| | | return; |
| | | } |
| | | if (outcomeStatus != null && outcomeStatus == 2) { |
| | | executeScanShipDeliveryApproved(approveProcess, ctx); |
| | | } else if (outcomeStatus != null && outcomeStatus == 3) { |
| | | updateScanShipBatchShippingStatus(ctx.ledgerId, ctx.shippingNo, "审核拒绝"); |
| | | SalesLedger sl = baseMapper.selectById(ctx.ledgerId); |
| | | if (sl != null) { |
| | | sl.setDeliveryStatus(3); |
| | | baseMapper.updateById(sl); |
| | | } |
| | | } else if (outcomeStatus != null && outcomeStatus == 1) { |
| | | updateScanShipBatchShippingStatus(ctx.ledgerId, ctx.shippingNo, "审核中"); |
| | | } |
| | | } |
| | | |
| | | private void updateScanShipBatchShippingStatus(Long ledgerId, String shippingNo, String statusText) { |
| | | if (ledgerId == null || !StringUtils.hasText(shippingNo)) { |
| | | return; |
| | | } |
| | | shippingInfoMapper.update(null, new UpdateWrapper<ShippingInfo>().lambda() |
| | | .set(ShippingInfo::getStatus, statusText) |
| | | .eq(ShippingInfo::getSalesLedgerId, ledgerId) |
| | | .eq(ShippingInfo::getShippingNo, shippingNo)); |
| | | } |
| | | |
| | | private void executeScanShipDeliveryApproved(ApproveProcess approveProcess, ScanShipPayload ctx) { |
| | | int saleType = SaleEnum.SALE.getCode(); |
| | | Date now = new Date(); |
| | | List<ShippingInfo> batch = shippingInfoMapper.selectList(Wrappers.<ShippingInfo>lambdaQuery() |
| | | .eq(ShippingInfo::getSalesLedgerId, ctx.ledgerId) |
| | | .eq(ShippingInfo::getShippingNo, ctx.shippingNo)); |
| | | if (CollectionUtils.isEmpty(batch)) { |
| | | log.warn("扫码发货审批通过但未找到发货台账 batch ledgerId={} shippingNo={}", ctx.ledgerId, ctx.shippingNo); |
| | | return; |
| | | } |
| | | for (Map.Entry<Long, BigDecimal> entry : ctx.linesQty.entrySet()) { |
| | | Long productLineId = entry.getKey(); |
| | | BigDecimal shipQty = entry.getValue(); |
| | | if (shipQty == null || shipQty.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(productLineId); |
| | | if (dbProduct == null) { |
| | | throw new ServiceException("销售产品不存在"); |
| | | } |
| | | ShippingInfo row = batch.stream() |
| | | .filter(si -> Objects.equals(si.getSalesLedgerProductId(), productLineId)) |
| | | .findFirst() |
| | | .orElse(null); |
| | | if (row == null) { |
| | | throw new ServiceException("未找到对应发货台账行"); |
| | | } |
| | | if (!"已发货".equals(row.getStatus())) { |
| | | stockUtils.substractStock(ctx.ledgerId, productLineId, dbProduct.getProductModelId(), shipQty, |
| | | StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), row.getId()); |
| | | BigDecimal oldShipped = defaultDecimal(dbProduct.getShippedQuantity()); |
| | | dbProduct.setShippedQuantity(oldShipped.add(shipQty)); |
| | | dbProduct.fillRemainingQuantity(); |
| | | salesLedgerProductMapper.updateById(dbProduct); |
| | | } |
| | | row.setStatus("已发货"); |
| | | row.setShippingDate(now); |
| | | if (StringUtils.hasText(ctx.car)) { |
| | | row.setShippingCarNumber(ctx.car.trim()); |
| | | } |
| | | if (StringUtils.hasText(ctx.express)) { |
| | | row.setExpressNumber(ctx.express.trim()); |
| | | } |
| | | if (StringUtils.hasText(ctx.shipType)) { |
| | | row.setType(ctx.shipType.trim()); |
| | | } |
| | | shippingInfoMapper.updateById(row); |
| | | } |
| | | List<Long> shippingIds = batch.stream().map(ShippingInfo::getId).filter(Objects::nonNull).collect(Collectors.toList()); |
| | | commonFileService.copyApproveProcessShipAttachmentsToShippingInfos(approveProcess.getId(), shippingIds); |
| | | List<SalesLedgerProduct> ledgerAllProducts = salesLedgerProductMapper.selectList( |
| | | Wrappers.<SalesLedgerProduct>lambdaQuery().eq(SalesLedgerProduct::getSalesLedgerId, ctx.ledgerId)); |
| | | SalesLedger salesLedger = baseMapper.selectById(ctx.ledgerId); |
| | | if (salesLedger == null) { |
| | | return; |
| | | } |
| | | boolean anyInbound = ledgerAllProducts.stream().anyMatch(p -> { |
| | | BigDecimal sq = p.getStockedQuantity(); |
| | | return sq != null && sq.compareTo(BigDecimal.ZERO) > 0; |
| | | }); |
| | | boolean allLinesFull = ledgerAllProducts.stream().allMatch(p -> Objects.equals(p.getProductStockStatus(), 2)); |
| | | salesLedger.setStockStatus(allLinesFull ? 2 : (anyInbound ? 1 : 0)); |
| | | List<SalesLedgerProduct> saleLines = ledgerAllProducts.stream() |
| | | .filter(p -> Objects.equals(p.getType(), saleType)) |
| | | .collect(Collectors.toList()); |
| | | boolean allDelivered = !saleLines.isEmpty() && saleLines.stream().allMatch(p -> { |
| | | BigDecimal q = defaultDecimal(p.getQuantity()); |
| | | BigDecimal s = defaultDecimal(p.getShippedQuantity()); |
| | | return q.compareTo(BigDecimal.ZERO) <= 0 || s.compareTo(q) >= 0; |
| | | }); |
| | | if (allDelivered) { |
| | | salesLedger.setDeliveryStatus(5); |
| | | } else { |
| | | boolean anyLineShipped = saleLines.stream() |
| | | .anyMatch(p -> defaultDecimal(p.getShippedQuantity()).compareTo(BigDecimal.ZERO) > 0); |
| | | if (anyLineShipped) { |
| | | salesLedger.setDeliveryStatus(6); |
| | | } |
| | | } |
| | | baseMapper.updateById(salesLedger); |
| | | } |
| | | } |