6 天以前 901e3c96c76f168ddbeaa10562a4d2d6f4d3ba8c
refactor(approve): 重构审批业务状态同步逻辑

- 移除 ApproveNodeServiceImpl 中的冗余导入和字段依赖
- 提取审批业务状态同步逻辑至独立的 ApproveBusinessStatusService 服务类
- 移除 ApproveProcessServiceImpl 中的采购审核特定代码和相关字段依赖
- 简化审批流程创建逻辑,统一处理无审核人情况下的业务状态同步
- 优化发货审批流程,先保存发货单再发起审批确保状态回写正确
- 新增 ApproveBusinessStatusService 专门处理各类审批类型的状态同步
已添加2个文件
已修改4个文件
113 ■■■■■ 文件已修改
doc/20260525_sales_总数字段前端联调文档.md 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260525_sales_销售产品总数字段.sql 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260525_sales_×ÜÊý×Ö¶Îǰ¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
# 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
    }
  ]
}
```
doc/20260525_sales_ÏúÊÛ²úÆ·×ÜÊý×Ö¶Î.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
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;
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -66,9 +66,15 @@
     */
    @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;
    /**
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -168,6 +168,11 @@
        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);
@@ -225,7 +230,11 @@
        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());//需求日期=交货日期
@@ -344,6 +353,16 @@
        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);
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -389,6 +389,11 @@
                    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);
                    // è®¡ç®—不含税总价
@@ -594,6 +599,7 @@
                .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));
@@ -631,6 +637,16 @@
        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();
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -11,6 +11,7 @@
        T1.warn_num,
        T1.quantity,
        T1.single_quantity,
        T1.total_quantity,
        T1.min_stock,
        T1.tax_rate,
        T1.tax_inclusive_unit_price,
@@ -34,10 +35,10 @@
        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