| src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/dto/SalesScanShipDto.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/service/impl/CommonFileServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/test/java/com/ruoyi/sales/InvoiceLedgerReceiptIncomeRebuildBatchTest.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -259,26 +259,28 @@ } // åºåºå®¡æ¹ä¿®æ¹ (订å级å«) if(approveProcess.getApproveType().equals(7)){ String[] split = approveProcess.getApproveReason().split(":"); if (split.length > 1) { String identifier = split[1]; // æ¥æ¾éå®å°è´¦ SalesLedger salesLedger = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>() .eq(SalesLedger::getSalesContractNo, identifier) .last("limit 1")); String scanRemark = approveProcess.getApproveRemark(); if (org.springframework.util.StringUtils.hasText(scanRemark) && scanRemark.startsWith("SCAN_SHIP_DELIVERY_JSON:")) { salesLedgerService.onScanShipDeliveryApproveOutcome(approveProcess, status); } else { String[] split = approveProcess.getApproveReason().split(":"); if (split.length > 1) { String identifier = split[1]; SalesLedger salesLedger = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>() .eq(SalesLedger::getSalesContractNo, identifier) .last("limit 1")); if (salesLedger != null) { if(status.equals(2)){ // 审æ¹å®æ -> ä¿®æ¹ç¶æä¸ºå®¡æ ¸éè¿ï¼ä¸æ£é¤åºåï¼æ£é¤åºåå¨åè´§å°è´¦è¡¥å ä¿¡æ¯ï¼ updateSalesLedgerDeliveryStatus(salesLedger.getId(), 4); updateShippingInfoStatusByOrder(salesLedger.getId(), "å®¡æ ¸éè¿"); } else if(status.equals(3)){ updateSalesLedgerDeliveryStatus(salesLedger.getId(), 3); // æ´æ°å ³èçåè´§è®°å½ä¸ºå®¡æ ¸æç» updateShippingInfoStatusByOrder(salesLedger.getId(), "å®¡æ ¸æç»"); } else if(status.equals(1)){ updateSalesLedgerDeliveryStatus(salesLedger.getId(), 2); updateShippingInfoStatusByOrder(salesLedger.getId(), "å®¡æ ¸ä¸"); if (salesLedger != null) { if(status.equals(2)){ updateSalesLedgerDeliveryStatus(salesLedger.getId(), 4); updateShippingInfoStatusByOrder(salesLedger.getId(), "å®¡æ ¸éè¿"); } else if(status.equals(3)){ updateSalesLedgerDeliveryStatus(salesLedger.getId(), 3); updateShippingInfoStatusByOrder(salesLedger.getId(), "å®¡æ ¸æç»"); } else if(status.equals(1)){ updateSalesLedgerDeliveryStatus(salesLedger.getId(), 2); updateShippingInfoStatusByOrder(salesLedger.getId(), "å®¡æ ¸ä¸"); } } } } src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -342,6 +342,13 @@ return AjaxResult.success(); } @PostMapping("/scanShipApply") @ApiOperation("éå®è®¢åæ«ç -åèµ·å货审æ¹ï¼å¡«å车ç/å¿«éã审æ¹äººãéä»¶ï¼éè¿åèªå¨æ£åºåå¹¶æ è®°å·²åè´§ï¼") public AjaxResult scanShipApply(@RequestBody SalesScanShipDto dto) { salesLedgerService.scanShipApply(dto); return AjaxResult.success("å货审æ¹å·²åèµ·"); } @PostMapping("/scanOutboundUnqualified") @ApiOperation("éå®è®¢åæ«ç -ä¸åæ ¼åºåº") public AjaxResult scanOutboundUnqualified(@RequestBody SalesScanInboundDto dto) { src/main/java/com/ruoyi/sales/dto/SalesScanShipDto.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,37 @@ package com.ruoyi.sales.dto; import com.ruoyi.sales.pojo.SalesLedgerProduct; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.List; /** * APP æ«ç åè´§ï¼å¡«ååè´§ä¿¡æ¯ã审æ¹äººãéä»¶ååèµ·å货审æ¹ï¼å®¡æ¹éè¿åèªå¨æ£åºåå¹¶æ è®°å·²åè´§ã */ @Data @ApiModel(value = "SalesScanShipDto", description = "é宿«ç åè´§ï¼å起审æ¹ï¼") public class SalesScanShipDto { @ApiModelProperty("éå®è®¢å Id") private Long salesLedgerId; @ApiModelProperty("æ¬æ¬¡åè´§æ°é") private List<SalesLedgerProduct> salesLedgerProductList; @ApiModelProperty(value = "审æ¹äºº userIdï¼éå·åé", required = true) private String approveUserIds; @ApiModelProperty("åè´§ç±»åï¼è´§è½¦ / å¿«é") private String shipType; @ApiModelProperty("车çå·") private String shippingCarNumber; @ApiModelProperty("å¿«éåå·") private String expressNumber; @ApiModelProperty("ä¸´æ¶æä»¶ id å表") private List<String> tempFileIds; } src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.basic.pojo.Customer; import com.ruoyi.common.enums.SaleEnum; import com.ruoyi.approve.pojo.ApproveProcess; import com.ruoyi.sales.dto.*; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.SalesLedgerProcessRoute; @@ -85,6 +86,18 @@ void scanOutboundUnqualified(SalesScanInboundDto dto); /** * APP æ«ç åè´§ï¼åèµ·å货审æ¹ï¼å®¡æ¹éè¿åèªå¨æ£åºåãåè´§å°è´¦ä¸è®¢åç¶æä¸ºå·²åè´§ï¼ */ void scanShipApply(SalesScanShipDto dto); /** * å货审æ¹ï¼ç±»å 7ï¼èç¹ç¶æåæ´ï¼æ«ç åè´§æµç¨ {@code approveRemark} 以 {@code SCAN_SHIP_DELIVERY_JSON:} å¼å¤´æ¶åè°ã * * @param outcomeStatus å®¡æ¹æµç¶æï¼1 å®¡æ ¸ä¸ 2 éè¿ 3 æç» */ void onScanShipDeliveryApproveOutcome(ApproveProcess approveProcess, Integer outcomeStatus); void shippingImport(MultipartFile file); void notShippingImport(MultipartFile file); src/main/java/com/ruoyi/sales/service/impl/CommonFileServiceImpl.java
@@ -2,6 +2,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.ruoyi.common.enums.FileNameType; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.StringUtils; @@ -30,6 +31,7 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -173,6 +175,34 @@ } } /** * å°å®¡æ¹åä¸çéä»¶è®°å½å¤å¶ä¸ºå¤æ¡åè´§å°è´¦éä»¶ï¼åä¸æä»¶è·¯å¾ï¼æ¯æ¡åè´§å°è´¦å䏿¡è®°å½ï¼ã */ public void copyApproveProcessShipAttachmentsToShippingInfos(Long approveProcessId, List<Long> shippingInfoIds) { if (approveProcessId == null || CollectionUtils.isEmpty(shippingInfoIds)) { return; } List<CommonFile> files = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>() .eq(CommonFile::getCommonId, approveProcessId) .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue())); if (CollectionUtils.isEmpty(files)) { return; } List<Long> distinctTargets = shippingInfoIds.stream().filter(java.util.Objects::nonNull).distinct().collect(Collectors.toList()); for (Long sid : distinctTargets) { for (CommonFile f : files) { CommonFile copy = new CommonFile(); copy.setCommonId(sid); copy.setName(f.getName()); copy.setUrl(f.getUrl()); copy.setLink(f.getLink()); copy.setType(FileNameType.SHIP.getValue()); copy.setCreateTime(LocalDateTime.now()); commonFileMapper.insert(copy); } } } private String buildAccessLink(Path formalFilePath) { String normalizedPath = formalFilePath.toString().replace("\\", "/"); String profile = RuoYiConfig.getProfile(); src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -1,6 +1,7 @@ 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; @@ -11,6 +12,9 @@ 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; @@ -3012,4 +3016,303 @@ } 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); } } src/test/java/com/ruoyi/sales/InvoiceLedgerReceiptIncomeRebuildBatchTest.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,264 @@ package com.ruoyi.sales; import com.ruoyi.RuoYiApplication; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import javax.sql.DataSource; import static org.junit.jupiter.api.Assertions.assertTrue; /** * <b>ä¸å¡å£å¾</b>ï¼åå²é {@code receipt_payment.invoice_ledger_id} å¸¸è¢«åæ <b>éå®äº§åè¡ id</b>ï¼{@code sales_ledger_product_id}ï¼ï¼ * ä¸çå® {@code invoice_ledger.id} ä¸ä¸è´ã忥æ¶åºä»¥ <b>å¼ç¥¨å°è´¦</b> 为åï¼å¡æ¯ãå·²ç»å¼ç¥¨ãçéå®å°è´¦äº§åè¡ï¼ * å°±å¿ é¡»æä¸ä¹å¯¹åºç忬¾æµæ°´ä¸å款æ¶å ¥ï¼ä¸<strong>ä¸è¡å¼ç¥¨å°è´¦ â ä¸è¡å款 â ä¸è¡æ¶å ¥</strong>ã * <p> * <b>æ¨èï¼{@link #syncReceiptByInvoicedSalesProducts_hbtmblcSchema()}</b> * <ol> * <li>æ¥åºææå¨å¼ç¥¨å°è´¦éåºç°è¿ç {@code sales_ledger_product_id}ï¼{@code invoice_ledger} â {@code invoice_registration_product}ï¼ã</li> * <li>å é¤è¿äº<strong>éå®äº§åè¡</strong>ä¸ç°æç {@code receipt_payment}ï¼å¹¶å é¤ {@code business_type=1} 䏿å¨è¿äºå款ä¸ç {@code account_income}ã</li> * <li>æ {@code invoice_ledger} éè¡æå ¥æ°ç {@code receipt_payment}ï¼{@code sales_ledger_id}ã{@code sales_ledger_product_id} æ¥èªç»è®°äº§åè¡ï¼ * {@code invoice_ledger_id = il.id}ï¼çå®å°è´¦ä¸»é®ï¼ï¼éé¢/æ¥æ/å¼ç¥¨äººä¸å¼ç¥¨å°è´¦ä¸è´ã</li> * <li>ææ°åæ¬¾ä¸»é®æå ¥ {@code account_income}ï¼ä¸ {@link com.ruoyi.sales.service.impl.ReceiptPaymentServiceImpl} ä¸è´ï¼{@code business_id=receipt_payment.id}ï¼ã</li> * </ol> * æªå¨å¼ç¥¨éåºç°çéå®äº§åï¼å ¶å款记å½<strong>ä¸å 䏿¹</strong>ã * <p> * <b>æ ç¨ï¼{@link #fullRebuildReceiptFromInvoiceLedger_hbtmblcSchema()}</b> â æ¸ 空<b>å ¨é¨</b>忬¾ä¸<b>å ¨é¨</b>忬¾ç±»æ¶å ¥åéå»ºï¼ * ä» ç¨äºæ´åºä»¥å¼ç¥¨ä¸ºå¯ä¸äºå®æºçåºæ¯ã * <p> * <b>ååºå¼å ³</b>ï¼{@code -Druoyi.invoiceReceiptRebuild.commit=true} æ {@code RUOYI_INVOICE_RECEIPT_REBUILD_COMMIT=true}ã */ @SpringBootTest(classes = RuoYiApplication.class) class InvoiceLedgerReceiptIncomeRebuildBatchTest { private static final Logger log = LoggerFactory.getLogger(InvoiceLedgerReceiptIncomeRebuildBatchTest.class); private static final Long TENANT_ID = null; private static final String DEFAULT_REGISTRANT = "æ¨å¿è±"; private static final String RECEIPT_PAYMENT_TYPE = "0"; static boolean commitSwitchEnabled() { String p = System.getProperty("ruoyi.invoiceReceiptRebuild.commit"); if (p != null && "true".equalsIgnoreCase(p.trim())) { return true; } String e = System.getenv("RUOYI_INVOICE_RECEIPT_REBUILD_COMMIT"); return e != null && "true".equalsIgnoreCase(e.trim()); } static MapSqlParameterSource tenantParams() { return new MapSqlParameterSource("tenantId", TENANT_ID); } static String tenantCondIl() { return "(:tenantId IS NULL OR COALESCE(irp.tenant_id, il.tenant_id) = :tenantId)"; } static String tenantRp(String alias) { return "(:tenantId IS NULL OR " + alias + ".tenant_id = :tenantId)"; } /** * åºç°å¨å¼ç¥¨é¾è·¯éçéå®äº§å id éåï¼åæ¥è¯¢ç段ï¼ä¸å«å¤å±æ¬å·ï¼ã */ static String invoicedProductIdSubquery() { return "SELECT DISTINCT irp.sales_ledger_product_id " + "FROM invoice_ledger il " + "INNER JOIN invoice_registration_product irp ON irp.id = il.invoice_registration_product_id " + "WHERE irp.sales_ledger_product_id IS NOT NULL " + "AND (" + tenantCondIl() + ")"; } @Autowired private DataSource dataSource; /** * æãå·²å¼ç¥¨çéå®å°è´¦äº§åè¡ãéå»ºåæ¬¾ä¸æ¶å ¥ï¼æ¨èï¼ã */ @Test @Transactional void syncReceiptByInvoicedSalesProducts_hbtmblcSchema() { NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate(dataSource); MapSqlParameterSource p = tenantParams(); String deleteIncome = "DELETE ai FROM account_income ai " + "INNER JOIN receipt_payment rp ON ai.business_id = rp.id AND ai.business_type = 1 " + "WHERE rp.sales_ledger_product_id IN (" + invoicedProductIdSubquery() + ") " + "AND (" + tenantRp("rp") + ")"; int delInc = named.update(deleteIncome, p); log.warn("å·²å é¤ãå·²å¼ç¥¨äº§åãå ³èç忬¾æ¶å ¥ account_income è¡æ°: {}", delInc); String deleteReceipt = "DELETE rp FROM receipt_payment rp " + "WHERE rp.sales_ledger_product_id IN (" + invoicedProductIdSubquery() + ") " + "AND (" + tenantRp("rp") + ")"; int delRp = named.update(deleteReceipt, p); log.warn("å·²å é¤ãå·²å¼ç¥¨äº§åãä¸çæ§å款 receipt_payment è¡æ°: {}", delRp); MapSqlParameterSource insRp = tenantParams().addValue("rpType", RECEIPT_PAYMENT_TYPE); String insertReceipt = "INSERT INTO receipt_payment (" + "sales_ledger_id, sales_ledger_product_id, invoice_ledger_id, " + "receipt_payment_type, receipt_payment_amount, registrant, receipt_payment_date, " + "create_time, create_user, update_time, update_user, tenant_id) " + "SELECT " + " CAST(irp.sales_ledger_id AS SIGNED), " + " CAST(irp.sales_ledger_product_id AS SIGNED), " + " il.id, " + " :rpType, " + " il.invoice_total, " + " NULLIF(TRIM(il.invoice_person), ''), " + " DATE(il.invoice_date), " + " CAST(DATE(il.invoice_date) AS DATETIME), " + " il.create_user, " + " CAST(DATE(il.invoice_date) AS DATETIME), " + " il.update_user, " + " COALESCE(irp.tenant_id, il.tenant_id) " + "FROM invoice_ledger il " + "INNER JOIN invoice_registration_product irp ON irp.id = il.invoice_registration_product_id " + "WHERE irp.sales_ledger_product_id IS NOT NULL " + "AND (" + tenantCondIl() + ")"; int insRpCnt = named.update(insertReceipt, insRp); log.warn("å·²æå¼ç¥¨å°è´¦æå ¥ receipt_payment è¡æ°: {}", insRpCnt); MapSqlParameterSource regP = tenantParams().addValue("defReg", DEFAULT_REGISTRANT); named.update( "UPDATE receipt_payment rp SET rp.registrant = :defReg " + "WHERE (rp.registrant IS NULL OR rp.registrant = '') AND (" + tenantRp("rp") + ")", regP); MapSqlParameterSource insAi = tenantParams() .addValue("rpType", RECEIPT_PAYMENT_TYPE) .addValue("defReg", DEFAULT_REGISTRANT); String insertIncome = "INSERT INTO account_income (" + "income_date, income_type, customer_name, income_money, income_described, income_method, " + "invoice_number, input_user, input_time, business_id, business_type, tenant_id, " + "create_time, create_user, update_time, update_user) " + "SELECT " + " DATE(il.invoice_date), " + " '3', " + " sl.customer_name, " + " rp.receipt_payment_amount, " + " '忬¾æ¶å ¥', " + " :rpType, " + " il.invoice_no, " + " COALESCE(NULLIF(TRIM(rp.registrant), ''), :defReg), " + " DATE(il.invoice_date), " + " rp.id, " + " 1, " + " rp.tenant_id, " + " CAST(DATE(il.invoice_date) AS DATETIME), " + " rp.create_user, " + " CAST(DATE(il.invoice_date) AS DATETIME), " + " rp.update_user " + "FROM receipt_payment rp " + "INNER JOIN invoice_ledger il ON il.id = rp.invoice_ledger_id " + "INNER JOIN invoice_registration_product irp ON irp.id = il.invoice_registration_product_id " + "INNER JOIN sales_ledger sl ON sl.id = irp.sales_ledger_id " + "WHERE (" + tenantCondIl() + ") " + "AND (" + tenantRp("rp") + ") " + "AND NOT EXISTS (" + " SELECT 1 FROM account_income x WHERE x.business_id = rp.id AND x.business_type = 1" + ")"; int insAiCnt = named.update(insertIncome, insAi); log.warn("å·²æå ¥å款æ¶å ¥ account_income è¡æ°: {}", insAiCnt); assertTrue(insRpCnt >= 0 && insAiCnt >= 0); } /** * æ¸ ç©º<b>å ¨é¨</b>忬¾ä¸å款类æ¶å ¥åï¼ä» æå¼ç¥¨å°è´¦é建ï¼ä¼å½±åæªå¼ç¥¨äº§åç忬¾ï¼æ ç¨ï¼ã */ @Test @Transactional @Rollback(false) void fullRebuildReceiptFromInvoiceLedger_hbtmblcSchema() { NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate(dataSource); MapSqlParameterSource insertParams = tenantParams().addValue("rpType", RECEIPT_PAYMENT_TYPE); int deletedIncome = named.update("DELETE FROM account_income WHERE business_type = 1", new MapSqlParameterSource()); log.warn("fullRebuild: å·²å é¤å ¨é¨å款类æ¶å ¥è¡æ°: {}", deletedIncome); int deletedReceipt = named.update("DELETE FROM receipt_payment", new MapSqlParameterSource()); log.warn("fullRebuild: å·²æ¸ ç©º receipt_payment è¡æ°: {}", deletedReceipt); String insertReceipt = "INSERT INTO receipt_payment (" + "sales_ledger_id, sales_ledger_product_id, invoice_ledger_id, " + "receipt_payment_type, receipt_payment_amount, registrant, receipt_payment_date, " + "create_time, create_user, update_time, update_user, tenant_id) " + "SELECT " + " CAST(irp.sales_ledger_id AS SIGNED), " + " CAST(irp.sales_ledger_product_id AS SIGNED), " + " il.id, " + " :rpType, " + " il.invoice_total, " + " NULLIF(TRIM(il.invoice_person), ''), " + " DATE(il.invoice_date), " + " CAST(DATE(il.invoice_date) AS DATETIME), " + " il.create_user, " + " CAST(DATE(il.invoice_date) AS DATETIME), " + " il.update_user, " + " COALESCE(irp.tenant_id, il.tenant_id) " + "FROM invoice_ledger il " + "INNER JOIN invoice_registration_product irp ON irp.id = il.invoice_registration_product_id " + "WHERE " + tenantCondIl(); int insertedReceipt = named.update(insertReceipt, insertParams); log.warn("fullRebuild: å·²æå ¥ receipt_payment è¡æ°: {}", insertedReceipt); named.update( "UPDATE receipt_payment SET registrant = :defReg WHERE registrant IS NULL OR registrant = ''", new MapSqlParameterSource("defReg", DEFAULT_REGISTRANT)); insertParams.addValue("defReg", DEFAULT_REGISTRANT); String insertIncome = "INSERT INTO account_income (" + "income_date, income_type, customer_name, income_money, income_described, income_method, " + "invoice_number, input_user, input_time, business_id, business_type, tenant_id, " + "create_time, create_user, update_time, update_user) " + "SELECT " + " DATE(il.invoice_date), " + " '3', " + " sl.customer_name, " + " rp.receipt_payment_amount, " + " '忬¾æ¶å ¥', " + " :rpType, " + " il.invoice_no, " + " COALESCE(NULLIF(TRIM(rp.registrant), ''), :defReg), " + " DATE(il.invoice_date), " + " rp.id, " + " 1, " + " rp.tenant_id, " + " CAST(DATE(il.invoice_date) AS DATETIME), " + " rp.create_user, " + " CAST(DATE(il.invoice_date) AS DATETIME), " + " rp.update_user " + "FROM receipt_payment rp " + "INNER JOIN invoice_ledger il ON il.id = rp.invoice_ledger_id " + "INNER JOIN invoice_registration_product irp ON irp.id = il.invoice_registration_product_id " + "INNER JOIN sales_ledger sl ON sl.id = irp.sales_ledger_id " + "WHERE " + tenantCondIl(); int insertedIncome = named.update(insertIncome, insertParams); log.warn("fullRebuild: å·²æå ¥ account_income è¡æ°: {}", insertedIncome); assertTrue(insertedReceipt >= 0 && insertedIncome >= 0); } }