refactor(approve): 重构审批业务状态同步逻辑
- 移除 ApproveNodeServiceImpl 中的冗余导入和字段依赖
- 提取审批业务状态同步逻辑至独立的 ApproveBusinessStatusService 服务类
- 移除 ApproveProcessServiceImpl 中的采购审核特定代码和相关字段依赖
- 简化审批流程创建逻辑,统一处理无审核人情况下的业务状态同步
- 优化发货审批流程,先保存发货单再发起审批确保状态回写正确
- 新增 ApproveBusinessStatusService 专门处理各类审批类型的状态同步
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # Sales 模åãæ»æ°ãå端èè°ææ¡£ |
| | | |
| | | ## 1. åæ´ç®æ |
| | | |
| | | - éå®å°è´¦äº§åæ°å¢å段 `totalQuantity`ï¼æ»æ°ï¼ã |
| | | - å½äº§å `isProduction = true` æ¶ï¼è¿å
¥ç产主计åï¼`production_plan`ï¼ç `qtyRequired` ä½¿ç¨ `totalQuantity`ã |
| | | |
| | | ## 2. æ°æ®åºåæ´ |
| | | |
| | | - æ°å¢åï¼`sales_ledger_product.total_quantity`ï¼`DECIMAL(18,4)`ï¼å¯ç©ºï¼ã |
| | | - SQL æä»¶ï¼`doc/20260525_sales_éå®äº§åæ»æ°å段.sql`ã |
| | | |
| | | ## 3. åæ®µå®ä¹ |
| | | |
| | | - åæ®µåï¼`totalQuantity` |
| | | - å«ä¹ï¼äº§åæ»æ°ï¼ç¨äºåè´§å¾
åè´§æ°éå±ç¤ºä¸ç产主计åéæ±æ°éï¼ |
| | | - ç±»åï¼`number`ï¼å端 `BigDecimal`ï¼ |
| | | - å端å
åºè§åï¼ |
| | | - è¥æªä¼ æ `<= 0`ï¼å端æ `quantity * singleQuantity` èªå¨è¡¥é½ï¼ |
| | | - `singleQuantity` 为空æ `<= 0` æ¶æ `1` å¤çã |
| | | |
| | | ## 4. æ¥å£èè°è¯´æ |
| | | |
| | | 1. `POST /sales/ledger/addOrUpdateSalesLedger` |
| | | - å
¥å `productData[]` æ¯æ `totalQuantity`ã |
| | | 2. `POST /sales/product/addOrUpdateSalesLedgerProduct` |
| | | - å
¥åæ¯æ `totalQuantity`ã |
| | | 3. `GET /sales/product/list` |
| | | - è¿åæ°å¢å段 `totalQuantity`ï¼ |
| | | - `noQuantity`ï¼å¾
åè´§æ°éï¼æ `totalQuantity - shippedQuantity` 计ç®ï¼è¥ `totalQuantity` 为空ï¼å端èªå¨åé为 `quantity * singleQuantity`ï¼ã |
| | | |
| | | ## 5. ç产主计åèå¨ |
| | | |
| | | - è§¦åæ¡ä»¶ï¼éå®äº§å `isProduction = true`ã |
| | | - 主计åè½åºè§åï¼ |
| | | - `production_plan.qty_required = totalQuantity`ï¼ä¼å
ï¼ï¼ |
| | | - è¥ `totalQuantity` æ æååé `quantity * singleQuantity`ã |
| | | |
| | | ## 6. å端å®ç°å»ºè®® |
| | | |
| | | 1. å¨éå®äº§åå¼¹çªæ°å¢å±ç¤ºå段 `totalQuantity`ï¼å»ºè®®åªè¯»ï¼ã |
| | | 2. åç«¯å¨ `quantity`ã`singleQuantity` å忶宿¶è®¡ç®ï¼`totalQuantity = quantity * singleQuantity`ã |
| | | 3. æäº¤æ¶å° `totalQuantity` ä¸å¹¶ä¼ ç»å端ï¼é¿å
åå端å±ç¤ºå·®å¼ã |
| | | |
| | | ## 7. å
¥åç¤ºä¾ |
| | | |
| | | ```json |
| | | { |
| | | "productData": [ |
| | | { |
| | | "productModelId": 101, |
| | | "quantity": 10, |
| | | "singleQuantity": 12, |
| | | "totalQuantity": 120, |
| | | "isProduction": true |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | ALTER TABLE sales_ledger_product |
| | | ADD COLUMN total_quantity DECIMAL(18, 4) NULL COMMENT 'æ»æ°' AFTER single_quantity; |
| | | |
| | | UPDATE sales_ledger_product |
| | | SET total_quantity = IFNULL(quantity, 0) * IFNULL(NULLIF(single_quantity, 0), 1) |
| | | WHERE total_quantity IS NULL; |
| | |
| | | */ |
| | | @Excel(name = "æ°é") |
| | | private BigDecimal quantity; |
| | | |
| | | @TableField(value = "single_quantity") |
| | | @Excel(name = "æ¯ä»¶æ°é") |
| | | private BigDecimal singleQuantity; |
| | | |
| | | @TableField(value = "total_quantity") |
| | | @Excel(name = "æ»æ°") |
| | | private BigDecimal totalQuantity; |
| | | |
| | | @Excel(name = "æä½åºåæ°é") |
| | | private BigDecimal minStock; |
| | | /** |
| | |
| | | int result; |
| | | Long salesLedgerId = salesLedgerProduct.getSalesLedgerId(); |
| | | salesLedgerProduct.setSingleQuantity(normalizeSingleQuantity(salesLedgerProduct.getSingleQuantity())); |
| | | salesLedgerProduct.setTotalQuantity(normalizeTotalQuantity( |
| | | salesLedgerProduct.getTotalQuantity(), |
| | | salesLedgerProduct.getQuantity(), |
| | | salesLedgerProduct.getSingleQuantity() |
| | | )); |
| | | if (salesLedgerProduct.getId() == null) { |
| | | salesLedgerProduct.setRegisterDate(LocalDateTime.now()); |
| | | result = salesLedgerProductMapper.insert(salesLedgerProduct); |
| | |
| | | productionPlan.setSalesLedgerProductId(salesLedgerProduct.getId()); |
| | | productionPlan.setMpsNo(generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))); |
| | | productionPlan.setProductModelId(salesLedgerProduct.getProductModelId()); |
| | | productionPlan.setQtyRequired(salesLedgerProduct.getQuantity()); |
| | | productionPlan.setQtyRequired(normalizeTotalQuantity( |
| | | salesLedgerProduct.getTotalQuantity(), |
| | | salesLedgerProduct.getQuantity(), |
| | | salesLedgerProduct.getSingleQuantity() |
| | | )); |
| | | productionPlan.setSource("éå®"); |
| | | productionPlan.setStatus(0); |
| | | productionPlan.setRequiredDate(salesLedger.getDeliveryDate());//éæ±æ¥æ=äº¤è´§æ¥æ |
| | |
| | | return singleQuantity; |
| | | } |
| | | |
| | | private BigDecimal normalizeTotalQuantity(BigDecimal totalQuantity, BigDecimal quantity, BigDecimal singleQuantity) { |
| | | if (totalQuantity != null && totalQuantity.compareTo(BigDecimal.ZERO) > 0) { |
| | | return totalQuantity; |
| | | } |
| | | if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | return quantity.multiply(normalizeSingleQuantity(singleQuantity)); |
| | | } |
| | | |
| | | private String generateNextPlanNo(String datePrefix) { |
| | | QueryWrapper<ProductionPlan> queryWrapper = new QueryWrapper<>(); |
| | | queryWrapper.likeRight("mps_no", "JH" + datePrefix); |
| | |
| | | SalesLedgerProduct salesLedgerProduct = new SalesLedgerProduct(); |
| | | BeanUtils.copyProperties(salesLedgerProductImportDto, salesLedgerProduct); |
| | | salesLedgerProduct.setSingleQuantity(normalizeSingleQuantity(salesLedgerProduct.getSingleQuantity())); |
| | | salesLedgerProduct.setTotalQuantity(normalizeTotalQuantity( |
| | | salesLedgerProduct.getTotalQuantity(), |
| | | salesLedgerProduct.getQuantity(), |
| | | salesLedgerProduct.getSingleQuantity() |
| | | )); |
| | | salesLedgerProduct.setSalesLedgerId(salesLedger.getId()); |
| | | salesLedgerProduct.setType(1); |
| | | // 计ç®ä¸å«ç¨æ»ä»· |
| | |
| | | .peek(p -> { |
| | | p.setSalesLedgerId(salesLedgerId); |
| | | p.setSingleQuantity(normalizeSingleQuantity(p.getSingleQuantity())); |
| | | p.setTotalQuantity(normalizeTotalQuantity(p.getTotalQuantity(), p.getQuantity(), p.getSingleQuantity())); |
| | | }) |
| | | .collect(Collectors.partitioningBy(p -> p.getId() != null)); |
| | | |
| | |
| | | return singleQuantity; |
| | | } |
| | | |
| | | private BigDecimal normalizeTotalQuantity(BigDecimal totalQuantity, BigDecimal quantity, BigDecimal singleQuantity) { |
| | | if (totalQuantity != null && totalQuantity.compareTo(BigDecimal.ZERO) > 0) { |
| | | return totalQuantity; |
| | | } |
| | | if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | return quantity.multiply(normalizeSingleQuantity(singleQuantity)); |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public String generateSalesContractNo() { |
| | | LocalDate currentDate = LocalDate.now(); |
| | |
| | | T1.warn_num, |
| | | T1.quantity, |
| | | T1.single_quantity, |
| | | T1.total_quantity, |
| | | T1.min_stock, |
| | | T1.tax_rate, |
| | | T1.tax_inclusive_unit_price, |
| | |
| | | WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0)) >0 THEN 1 |
| | | ELSE 0 |
| | | END as has_sufficient_stock, |
| | | (IFNULL(T1.quantity, 0) * IFNULL(NULLIF(T1.single_quantity, 0), 1) - IFNULL(t3.shipped_quantity, 0)) as no_quantity, |
| | | (IFNULL(NULLIF(T1.total_quantity, 0), IFNULL(T1.quantity, 0) * IFNULL(NULLIF(T1.single_quantity, 0), 1)) - IFNULL(t3.shipped_quantity, 0)) as no_quantity, |
| | | CASE |
| | | WHEN IFNULL(t3.shipped_quantity, 0) = 0 THEN 'å¾
åè´§' |
| | | WHEN (IFNULL(T1.quantity, 0) * IFNULL(NULLIF(T1.single_quantity, 0), 1) - IFNULL(t3.shipped_quantity, 0)) > 0 THEN 'é¨ååè´§' |
| | | WHEN (IFNULL(NULLIF(T1.total_quantity, 0), IFNULL(T1.quantity, 0) * IFNULL(NULLIF(T1.single_quantity, 0), 1)) - IFNULL(t3.shipped_quantity, 0)) > 0 THEN 'é¨ååè´§' |
| | | ELSE 'å·²åè´§' |
| | | END as shippingStatus, |
| | | CASE |