2026-06-05 d1fac30e634e33edd29e3440de1f91da84c150c1
feat(account): 新增付款状态管理功能并优化生产任务查询

- 在AccountPaymentApplication实体中新增paymentStatus付款状态字段和paidAmount已付款金额字段
- 更新AccountPaymentApplicationDto和AccountPaymentApplicationVo数据传输对象
- 修改Mapper XML文件添加付款状态相关查询和条件筛选
- 实现付款单新增和删除时自动更新付款申请的付款状态逻辑
- 添加付款申请状态校验和累计金额验证机制
- 新增根据生产订单ID查询工序任务的API接口
- 重构ProductionOperationTask查询逻辑实现按生产订单分组统计
- 添加ProductionPlan实体的单位和数量相关字段
- 实现销售台账产品转生产计划时的产品单位获取逻辑
- 生成详细的前后端联调文档说明接口变更和业务流程
已添加4个文件
已修改11个文件
1245 ■■■■■ 文件已修改
docs/ProductionOperationTask前端联调文档.md 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/付款申请付款状态功能文档.md 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/付款申请已付款状态功能文档.md 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/销售台账现在生产字段同步文档.md 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/purchase/AccountPaymentApplicationDto.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/purchase/AccountPaymentApplicationVo.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/purchase/AccountPaymentApplication.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPurchasePaymentServiceImpl.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOperationTaskController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOperationTaskMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionPlan.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOperationTaskMapper.xml 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/ProductionOperationTaskǰ¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,200 @@
# ProductionOperationTask å‰ç«¯è”调文档
## ä¸€ã€å˜æ›´æ¦‚è¿°
`ProductionOperationTaskController` æ¨¡å—做了以下调整:
1. `/page` åˆ†é¡µæŽ¥å£æ”¹ä¸º**按生产订单号分组**,工序聚合返回
2. æŸ¥è¯¢æ¡ä»¶ç®€åŒ–为仅支持 `npsNo`(订单号模糊搜索)
3. æ–°å¢ž `/byOrder/{orderId}` æŽ¥å£ï¼Œæ ¹æ®ç”Ÿäº§è®¢å• ID æŸ¥è¯¢ï¼Œ**直接复用了 `list` æ–¹æ³•**
## äºŒã€list å¤ç”¨è¯´æ˜Ž
**不能直接复用原来的 `list`**。原来的 `list` åªæŸ¥ `production_operation_task` å•表,没有 JOIN å…³è”表,缺少 `productName`、`model`、`unit`、`operationName` ç­‰å­—段。已将 `list` æ”¹ä¸ºè°ƒç”¨ä¸Ž `page` ç›¸åŒçš„分组查询 SQL(仅去掉了分页包裹),现在 `/list` å’Œ `/byOrder/{orderId}` å…±ç”¨åŒä¸€å¥—查询逻辑。
---
## ä¸‰ã€æŽ¥å£è¯¦æƒ…
### 1. åˆ†é¡µæŸ¥è¯¢ï¼ˆå·²æ”¹é€ ï¼‰
```
GET /productionOperationTask/page
```
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| current | int | å¦ | å½“前页,默认 1 |
| size | int | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤ 10 |
| npsNo | string | å¦ | è®¢å•号,模糊搜索 |
> æ³¨æ„ï¼šåŽŸæœ‰çš„ `id`、`productionOrderId`、`productionOrderRoutingOperationId`、`status`、`isProduction`、`workOrderNo` æŸ¥è¯¢æ¡ä»¶å·²ç§»é™¤ï¼Œä¸å†ç”Ÿæ•ˆã€‚
**请求示例:**
```
GET /productionOperationTask/page?current=1&size=10&npsNo=NPS2026
```
**响应结构:**
```json
{
  "code": 200,
  "data": {
    "records": [
      {
        "productionOrderId": 1001,
        "npsNo": "NPS202606001",
        "endOrder": false,
        "productName": "某某产品",
        "model": "XL-2026",
        "unit": "个",
        "operationName": "切割,焊接,打磨",
        "productionTaskCount": 3,
        "planQuantity": 100,
        "completeQuantity": 80,
        "goodQuantity": 80,
        "scrapQty": 5,
        "completionStatus": 80.00,
        "type": 1,
        "workOrderType": "正常",
        "workOrderNo": "WO202606001,WO202606002,WO202606003",
        "planStartTime": "2026-06-01",
        "planEndTime": "2026-06-15",
        "actualStartTime": "2026-06-02",
        "actualEndTime": "2026-06-14",
        "status": 4,
        "userNames": "张三,李四"
      }
    ],
    "total": 50,
    "current": 1,
    "size": 10
  }
}
```
---
### 2. æ ¹æ®ç”Ÿäº§è®¢å•ID查询(新增)
```
GET /productionOperationTask/byOrder/{orderId}
```
**路径参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| orderId | Long | æ˜¯ | ç”Ÿäº§è®¢å• ID(production_order è¡¨ä¸»é”®ï¼‰ |
**请求示例:**
```
GET /productionOperationTask/byOrder/1001
```
**响应结构:** ä¸Ž `/page` çš„ `records` å•项一致,外层为数组(通常 0-1 æ¡ï¼‰ã€‚
```json
{
  "code": 200,
  "data": [
    {
      "productionOrderId": 1001,
      "npsNo": "NPS202606001",
      "endOrder": false,
      "productName": "某某产品",
      "model": "XL-2026",
      "unit": "个",
      "operationName": "切割,焊接,打磨",
      "productionTaskCount": 3,
      "planQuantity": 100,
      "completeQuantity": 80,
      "goodQuantity": 80,
      "scrapQty": 5,
      "completionStatus": 80.00,
      "type": 1,
      "workOrderType": "正常",
      "workOrderNo": "WO202606001,WO202606002,WO202606003",
      "planStartTime": "2026-06-01",
      "planEndTime": "2026-06-15",
      "actualStartTime": "2026-06-02",
      "actualEndTime": "2026-06-14",
      "status": 4,
      "userNames": "张三,李四"
    }
  ]
}
```
---
### 3. å·¥å•列表(同步改造)
```
GET /productionOperationTask/list
```
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| npsNo | string | å¦ | è®¢å•号,模糊搜索 |
| productionOrderId | Long | å¦ | ç”Ÿäº§è®¢å• ID,精确匹配 |
**请求示例:**
```
GET /productionOperationTask/list?npsNo=NPS2026
GET /productionOperationTask/list?productionOrderId=1001
```
**响应结构:** åŒ `/byOrder/{orderId}`,返回数组。
---
## å››ã€å“åº”字段说明
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| productionOrderId | Long | ç”Ÿäº§è®¢å• ID |
| npsNo | string | è®¢å•号 |
| endOrder | boolean | æ˜¯å¦ç»“单 |
| productName | string | äº§å“åç§° |
| model | string | è§„格型号 |
| unit | string | å•位 |
| operationName | string | å·¥åºåç§°ï¼Œå¤šä¸ªç”¨é€—号拼接,按 drag_sort æŽ’序 |
| productionTaskCount | Long | è¯¥è®¢å•下的生产任务数 |
| planQuantity | BigDecimal | è®¡åˆ’数量(各任务汇总) |
| completeQuantity | BigDecimal | å®Œæˆæ•°é‡ï¼ˆå„任务汇总) |
| goodQuantity | BigDecimal | è‰¯å“æ•°é‡ï¼ˆç­‰äºŽ completeQuantity æ±‡æ€»ï¼‰ |
| scrapQty | BigDecimal | æŠ¥åºŸæ•°é‡ï¼ˆå„任务汇总) |
| completionStatus | BigDecimal | å®Œæˆè¿›åº¦ç™¾åˆ†æ¯”,如 80.00 |
| type | Integer | å·¥åºç±»åž‹ï¼š0=计时,1=计件 |
| workOrderType | string | å·¥å•类型:"正常" / "返工返修"(任一任务为返工则显示返工返修) |
| workOrderNo | string | å·¥å•编号,多个用逗号拼接 |
| planStartTime | LocalDate | æœ€æ—©è®¡åˆ’开始时间 |
| planEndTime | LocalDate | æœ€æ™šè®¡åˆ’结束时间 |
| actualStartTime | LocalDate | æœ€æ—©å®žé™…开始时间 |
| actualEndTime | LocalDate | æœ€æ™šå®žé™…结束时间 |
| status | Integer | çŠ¶æ€ï¼Œå–æœ€å¤§å€¼ï¼š1=待确认, 2=待生产, 3=生产中, 4=已生产 |
| userNames | string | æŠ¥å·¥äººå‘˜åç§°ï¼Œå¤šä¸ªé€—号分隔 |
---
## äº”、与旧接口的核心差异
| å·®å¼‚点 | æ—§ page | æ–° page |
|--------|---------|---------|
| æ•°æ®ç»“æž„ | æ¯è¡Œä¸€æ¡ä»»åŠ¡ | æ¯è¡Œä¸€ä¸ªç”Ÿäº§è®¢å•(任务聚合) |
| operationName | å•个工序名 | é€—号拼接的工序名列表 |
| æŸ¥è¯¢æ¡ä»¶ | 7 ä¸ªè¿‡æ»¤å­—段 | ä»… npsNo |
| planQuantity | å•任务计划数 | è¯¥è®¢å•所有任务计划数汇总 |
---
## å…­ã€å‰ç«¯è”调要点
1. `/page` åŽŸæ¥æ¯è¡Œä¸€ä¸ªå·¥åºä»»åŠ¡ï¼ŒçŽ°åœ¨æ˜¯æ¯è¡Œä¸€ä¸ªè®¢å•ï¼Œè¡¨æ ¼åˆ—å®šä¹‰éœ€è¦ç›¸åº”è°ƒæ•´
2. å¦‚果需要展示订单下的工序明细,可结合 `/getOperation` æŽ¥å£ï¼ˆè¯¥æŽ¥å£æŒ‰å·¥åºåˆ†ç»„,支持 `productionOrderId` è¿‡æ»¤ï¼‰
3. `/byOrder/{orderId}` è¿”回数组,取第一个元素即可(或判空处理)
4. `operationName` å­—段现在可能包含多个工序名(逗号拼接),注意前端展示处理(如换行或标签展示)
docs/¸¶¿îÉêÇ븶¿î״̬¹¦ÄÜÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,327 @@
# ä»˜æ¬¾ç”³è¯·"付款状态"功能文档
## ä¸€ã€éœ€æ±‚背景
付款申请(AccountPaymentApplication)需要支持多次付款,新增独立的"付款状态"字段,与原有的"审核状态"区分。
**原有审核状态(status)**:
- 0:待审核
- 1:审核通过
- 2:审核不通过
**新增付款状态(payment_status)**:
- 0:未付款
- 1:部分付款
- 2:已付款
---
## äºŒã€æ•°æ®åº“变更(SQL)
```sql
-- =====================================================
-- 1. ä»˜æ¬¾ç”³è¯·è¡¨æ–°å¢žä»˜æ¬¾çŠ¶æ€å­—æ®µ
-- =====================================================
ALTER TABLE `account_payment_application`
ADD COLUMN `payment_status` INT DEFAULT 0 COMMENT '付款状态:0未付款 1部分付款 2已付款' AFTER `status`;
-- =====================================================
-- 2. åˆå§‹åŒ–历史数据的付款状态
-- =====================================================
-- æ ¹æ®å·²å­˜åœ¨çš„付款单计算付款状态
UPDATE account_payment_application apa
SET apa.payment_status = (
    CASE
        WHEN apa.payment_amount IS NULL OR apa.payment_amount = 0 THEN 0
        ELSE (
            SELECT CASE
                WHEN IFNULL(SUM(app.payment_amount), 0) = 0 THEN 0
                WHEN IFNULL(SUM(app.payment_amount), 0) < apa.payment_amount THEN 1
                ELSE 2
            END
            FROM account_purchase_payment app
            WHERE app.account_payment_application_id = apa.id
        )
    END
);
-- =====================================================
-- 3. å­—段说明
-- =====================================================
-- account_payment_application è¡¨æ–°å¢žå­—段:
--   payment_status - ä»˜æ¬¾çŠ¶æ€ï¼ˆæ–°å¢žï¼‰
--     0 = æœªä»˜æ¬¾ï¼ˆé»˜è®¤å€¼ï¼Œæ— ä»˜æ¬¾å•或累计付款金额为0)
--     1 = éƒ¨åˆ†ä»˜æ¬¾ï¼ˆç´¯è®¡ä»˜æ¬¾é‡‘额 < ç”³è¯·é‡‘额)
--     2 = å·²ä»˜æ¬¾ï¼ˆç´¯è®¡ä»˜æ¬¾é‡‘额 >= ç”³è¯·é‡‘额)
--
-- åŽŸæœ‰å­—æ®µä¿æŒä¸å˜ï¼š
--   status - å®¡æ ¸çŠ¶æ€
--     0 = å¾…审核
--     1 = å®¡æ ¸é€šè¿‡
--     2 = å®¡æ ¸ä¸é€šè¿‡
```
---
## ä¸‰ã€åŽç«¯æ”¹åŠ¨ç‚¹
### 3.1 å®žä½“类修改
**AccountPaymentApplication.java**
```java
/**
 * å®¡æ ¸çŠ¶æ€:0待审核 1审核通过 2审核不通过
 */
@ApiModelProperty("审核状态:0待审核 1审核通过 2审核不通过")
@Excel(name = "审核状态", readConverterExp = "0=待审核,1=审核通过,2=审核不通过")
private Integer status;
/**
 * ä»˜æ¬¾çŠ¶æ€:0未付款 1部分付款 2已付款
 */
@ApiModelProperty("付款状态:0未付款 1部分付款 2已付款")
@Excel(name = "付款状态", readConverterExp = "0=未付款,1=部分付款,2=已付款")
private Integer paymentStatus;
/**
 * å·²ä»˜æ¬¾é‡‘额(非持久化,查询时计算)
 */
@ApiModelProperty("已付款金额")
@TableField(exist = false)
private BigDecimal paidAmount;
```
### 3.2 ä»˜æ¬¾å•新增逻辑
**新增付款单时自动更新付款状态:**
```java
private void updatePaymentStatus(AccountPaymentApplication application, BigDecimal paidAmount) {
    BigDecimal applyAmount = application.getPaymentAmount();
    int newPaymentStatus;
    if (paidAmount.compareTo(BigDecimal.ZERO) == 0) {
        newPaymentStatus = 0; // æœªä»˜æ¬¾
    } else if (paidAmount.compareTo(applyAmount) < 0) {
        newPaymentStatus = 1; // éƒ¨åˆ†ä»˜æ¬¾
    } else {
        newPaymentStatus = 2; // å·²ä»˜æ¬¾
    }
    application.setPaymentStatus(newPaymentStatus);
    accountPaymentApplicationMapper.updateById(application);
}
```
### 3.3 ä»˜æ¬¾å•删除逻辑
**删除付款单时自动更新付款状态:**
删除付款单后,重新计算剩余付款金额,更新付款状态(可能从"已付款"变为"部分付款"或"未付款")。
---
## å››ã€ä¸šåŠ¡æµç¨‹å›¾
```
┌─────────────────────────────────────────────────────────────────┐
│                       ä»˜æ¬¾ç”³è¯·æµç¨‹                               â”‚
├─────────────────────────────────────────────────────────────────┤
│                                                                 â”‚
│   æ–°å¢žä»˜æ¬¾ç”³è¯·                                                   â”‚
│   status=0(待审核), payment_status=0(未付款)                     â”‚
│         â”‚                                                       â”‚
│         â–¼                                                       â”‚
│   â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”                                               â”‚
│   â”‚   å®¡æ ¸æ“ä½œ   â”‚                                               â”‚
│   â””─────────────┘                                               â”‚
│         â”‚                                                       â”‚
│    â”Œâ”€â”€â”€â”€â”´â”€â”€â”€â”€â”                                                  â”‚
│ é€šè¿‡       ä¸é€šè¿‡                                                â”‚
│    â”‚         â”‚                                                  â”‚
│    â–¼         â–¼                                                  â”‚
│ status=1   status=2                                             â”‚
│ payment_   payment_                                              â”‚
│ status=0   status=0                                              â”‚
│ (审核通过) (审核不通过)                                          â”‚
│    â”‚                                                            â”‚
│    â”‚ ä¸é€šè¿‡ï¼šä¸èƒ½ä»˜æ¬¾                                            â”‚
│    â–¼                                                            â”‚
│   å¯ä»¥æ–°å¢žä»˜æ¬¾å•                                                 â”‚
│    â”‚                                                            â”‚
│    â–¼                                                            â”‚
│   æ–°å¢žä»˜æ¬¾å•1                                                    â”‚
│   ç´¯è®¡ä»˜æ¬¾ < ç”³è¯·é‡‘额                                            â”‚
│   payment_status=1(部分付款)                                     â”‚
│    â”‚                                                            â”‚
│    â–¼                                                            â”‚
│   æ–°å¢žä»˜æ¬¾å•2                                                    â”‚
│   ç´¯è®¡ä»˜æ¬¾ = ç”³è¯·é‡‘额                                            â”‚
│   payment_status=2(已付款)                                       â”‚
│                                                                 â”‚
└─────────────────────────────────────────────────────────────────┘
```
---
## äº”、状态对照表
### 5.1 å®¡æ ¸çŠ¶æ€ vs ä»˜æ¬¾çŠ¶æ€
| å®¡æ ¸çŠ¶æ€(status) | è¯´æ˜Ž | ä»˜æ¬¾çŠ¶æ€(payment_status) | è¯´æ˜Ž |
|------------------|------|--------------------------|------|
| 0 | å¾…审核 | 0 | æœªä»˜æ¬¾ |
| 1 | å®¡æ ¸é€šè¿‡ | 1 | éƒ¨åˆ†ä»˜æ¬¾ |
| 2 | å®¡æ ¸ä¸é€šè¿‡ | 2 | å·²ä»˜æ¬¾ |
**两个状态独立存在,互不影响:**
- å®¡æ ¸é€šè¿‡(status=1)后才能付款
- ä»˜æ¬¾çŠ¶æ€æ ¹æ®ç´¯è®¡ä»˜æ¬¾é‡‘é¢è‡ªåŠ¨è®¡ç®—
### 5.2 çŠ¶æ€ç»„åˆåœºæ™¯
| status | payment_status | åœºæ™¯è¯´æ˜Ž |
|--------|----------------|----------|
| 0 | 0 | æ–°å¢žä»˜æ¬¾ç”³è¯·ï¼Œç­‰å¾…审核 |
| 1 | 0 | å®¡æ ¸é€šè¿‡ï¼Œä½†å°šæœªä»˜æ¬¾ |
| 1 | 1 | å®¡æ ¸é€šè¿‡ï¼Œå·²éƒ¨åˆ†ä»˜æ¬¾ |
| 1 | 2 | å®¡æ ¸é€šè¿‡ï¼Œå·²å…¨é¢ä»˜æ¬¾ |
| 2 | 0 | å®¡æ ¸ä¸é€šè¿‡ï¼Œä¸èƒ½ä»˜æ¬¾ |
---
## å…­ã€å‰ç«¯æ”¹åŠ¨ç‚¹
### 6.1 ä»˜æ¬¾ç”³è¯·åˆ—表页面
**新增列展示:**
| å­—段 | åˆ—名 | è¯´æ˜Ž |
|------|------|------|
| status | å®¡æ ¸çŠ¶æ€ | 0待审核/1审核通过/2审核不通过 |
| payment_status | ä»˜æ¬¾çŠ¶æ€ï¼ˆæ–°å¢žï¼‰ | 0未付款/1部分付款/2已付款 |
| paidAmount | å·²ä»˜æ¬¾é‡‘额(新增) | ç´¯è®¡å·²ä»˜æ¬¾é‡‘额(查询时计算) |
| paymentAmount | ç”³è¯·é‡‘额 | ä»˜æ¬¾ç”³è¯·æ€»é‡‘额 |
**状态标签颜色建议:**
| ä»˜æ¬¾çŠ¶æ€ | çŠ¶æ€åç§° | æ ‡ç­¾é¢œè‰² |
|----------|----------|----------|
| 0 | æœªä»˜æ¬¾ | warning(橙色) |
| 1 | åˆ†ä»˜æ¬¾ | primary(蓝色) |
| 2 | å·²ä»˜æ¬¾ | success(绿色) |
### 6.2 ä»˜æ¬¾ç”³è¯·åˆ—表筛选
**筛选条件新增:**
```
审核状态:全部 / å¾…审核 / å®¡æ ¸é€šè¿‡ / å®¡æ ¸ä¸é€šè¿‡
付款状态:全部 / æœªä»˜æ¬¾ / éƒ¨åˆ†ä»˜æ¬¾ / å·²ä»˜æ¬¾ï¼ˆæ–°å¢žï¼‰
```
### 6.3 ä»˜æ¬¾ç”³è¯·è¯¦æƒ…/新增页面
无需改动,付款状态为自动计算,不可手动编辑。
### 6.4 æ–°å¢žä»˜æ¬¾å•页面
**展示信息建议:**
| ä¿¡æ¯é¡¹ | è¯´æ˜Ž |
|--------|------|
| ä»˜æ¬¾ç”³è¯·å•号 | å…³è”的付款申请 |
| ç”³è¯·é‡‘额 | paymentAmount |
| å·²ä»˜æ¬¾é‡‘额 | paidAmount(累计) |
| å‰©ä½™å¯ä»˜æ¬¾é‡‘额 | paymentAmount - paidAmount |
| æœ¬æ¬¡ä»˜æ¬¾é‡‘额 | ç”¨æˆ·è¾“å…¥ |
**校验提示:**
- ä»˜æ¬¾ç”³è¯·æœªå®¡æ ¸é€šè¿‡ï¼š"付款申请未审核通过,不能付款"
- è¶…额付款:"累计付款金额不能超过申请金额"
---
## ä¸ƒã€API接口
### 7.1 çŽ°æœ‰æŽ¥å£ï¼ˆæ— éœ€ä¿®æ”¹ï¼‰
| æŽ¥å£ | æ–¹æ³• | è¯´æ˜Ž |
|------|------|------|
| /accountPaymentApplication/listPageAccountPaymentApplication | GET | åˆ†é¡µæŸ¥è¯¢ä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/addAccountPaymentApplication | POST | æ–°å¢žä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/updateAccountPaymentApplication | PUT | ä¿®æ”¹ä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/auditAccountPaymentApplication | PUT | å®¡æ ¸ä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/deleteAccountPaymentApplication | DELETE | åˆ é™¤ä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/exportAccountPaymentApplication | POST | å¯¼å‡ºä»˜æ¬¾ç”³è¯· |
### 7.2 ä»˜æ¬¾å•接口(逻辑已更新)
| æŽ¥å£ | æ–¹æ³• | è¯´æ˜Ž |
|------|------|------|
| /accountPurchasePayment/addAccountPurchasePayment | POST | æ–°å¢žä»˜æ¬¾å•(自动更新payment_status) |
| /accountPurchasePayment/deleteAccountPurchasePayment | DELETE | åˆ é™¤ä»˜æ¬¾å•(自动更新payment_status) |
---
## å…«ã€Mapper查询修改建议
**AccountPaymentApplicationMapper.xml** éœ€è¦åœ¨æŸ¥è¯¢åˆ—表时增加已付款金额的计算:
```xml
<select id="listPageAccountPaymentApplication" resultType="...">
    SELECT
        apa.*,
        -- è®¡ç®—已付款金额
        IFNULL((
            SELECT SUM(app.payment_amount)
            FROM account_purchase_payment app
            WHERE app.account_payment_application_id = apa.id
        ), 0) AS paid_amount
    FROM account_payment_application apa
    ...
</select>
```
---
## ä¹ã€æ³¨æ„äº‹é¡¹
1. **付款状态自动更新:** æ–°å¢ž/删除付款单时自动计算,不可手动修改
2. **状态流转规则:**
   - æœªä»˜æ¬¾(0) â†’ éƒ¨åˆ†ä»˜æ¬¾(1):首次新增付款单
   - éƒ¨åˆ†ä»˜æ¬¾(1) â†’ å·²ä»˜æ¬¾(2):累计付款金额 >= ç”³è¯·é‡‘额
   - å·²ä»˜æ¬¾(2) â†’ éƒ¨åˆ†ä»˜æ¬¾(1):删除付款单后剩余金额 < ç”³è¯·é‡‘额
   - éƒ¨åˆ†ä»˜æ¬¾(1) â†’ æœªä»˜æ¬¾(0):删除所有付款单
3. **付款前提条件:** ä»˜æ¬¾ç”³è¯·å¿…须审核通过(status=1)才能新增付款单
4. **历史数据迁移:** æ–°å¢žå­—段后需执行初始化SQL计算历史数据的付款状态
---
## åã€æµ‹è¯•用例
### 10.1 å¤šæ¬¡ä»˜æ¬¾æµç¨‹
| æ­¥éª¤ | æ“ä½œ | é¢„期结果 |
|------|------|----------|
| 1 | æ–°å¢žä»˜æ¬¾ç”³è¯·ï¼Œé‡‘额10000元 | status=0, payment_status=0 |
| 2 | å®¡æ ¸é€šè¿‡ | status=1, payment_status=0 |
| 3 | æ–°å¢žä»˜æ¬¾å•,金额3000元 | payment_status=1(部分付款), paidAmount=3000 |
| 4 | æ–°å¢žä»˜æ¬¾å•,金额5000元 | payment_status=1(部分付款), paidAmount=8000 |
| 5 | æ–°å¢žä»˜æ¬¾å•,金额2000元 | payment_status=2(已付款), paidAmount=10000 |
### 10.2 åˆ é™¤ä»˜æ¬¾å•
| æ­¥éª¤ | æ“ä½œ | é¢„期结果 |
|------|------|----------|
| 1 | å½“前状态:payment_status=2, paidAmount=10000 | - |
| 2 | åˆ é™¤æœ€åŽä¸€ç¬”付款单(2000元) | payment_status=1, paidAmount=8000 |
| 3 | åˆ é™¤æ‰€æœ‰ä»˜æ¬¾å• | payment_status=0, paidAmount=0 |
### 10.3 å¼‚常场景
| åœºæ™¯ | æ“ä½œ | é¢„期结果 |
|------|------|----------|
| æœªå®¡æ ¸ä»˜æ¬¾ | status=0时新增付款单 | æç¤º"付款申请未审核通过,不能付款" |
| å®¡æ ¸ä¸é€šè¿‡ | status=2时新增付款单 | æç¤º"付款申请未审核通过,不能付款" |
| è¶…额付款 | ç”³è¯·10000,已付9000,再付2000 | æç¤º"累计付款金额不能超过申请金额" |
docs/¸¶¿îÉêÇëÒѸ¶¿î״̬¹¦ÄÜÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,227 @@
# ä»˜æ¬¾ç”³è¯·"已付款"状态功能文档
## ä¸€ã€éœ€æ±‚背景
付款申请(AccountPaymentApplication)原有状态:
- 0:待审核
- 1:审核通过
- 2:审核不通过
新增状态:
- **3:已付款**
当付款申请关联的付款单累计付款金额等于申请金额时,自动将付款申请状态更新为"已付款"。
---
## äºŒã€æ•°æ®åº“变更(SQL)
```sql
-- æ— éœ€ä¿®æ”¹è¡¨ç»“构,status字段为Integer类型,可直接支持新状态值
-- çŠ¶æ€è¯´æ˜Žï¼š
-- 0 = å¾…审核
-- 1 = å®¡æ ¸é€šè¿‡
-- 2 = å®¡æ ¸ä¸é€šè¿‡
-- 3 = å·²ä»˜æ¬¾ï¼ˆæ–°å¢žï¼‰
```
---
## ä¸‰ã€åŽç«¯æ”¹åŠ¨ç‚¹
### 3.1 å®žä½“类修改
**AccountPaymentApplication.java**
```java
/**
 * å®¡æ ¸çŠ¶æ€:0待审核 1审核通过 2审核不通过 3已付款
 */
@ApiModelProperty("审核状态:0待审核 1审核通过 2审核不通过 3已付款")
@Excel(name = "审核状态", readConverterExp = "0=待审核,1=审核通过,2=审核不通过,3=已付款")
private Integer status;
```
### 3.2 ä»˜æ¬¾å•新增逻辑(AccountPurchasePaymentServiceImpl)
**新增付款单时的业务逻辑:**
1. æ ¡éªŒä»˜æ¬¾ç”³è¯·æ˜¯å¦å­˜åœ¨
2. æ ¡éªŒä»˜æ¬¾ç”³è¯·çŠ¶æ€å¿…é¡»ä¸º"审核通过"(status=1)
3. æ ¡éªŒç´¯è®¡ä»˜æ¬¾é‡‘额不能超过申请金额
4. ä¿å­˜ä»˜æ¬¾å•
5. **新增**:如果累计付款金额等于申请金额,自动更新付款申请状态为"已付款"(status=3)
```java
// å¦‚果累计付款金额等于申请金额,更新付款申请状态为已付款
if (result && accountPaymentApplication.getPaymentAmount().compareTo(newTotal) == 0) {
    accountPaymentApplication.setStatus(3); // å·²ä»˜æ¬¾
    accountPaymentApplicationMapper.updateById(accountPaymentApplication);
}
```
### 3.3 ä»˜æ¬¾å•删除逻辑
**删除付款单时的业务逻辑:**
1. æ ¡éªŒä»˜æ¬¾å•是否已生成对账单,如有则不能删除
2. åˆ é™¤ä»˜æ¬¾å•
3. **新增**:如果付款申请原状态为"已付款"(status=3),删除后剩余付款金额小于申请金额,则恢复状态为"审核通过"(status=1)
---
## å››ã€ä¸šåŠ¡æµç¨‹å›¾
```
┌─────────────────────────────────────────────────────────────────┐
│                       ä»˜æ¬¾ç”³è¯·æµç¨‹                               â”‚
├─────────────────────────────────────────────────────────────────┤
│                                                                 â”‚
│   æ–°å¢žä»˜æ¬¾ç”³è¯· â”€â”€â–º status=0(待审核)                              â”‚
│         â”‚                                                       â”‚
│         â–¼                                                       â”‚
│   â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”                                               â”‚
│   â”‚   å®¡æ ¸æ“ä½œ   â”‚                                               â”‚
│   â””─────────────┘                                               â”‚
│         â”‚                                                       â”‚
│    â”Œâ”€â”€â”€â”€â”´â”€â”€â”€â”€â”                                                  â”‚
│    â–¼         â–¼                                                  â”‚
│ é€šè¿‡       ä¸é€šè¿‡                                                â”‚
│    â”‚         â”‚                                                  â”‚
│    â–¼         â–¼                                                  â”‚
│ status=1   status=2                                             â”‚
│ (审核通过) (审核不通过)                                          â”‚
│    â”‚                                                            â”‚
│    â–¼                                                            â”‚
│   å¯ä»¥æ–°å¢žä»˜æ¬¾å•                                                 â”‚
│    â”‚                                                            â”‚
│    â–¼                                                            â”‚
│   â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”                      â”‚
│   â”‚ ç´¯è®¡ä»˜æ¬¾é‡‘额 = ç”³è¯·é‡‘额?             â”‚                      â”‚
│   â””─────────────────────────────────────┘                      â”‚
│         â”‚                                                       â”‚
│    â”Œâ”€â”€â”€â”€â”´â”€â”€â”€â”€â”                                                  â”‚
│   æ˜¯         å¦                                                  â”‚
│    â”‚         â”‚                                                  â”‚
│    â–¼         â”‚                                                  â”‚
│ status=3     â”‚                                                  â”‚
│ (已付款)     â”‚                                                  â”‚
│             â”‚                                                   â”‚
│             â–¼                                                   â”‚
│        ç»§ç»­ç­‰å¾…付款                                              â”‚
│                                                                 â”‚
└─────────────────────────────────────────────────────────────────┘
```
---
## äº”、前端改动点
### 5.1 ä»˜æ¬¾ç”³è¯·åˆ—表页面
**状态列展示调整:**
| çŠ¶æ€å€¼ | çŠ¶æ€åç§° | æ ‡ç­¾é¢œè‰²ï¼ˆå»ºè®®ï¼‰ |
|--------|----------|------------------|
| 0 | å¾…审核 | warning(橙色) |
| 1 | å®¡æ ¸é€šè¿‡ | success(绿色) |
| 2 | å®¡æ ¸ä¸é€šè¿‡ | danger(红色) |
| 3 | å·²ä»˜æ¬¾ | info(蓝色)或 primary |
**字典配置(sys_dict_data表):**
```sql
-- å¦‚果系统有字典配置,需要添加新状态
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark)
VALUES (4, '已付款', '3', 'payment_application_status', '', 'primary', 'N', '0', 'admin', NOW(), '', NULL, '付款申请已付款状态');
```
### 5.2 ä»˜æ¬¾ç”³è¯·è¯¦æƒ…页面
**状态展示:** éœ€è¦æ”¯æŒæ˜¾ç¤º"已付款"状态
### 5.3 æ–°å¢žä»˜æ¬¾å•页面
**前置条件校验提示:**
- å¦‚果付款申请状态不是"审核通过",提示:"付款申请未审核通过,不能付款"
- å¦‚果累计付款金额超过申请金额,提示:"累计付款金额不能超过申请金额"
**展示信息建议:**
- æ˜¾ç¤ºä»˜æ¬¾ç”³è¯·é‡‘额
- æ˜¾ç¤ºå·²ä»˜æ¬¾é‡‘额(累计)
- æ˜¾ç¤ºå‰©ä½™å¯ä»˜æ¬¾é‡‘额
### 5.4 ä»˜æ¬¾ç”³è¯·åˆ—表筛选
**状态筛选下拉框:**
```
全部
待审核
审核通过
审核不通过
已付款(新增)
```
---
## å…­ã€API接口
### 6.1 çŽ°æœ‰æŽ¥å£ï¼ˆæ— éœ€ä¿®æ”¹ï¼‰
| æŽ¥å£ | æ–¹æ³• | è¯´æ˜Ž |
|------|------|------|
| /accountPaymentApplication/listPageAccountPaymentApplication | GET | åˆ†é¡µæŸ¥è¯¢ä»˜æ¬¾ç”³è¯·åˆ—表 |
| /accountPaymentApplication/addAccountPaymentApplication | POST | æ–°å¢žä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/updateAccountPaymentApplication | PUT | ä¿®æ”¹ä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/auditAccountPaymentApplication | PUT | å®¡æ ¸ä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/deleteAccountPaymentApplication | DELETE | åˆ é™¤ä»˜æ¬¾ç”³è¯· |
| /accountPaymentApplication/exportAccountPaymentApplication | POST | å¯¼å‡ºä»˜æ¬¾ç”³è¯· |
### 6.2 ä»˜æ¬¾å•接口(逻辑已更新)
| æŽ¥å£ | æ–¹æ³• | è¯´æ˜Ž |
|------|------|------|
| /accountPurchasePayment/addAccountPurchasePayment | POST | æ–°å¢žä»˜æ¬¾å•(自动更新付款申请状态) |
| /accountPurchasePayment/deleteAccountPurchasePayment | DELETE | åˆ é™¤ä»˜æ¬¾å•(自动恢复付款申请状态) |
---
## ä¸ƒã€æ³¨æ„äº‹é¡¹
1. **状态流转规则:**
   - å¾…审核(0) â†’ å®¡æ ¸é€šè¿‡(1) æˆ– å®¡æ ¸ä¸é€šè¿‡(2)
   - å®¡æ ¸é€šè¿‡(1) â†’ å·²ä»˜æ¬¾(3)(自动,当累计付款金额等于申请金额)
   - å·²ä»˜æ¬¾(3) â†’ å®¡æ ¸é€šè¿‡(1)(自动,当删除付款单后剩余金额小于申请金额)
2. **删除付款申请时:**
   - å¦‚果状态为"已付款",需要先删除关联的付款单
   - å·²ç”Ÿæˆå¯¹è´¦å•的付款单不能删除,因此已付款状态通常不能直接删除付款申请
3. **金额计算:**
   - ç´¯è®¡ä»˜æ¬¾é‡‘额 = æ‰€æœ‰å…³è”付款单的paymentAmount之和
   - å½“累计付款金额 >= ç”³è¯·é‡‘额时,状态变为"已付款"
---
## å…«ã€æµ‹è¯•用例
### 8.1 æ­£å¸¸ä»˜æ¬¾æµç¨‹
| æ­¥éª¤ | æ“ä½œ | é¢„期结果 |
|------|------|----------|
| 1 | æ–°å¢žä»˜æ¬¾ç”³è¯·ï¼Œé‡‘额10000 | status=0(待审核) |
| 2 | å®¡æ ¸é€šè¿‡ | status=1(审核通过) |
| 3 | æ–°å¢žä»˜æ¬¾å•,金额6000 | status=1(审核通过),累计付款6000 |
| 4 | æ–°å¢žä»˜æ¬¾å•,金额4000 | status=3(已付款),累计付款10000 |
### 8.2 åˆ†æ‰¹ä»˜æ¬¾åŽåˆ é™¤
| æ­¥éª¤ | æ“ä½œ | é¢„期结果 |
|------|------|----------|
| 1 | å·²ä»˜æ¬¾ç”³è¯·ï¼Œç´¯è®¡ä»˜æ¬¾10000 | status=3(已付款) |
| 2 | åˆ é™¤å…¶ä¸­ä¸€ç¬”付款单(6000) | status=1(审核通过),累计付款4000 |
### 8.3 å¼‚常场景
| åœºæ™¯ | æ“ä½œ | é¢„期结果 |
|------|------|----------|
| æœªå®¡æ ¸ç›´æŽ¥ä»˜æ¬¾ | status=0时新增付款单 | æç¤º"付款申请未审核通过,不能付款" |
| è¶…额付款 | ç”³è¯·10000,累计已付9000,再付2000 | æç¤º"累计付款金额不能超过申请金额" |
docs/ÏúÊŲ̂ÕËÏÖÔÚÉú²ú×Ö¶Îͬ²½Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,263 @@
# é”€å”®å°è´¦"现在生产"字段同步优化文档
## ä¸€ã€éœ€æ±‚背景
新增销售台账时,如果勾选"现在生产",需要将以下字段同步到主生产计划和生产订单:
1. **单位**:从产品规格同步
2. **产品数量**:作为主计划所需数量
3. **总数**和**每件数量**:同步到生产侧,用于入库时计算实际入库量
### ä¸šåŠ¡è§„åˆ™
- **主计划所需数量** = äº§å“æ•°é‡ï¼ˆquantity),不是总数
- **生产入库数量** = æŠ¥å·¥æ•°é‡ Ã— æ¯ä»¶æ•°é‡ï¼ˆsingleQuantity)
---
## äºŒã€æ•°æ®åº“变更(SQL)
```sql
-- =====================================================
-- 1. ä¸»ç”Ÿäº§è®¡åˆ’表新增字段
-- =====================================================
ALTER TABLE `production_plan`
ADD COLUMN `unit` VARCHAR(50) NULL COMMENT '单位' AFTER `product_model_id`,
ADD COLUMN `quantity` DECIMAL(24,6) NULL COMMENT '产品数量' AFTER `unit`,
ADD COLUMN `single_quantity` DECIMAL(24,6) NULL COMMENT '每件数量' AFTER `quantity`,
ADD COLUMN `total_quantity` DECIMAL(24,6) NULL COMMENT '总数' AFTER `single_quantity`;
-- =====================================================
-- 2. ç”Ÿäº§è®¢å•表新增字段
-- =====================================================
ALTER TABLE `production_order`
ADD COLUMN `unit` VARCHAR(50) NULL COMMENT '单位' AFTER `product_model_id`,
ADD COLUMN `single_quantity` DECIMAL(24,6) NULL COMMENT '每件数量' AFTER `quantity`,
ADD COLUMN `total_quantity` DECIMAL(24,6) NULL COMMENT '总数' AFTER `single_quantity`;
-- =====================================================
-- 3. åŽ†å²æ•°æ®è¿ç§»ï¼ˆå¯é€‰ï¼Œä»Žäº§å“è§„æ ¼è¡¨å›žå¡«å•ä½ï¼‰
-- =====================================================
-- æ›´æ–°ä¸»ç”Ÿäº§è®¡åˆ’的单位
UPDATE production_plan pp
LEFT JOIN product_model pm ON pp.product_model_id = pm.id
SET pp.unit = pm.unit
WHERE pp.unit IS NULL;
-- æ›´æ–°ç”Ÿäº§è®¢å•的单位
UPDATE production_order po
LEFT JOIN product_model pm ON po.product_model_id = pm.id
SET po.unit = pm.unit
WHERE po.unit IS NULL;
-- =====================================================
-- 4. å­—段说明
-- =====================================================
-- production_plan è¡¨å­—段:
--   qty_required    - éœ€æ±‚数量(原有字段,改为存储产品数量 quantity)
--   unit            - å•位(新增)
--   quantity        - äº§å“æ•°é‡ï¼ˆæ–°å¢žï¼‰
--   single_quantity - æ¯ä»¶æ•°é‡ï¼ˆæ–°å¢žï¼‰
--   total_quantity  - æ€»æ•°ï¼ˆæ–°å¢žï¼‰
--
-- production_order è¡¨å­—段:
--   quantity        - éœ€æ±‚数量(原有字段,保持不变)
--   unit            - å•位(新增)
--   single_quantity - æ¯ä»¶æ•°é‡ï¼ˆæ–°å¢žï¼‰
--   total_quantity  - æ€»æ•°ï¼ˆæ–°å¢žï¼‰
```
---
## ä¸‰ã€åŽç«¯æ”¹åŠ¨ç‚¹
### 3.1 å®žä½“类修改
#### ProductionPlan.java(主生产计划)
新增字段:
```java
@Schema(description = "单位")
private String unit;
@Schema(description = "产品数量")
private BigDecimal quantity;
@Schema(description = "每件数量")
private BigDecimal singleQuantity;
@Schema(description = "总数")
private BigDecimal totalQuantity;
```
#### ProductionOrder.java(生产订单)
新增字段:
```java
@Schema(description = "单位")
private String unit;
@Schema(description = "每件数量")
private BigDecimal singleQuantity;
@Schema(description = "总数")
private BigDecimal totalQuantity;
```
### 3.2 SalesLedgerProductServiceImpl.addProductionData() æ–¹æ³•修改
**修改前**:
```java
productionPlan.setQtyRequired(normalizeTotalQuantity(
        salesLedgerProduct.getTotalQuantity(),
        salesLedgerProduct.getQuantity(),
        salesLedgerProduct.getSingleQuantity()
)); // éœ€æ±‚数量 = æ€»æ•°
```
**修改后**:
```java
// æŸ¥è¯¢äº§å“è§„格获取单位
ProductModel productModel = productModelMapper.selectById(salesLedgerProduct.getProductModelId());
productionPlan.setQtyRequired(salesLedgerProduct.getQuantity()); // éœ€æ±‚数量 = äº§å“æ•°é‡
productionPlan.setUnit(productModel != null ? productModel.getUnit() : null); // å•位
productionPlan.setQuantity(salesLedgerProduct.getQuantity()); // äº§å“æ•°é‡
productionPlan.setSingleQuantity(normalizeSingleQuantity(salesLedgerProduct.getSingleQuantity())); // æ¯ä»¶æ•°é‡
productionPlan.setTotalQuantity(normalizeTotalQuantity(
        salesLedgerProduct.getTotalQuantity(),
        salesLedgerProduct.getQuantity(),
        salesLedgerProduct.getSingleQuantity()
)); // æ€»æ•°
```
### 3.3 ç”Ÿäº§è®¡åˆ’下发到生产订单时同步字段
在 `ProductionPlanServiceImpl.combine()` æ–¹æ³•中,合并下发生产订单时,需要同步以下字段:
- unit(单位)
- singleQuantity(每件数量)
- totalQuantity(总数)
### 3.4 ç”Ÿäº§å…¥åº“数量计算修改
**修改位置**:`ProductionProductMainServiceImpl.addProductMainByProductionTask()` æ–¹æ³•
**修改逻辑**:
```java
// èŽ·å–ç”Ÿäº§è®¢å•å…³è”çš„æ¯ä»¶æ•°é‡
ProductionOrder productionOrder = productionOrderMapper.selectById(productionOperationTask.getProductionOrderId());
BigDecimal singleQuantity = productionOrder.getSingleQuantity();
if (singleQuantity == null || singleQuantity.compareTo(BigDecimal.ZERO) <= 0) {
    singleQuantity = BigDecimal.ONE;
}
// å…¥åº“数量 = æŠ¥å·¥æ•°é‡ Ã— æ¯ä»¶æ•°é‡
BigDecimal stockInQuantity = productQty.multiply(singleQuantity);
// å…¥åº“记录
StockInventoryDto stockInventoryDto = new StockInventoryDto();
stockInventoryDto.setQualitity(stockInQuantity); // ä½¿ç”¨è®¡ç®—后的入库数量
```
---
## å››ã€å‰ç«¯æ”¹åŠ¨ç‚¹
### 4.1 é”€å”®å°è´¦æ–°å¢ž/编辑页面
无需修改,"现在生产"开关已存在,后端自动同步数据到生产计划。
### 4.2 ä¸»ç”Ÿäº§è®¡åˆ’列表页面
新增展示列:
| å­—段 | åˆ—名 | è¯´æ˜Ž |
|------|------|------|
| unit | å•位 | ä»Žäº§å“è§„格同步 |
| quantity | äº§å“æ•°é‡ | åŽŸ qtyRequired æ”¹åæˆ–新增展示 |
| singleQuantity | æ¯ä»¶æ•°é‡ | æ–°å¢žå±•示 |
| totalQuantity | æ€»æ•° | æ–°å¢žå±•示 |
### 4.3 ç”Ÿäº§è®¢å•列表页面
新增展示列:
| å­—段 | åˆ—名 | è¯´æ˜Ž |
|------|------|------|
| unit | å•位 | ä»Žç”Ÿäº§è®¡åˆ’同步 |
| singleQuantity | æ¯ä»¶æ•°é‡ | æ–°å¢žå±•示 |
| totalQuantity | æ€»æ•° | æ–°å¢žå±•示 |
### 4.4 ç”Ÿäº§å…¥åº“/报工页面
**展示调整**:
- æŠ¥å·¥æ•°é‡ï¼šç”¨æˆ·è¾“入的产品数量
- å®žé™…入库数量:自动计算 = æŠ¥å·¥æ•°é‡ Ã— æ¯ä»¶æ•°é‡
**前端计算展示示例**:
```javascript
// æŠ¥å·¥è¡¨å•
const reportForm = {
  quantity: 0,        // æŠ¥å·¥æ•°é‡ï¼ˆäº§å“æ•°é‡ï¼‰
  singleQuantity: 1,  // æ¯ä»¶æ•°é‡ï¼ˆä»Žç”Ÿäº§è®¢å•带出,只读)
  actualQuantity: 0   // å®žé™…入库数量 = quantity Ã— singleQuantity
};
// è®¡ç®—实际入库数量
const calcActualQuantity = () => {
  const qty = reportForm.quantity || 0;
  const single = reportForm.singleQuantity || 1;
  reportForm.actualQuantity = qty * single;
};
```
---
## äº”、数据流转图
```
┌─────────────────────────────────────────────────────────────────┐
│                        é”€å”®å°è´¦äº§å“                              â”‚
│  SalesLedgerProduct                                             â”‚
│  â”œâ”€ quantity (产品数量) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”                     â”‚
│  â”œâ”€ singleQuantity (每件数量) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”                 â”‚
│  â”œâ”€ totalQuantity (总数) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”¼â”€â”€â”€â”             â”‚
│  â”œâ”€ unit (单位,来自product_model) â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”¼â”€â”€â”€â”¼â”€â”€â”€â”         â”‚
│  â””─ isProduction = true â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”˜   â”‚   â”‚   â”‚         â”‚
└────────────────────────────────────────────────┘   â”‚   â”‚   â”‚     â”‚
                                                     â”‚   â”‚   â”‚     â”‚
                                                     â–¼   â–¼   â–¼     â”‚
┌─────────────────────────────────────────────────────────────────┐
│                        ä¸»ç”Ÿäº§è®¡åˆ’                                â”‚
│  ProductionPlan                                                 â”‚
│  â”œâ”€ qtyRequired (需求数量) = quantity (产品数量)                 â”‚
│  â”œâ”€ quantity (产品数量) â—„─────────────────────────────────────┐ â”‚
│  â”œâ”€ singleQuantity (每件数量) â—„──────────────────────────────┐│ â”‚
│  â”œâ”€ totalQuantity (总数) â—„──────────────────────────────────┐││ â”‚
│  â””─ unit (单位) â—„──────────────────────────────────────────┐│││ â”‚
└─────────────────────────────────────────────────────────────┘│││ â”‚
                                                               â”‚││ â”‚
                          ä¸‹å‘到生产订单                         â”‚││ â”‚
                                                               â–¼â–¼â–¼ â”‚
┌─────────────────────────────────────────────────────────────────┐
│                        ç”Ÿäº§è®¢å•                                  â”‚
│  ProductionOrder                                               â”‚
│  â”œâ”€ quantity (需求数量)                                         â”‚
│  â”œâ”€ singleQuantity (每件数量)                                   â”‚
│  â”œâ”€ totalQuantity (总数)                                        â”‚
│  â””─ unit (单位)                                                 â”‚
└─────────────────────────────────────────────────────────────────┘
                               â”‚
                               â–¼
┌─────────────────────────────────────────────────────────────────┐
│                        ç”Ÿäº§æŠ¥å·¥                                  â”‚
│  ProductionProductMain                                         â”‚
│  â”œâ”€ æŠ¥å·¥æ•°é‡ (quantity)                                         â”‚
│  â””─ å®žé™…入库数量 = æŠ¥å·¥æ•°é‡ Ã— æ¯ä»¶æ•°é‡                           â”‚
└─────────────────────────────────────────────────────────────────┘
```
---
## å…­ã€æ³¨æ„äº‹é¡¹
1. **历史数据兼容**:历史数据 `singleQuantity` é»˜è®¤ä¸º 1,入库数量计算不受影响
2. **字段优先级**:
   - ä¸»è®¡åˆ’所需数量 `qtyRequired` æ”¹ä¸ºå­˜å‚¨äº§å“æ•°é‡ `quantity`
   - æ€»æ•° `totalQuantity` = quantity Ã— singleQuantity(冗余存储,便于查询展示)
3. **单位来源**:单位从 `product_model` è¡¨åŒæ­¥ï¼Œé”€å”®å°è´¦å­è¡¨æœ¬èº«ä¸å­˜å‚¨å•位
4. **编辑同步**:销售台账产品编辑时,会删除旧生产计划并重新创建(如果未下发)
src/main/java/com/ruoyi/account/bean/dto/purchase/AccountPaymentApplicationDto.java
@@ -17,9 +17,12 @@
    @Schema(description = "申请单号")
    private String invoiceApplicationNo;
    @Schema(description = "审核状态:0待审核1审核通过2审核不通过")
    @Schema(description = "审核状态:0待审核 1审核通过 2审核不通过")
    private Integer status;
    @Schema(description = "付款状态:0未付款 1部分付款 2已付款")
    private Integer paymentStatus;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
src/main/java/com/ruoyi/account/bean/vo/purchase/AccountPaymentApplicationVo.java
@@ -6,6 +6,8 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
@Schema(name = "AccountPaymentApplicationVo", description = "财务管理--付款申请台账(返回)")
@ExcelIgnoreUnannotated
@@ -19,5 +21,8 @@
    @Excel(name = "入库单号")
    private String inboundBatches;
    @Schema(description = "已付款金额")
    @Excel(name = "已付款金额")
    private BigDecimal paidAmount;
}
src/main/java/com/ruoyi/account/pojo/purchase/AccountPaymentApplication.java
@@ -126,13 +126,27 @@
    private String remark;
    /**
     * å®¡æ ¸çŠ¶æ€:0待审核1审核通过2审核不通过
     * å®¡æ ¸çŠ¶æ€:0待审核 1审核通过 2审核不通过
     */
    @ApiModelProperty("审核状态:0待审核1审核通过2审核不通过")
    @ApiModelProperty("审核状态:0待审核 1审核通过 2审核不通过")
    @Excel(name = "审核状态",readConverterExp = "0=待审核,1=审核通过,2=审核不通过")
    private Integer status;
    /**
     * ä»˜æ¬¾çŠ¶æ€:0未付款 1部分付款 2已付款
     */
    @ApiModelProperty("付款状态:0未付款 1部分付款 2已付款")
    @Excel(name = "付款状态",readConverterExp = "0=未付款,1=部分付款,2=已付款")
    private Integer paymentStatus;
    /**
     * å·²ä»˜æ¬¾é‡‘额
     */
    @ApiModelProperty("已付款金额")
    @TableField(exist = false)
    private BigDecimal paidAmount;
    /**
     * ä»˜æ¬¾é‡‘额
     */
    @ApiModelProperty("付款金额")
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPurchasePaymentServiceImpl.java
@@ -54,14 +54,31 @@
        if (StringUtils.isEmpty(accountPurchasePayment.getPaymentNumber())) {
            accountPurchasePayment.setPaymentNumber(genAccountPurchasePaymentNo());
        }
        //校验累计付款金额不能超过申请金额
        //校验付款申请是否存在且审核通过
        AccountPaymentApplication accountPaymentApplication = accountPaymentApplicationMapper.selectById(accountPurchasePayment.getAccountPaymentApplicationId());
        List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(Wrappers.<AccountPurchasePayment>lambdaQuery().eq(AccountPurchasePayment::getAccountPaymentApplicationId, accountPurchasePayment.getAccountPaymentApplicationId()));
        BigDecimal totalPaymentAmount = accountPurchasePayments.stream().map(AccountPurchasePayment::getPaymentAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (accountPaymentApplication.getPaymentAmount().compareTo(totalPaymentAmount) < 0) {
        if (accountPaymentApplication == null) {
            throw new ServiceException("付款申请不存在");
        }
        if (accountPaymentApplication.getStatus() == null || accountPaymentApplication.getStatus() != 1) {
            throw new ServiceException("付款申请未审核通过,不能付款");
        }
        //校验累计付款金额不能超过申请金额
        List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(
                Wrappers.<AccountPurchasePayment>lambdaQuery()
                        .eq(AccountPurchasePayment::getAccountPaymentApplicationId, accountPurchasePayment.getAccountPaymentApplicationId()));
        BigDecimal totalPaymentAmount = accountPurchasePayments.stream()
                .map(AccountPurchasePayment::getPaymentAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal newTotal = totalPaymentAmount.add(accountPurchasePayment.getPaymentAmount());
        if (newTotal.compareTo(accountPaymentApplication.getPaymentAmount()) > 0) {
            throw new ServiceException("累计付款金额不能超过申请金额");
        }
        return save(accountPurchasePayment);
        boolean result = save(accountPurchasePayment);
        // æ›´æ–°ä»˜æ¬¾ç”³è¯·çš„付款状态
        if (result) {
            updatePaymentStatus(accountPaymentApplication, newTotal);
        }
        return result;
    }
    @Override
@@ -73,6 +90,9 @@
    @Override
    public boolean deleteAccountPurchasePayment(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return false;
        }
        //如果该付款单已经生成对账单则无法删除
        List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectByIds(ids);
        List<String> strings = accountPurchasePayments.stream().map(AccountPurchasePayment::getPaymentNumber).toList();
@@ -81,7 +101,47 @@
        if (CollectionUtils.isNotEmpty(accountStatementDetails)){
            throw new ServiceException("该付款单已经生成对账单,无法删除");
        }
        return removeByIds(ids);
        boolean result = removeByIds(ids);
        // åˆ é™¤æˆåŠŸåŽï¼Œæ›´æ–°ä»˜æ¬¾ç”³è¯·çš„ä»˜æ¬¾çŠ¶æ€
        if (result) {
            for (AccountPurchasePayment payment : accountPurchasePayments) {
                if (payment.getAccountPaymentApplicationId() != null) {
                    AccountPaymentApplication application = accountPaymentApplicationMapper.selectById(payment.getAccountPaymentApplicationId());
                    if (application != null) {
                        // è®¡ç®—剩余付款金额
                        List<AccountPurchasePayment> remainingPayments = accountPurchasePaymentMapper.selectList(
                                Wrappers.<AccountPurchasePayment>lambdaQuery()
                                        .eq(AccountPurchasePayment::getAccountPaymentApplicationId, application.getId()));
                        BigDecimal remainingAmount = remainingPayments.stream()
                                .map(AccountPurchasePayment::getPaymentAmount)
                                .reduce(BigDecimal.ZERO, BigDecimal::add);
                        updatePaymentStatus(application, remainingAmount);
                    }
                }
            }
        }
        return result;
    }
    /**
     * æ›´æ–°ä»˜æ¬¾ç”³è¯·çš„付款状态
     * @param application ä»˜æ¬¾ç”³è¯·
     * @param paidAmount å·²ä»˜æ¬¾é‡‘额
     */
    private void updatePaymentStatus(AccountPaymentApplication application, BigDecimal paidAmount) {
        BigDecimal applyAmount = application.getPaymentAmount();
        int newPaymentStatus;
        if (paidAmount.compareTo(BigDecimal.ZERO) == 0) {
            newPaymentStatus = 0; // æœªä»˜æ¬¾
        } else if (paidAmount.compareTo(applyAmount) < 0) {
            newPaymentStatus = 1; // éƒ¨åˆ†ä»˜æ¬¾
        } else {
            newPaymentStatus = 2; // å·²ä»˜æ¬¾
        }
        if (application.getPaymentStatus() == null || application.getPaymentStatus() != newPaymentStatus) {
            application.setPaymentStatus(newPaymentStatus);
            accountPaymentApplicationMapper.updateById(application);
        }
    }
    private String genAccountPurchasePaymentNo() {
src/main/java/com/ruoyi/production/controller/ProductionOperationTaskController.java
@@ -76,6 +76,14 @@
        productionOperationTaskService.down(response, dto);
    }
    @GetMapping("/byOrder/{orderId}")
    @Operation(summary = "根据生产订单ID查询分组数据")
    public R<List<ProductionOperationTaskVo>> byOrder(@PathVariable Long orderId) {
        ProductionOperationTaskDto dto = new ProductionOperationTaskDto();
        dto.setProductionOrderId(orderId);
        return R.ok(productionOperationTaskService.listProductionOperationTask(dto));
    }
    @GetMapping("/getOperation")
    @Operation(summary = "工序详情查询")
    public R<List<ProductionOperationTaskVo>> getOperation(ProductionOperationTaskDto dto) {
src/main/java/com/ruoyi/production/mapper/ProductionOperationTaskMapper.java
@@ -43,4 +43,6 @@
    ProductionOperationTaskDto getProductWorkOrderFlowCard(@Param("id") Long id);
    List<ProductionOperationTaskVo> getOperation(@Param("c") ProductionOperationTaskDto dto);
    List<ProductionOperationTaskVo> listGroupedProductionOperationTask(@Param("c") ProductionOperationTaskDto dto);
}
src/main/java/com/ruoyi/production/pojo/ProductionPlan.java
@@ -68,6 +68,18 @@
    @Schema(description = "产品型号id")
    private Long productModelId;
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "产品数量")
    private BigDecimal quantity;
    @Schema(description = "每件数量")
    private BigDecimal singleQuantity;
    @Schema(description = "总数")
    private BigDecimal totalQuantity;
    @Schema(description = "需求数量")
    private BigDecimal qtyRequired;
src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java
@@ -71,8 +71,8 @@
    @Override
    public List<ProductionOperationTaskVo> listProductionOperationTask(ProductionOperationTaskDto dto) {
        // æŸ¥è¯¢å·¥åºä»»åŠ¡åˆ—è¡¨
        List<ProductionOperationTaskVo> result = BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOperationTaskVo.class);
        // æŒ‰ç”Ÿäº§è®¢å•分组查询工序任务列表
        List<ProductionOperationTaskVo> result = baseMapper.listGroupedProductionOperationTask(dto);
        fillOperationTypes(result);
        fillUserNames(result);
        return result;
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -4,6 +4,8 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.production.mapper.*;
@@ -86,6 +88,8 @@
    private StockUtils stockUtils;
    @Autowired
    private StockInventoryMapper stockInventoryMapper;
    @Autowired
    private ProductModelMapper productModelMapper;
    @Override
    public SalesLedgerProduct selectSalesLedgerProductById(Long id) {
@@ -225,12 +229,28 @@
            return;
        }
        SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerProduct.getSalesLedgerId());
        // èŽ·å–å•ä½ï¼šä¼˜å…ˆä½¿ç”¨å‰ç«¯ä¼ å…¥çš„unit,否则从产品规格获取
        String unit = salesLedgerProduct.getUnit();
        if (unit == null || unit.isEmpty()) {
            ProductModel productModel = productModelMapper.selectById(salesLedgerProduct.getProductModelId());
            unit = productModel != null ? productModel.getUnit() : null;
        }
        ProductionPlan productionPlan = new ProductionPlan();
        productionPlan.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId());
        productionPlan.setSalesLedgerProductId(salesLedgerProduct.getId());
        productionPlan.setMpsNo(generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))));
        productionPlan.setProductModelId(salesLedgerProduct.getProductModelId());
        productionPlan.setQtyRequired(normalizeTotalQuantity(
        // éœ€æ±‚数量 = äº§å“æ•°é‡ï¼ˆä¸æ˜¯æ€»æ•°ï¼‰
        productionPlan.setQtyRequired(salesLedgerProduct.getQuantity());
        // åŒæ­¥å•位
        productionPlan.setUnit(unit);
        // åŒæ­¥äº§å“æ•°é‡
        productionPlan.setQuantity(salesLedgerProduct.getQuantity());
        // åŒæ­¥æ¯ä»¶æ•°é‡
        productionPlan.setSingleQuantity(normalizeSingleQuantity(salesLedgerProduct.getSingleQuantity()));
        // åŒæ­¥æ€»æ•°
        productionPlan.setTotalQuantity(normalizeTotalQuantity(
                salesLedgerProduct.getTotalQuantity(),
                salesLedgerProduct.getQuantity(),
                salesLedgerProduct.getSingleQuantity()
src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml
@@ -18,6 +18,7 @@
        <result column="apply_date" property="applyDate" />
        <result column="remark" property="remark" />
        <result column="status" property="status" />
        <result column="payment_status" property="paymentStatus" />
        <result column="payment_amount" property="paymentAmount" />
    </resultMap>
     <select
@@ -25,7 +26,8 @@
         resultType="com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo">
        select * from (select apa.*,
               sm.supplier_name,
               GROUP_CONCAT(sir.inbound_batches SEPARATOR ',') AS inboundBatches
               GROUP_CONCAT(sir.inbound_batches SEPARATOR ',') AS inboundBatches,
               IFNULL((SELECT SUM(app.payment_amount) FROM account_purchase_payment app WHERE app.account_payment_application_id = apa.id), 0) AS paidAmount
        from account_payment_application apa
        left join supplier_manage sm on apa.supplier_id = sm.id
        left join stock_in_record sir on FIND_IN_SET(sir.id, apa.stock_in_record_ids) > 0
@@ -40,6 +42,9 @@
             <if test="req.status != null">
                 AND A.status = #{req.status}
            </if>
            <if test="req.paymentStatus != null">
                 AND A.payment_status = #{req.paymentStatus}
            </if>
            <if test="req.startDate != null and req.endDate != null">
                AND A.apply_date BETWEEN #{req.startDate} AND #{req.endDate}
            </if>
src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
@@ -21,21 +21,29 @@
        <result column="dept_id" property="deptId" />
    </resultMap>
    <select id="pageProductionOperationTask" resultType="com.ruoyi.production.bean.vo.ProductionOperationTaskVo">
        select pot.*,
               po.nps_no as npsNo,
               po.is_end_order as endOrder,
               p.product_name as productName,
               pm.model as model,
               pm.unit as unit,
               poro.operation_name as operationName,
               poro.type as type,
               IFNULL(scrapStat.scrapQty, 0) AS scrapQty,
        ROUND(IFNULL(pot.complete_quantity, 0) / NULLIF(pot.plan_quantity, 0) * 100, 2) AS completionStatus,
        CASE
            WHEN pot.work_order_no LIKE 'FG%' THEN '返工返修'
            ELSE '正常'
            END AS work_order_type
    <sql id="groupedProductionOperationTaskSelect">
        select po.id                   as productionOrderId,
               po.nps_no               as npsNo,
               po.is_end_order         as endOrder,
               p.product_name          as productName,
               pm.model                as model,
               pm.unit                 as unit,
               group_concat(distinct poro.operation_name order by poro.drag_sort, poro.operation_name separator ',') as operationName,
               count(distinct pot.id)  as productionTaskCount,
               sum(ifnull(pot.plan_quantity, 0))     as planQuantity,
               sum(ifnull(pot.complete_quantity, 0)) as completeQuantity,
               sum(ifnull(pot.complete_quantity, 0)) as goodQuantity,
               sum(ifnull(scrapStat.scrapQty, 0))    as scrapQty,
               round(sum(ifnull(pot.complete_quantity, 0)) / nullif(sum(ifnull(pot.plan_quantity, 0)), 0) * 100, 2) as completionStatus,
               max(poro.type)          as type,
               case when sum(case when pot.work_order_no like 'FG%' then 1 else 0 end) > 0
                   then '返工返修' else '正常' end as workOrderType,
               group_concat(distinct pot.work_order_no order by pot.work_order_no separator ',') as workOrderNo,
               min(pot.plan_start_time)   as planStartTime,
               max(pot.plan_end_time)     as planEndTime,
               min(pot.actual_start_time) as actualStartTime,
               max(pot.actual_end_time)   as actualEndTime,
               max(pot.status)            as status
        from production_operation_task pot
                 left join production_order po on pot.production_order_id = po.id
                 left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
@@ -48,30 +56,31 @@
                     left join production_product_output ppo on ppo.production_product_main_id = ppm.id
            group by ppm.production_operation_task_id
        ) scrapStat on scrapStat.taskId = pot.id
    </sql>
    <sql id="groupedProductionOperationTaskWhere">
        <where>
            <if test="c != null and c.id != null">
                and pot.id = #{c.id}
            </if>
            <if test="c != null and c.npsNo != null">
            <if test="c != null and c.npsNo != null and c.npsNo != ''">
                and po.nps_no like concat('%', #{c.npsNo}, '%')
            </if>
            <if test="c != null and c.productionOrderId != null">
                and pot.production_order_id = #{c.productionOrderId}
            </if>
            <if test="c != null and c.productionOrderRoutingOperationId != null">
                and pot.production_order_routing_operation_id = #{c.productionOrderRoutingOperationId}
            </if>
            <if test="c != null and c.status != null">
                and pot.status = #{c.status}
            </if>
            <if test="c != null and c.isProduction != null">
                and poro.is_production = #{c.isProduction}
            </if>
            <if test="c != null and c.workOrderNo != null and c.workOrderNo != ''">
                and pot.work_order_no like concat('%', #{c.workOrderNo}, '%')
            </if>
        </where>
        order by pot.id desc
    </sql>
    <select id="pageProductionOperationTask" resultType="com.ruoyi.production.bean.vo.ProductionOperationTaskVo">
        <include refid="groupedProductionOperationTaskSelect"/>
        <include refid="groupedProductionOperationTaskWhere"/>
        group by po.id
        order by po.id desc
    </select>
    <select id="listGroupedProductionOperationTask" resultType="com.ruoyi.production.bean.vo.ProductionOperationTaskVo">
        <include refid="groupedProductionOperationTaskSelect"/>
        <include refid="groupedProductionOperationTaskWhere"/>
        group by po.id, po.nps_no, po.is_end_order, p.product_name, pm.model, pm.unit
        order by po.id desc
    </select>
    <select id="selectTaskStatisticsByDate" resultType="com.ruoyi.home.dto.ProductionTaskStatisticsDto">