6 天以前 1fdbd0faf924697d735a7a1cb3499e5f0e572f22
docs(api): 更新采购多文件分析确认接口传参类型约束文档

- 添加了详细的接口传参类型约束文档,避免反序列化异常
- 定义了 purchase_ledger 业务类型的 JSON 结构和字段类型映射规则
- 说明了必填字段和后端默认行为,提供正确的传参示例
- 更新了审批状态建议值和前端高频错误示例

- 在 approve 流程服务中添加采购单无审核人自动通过逻辑
- 移除采购台账 DTO 中的白名单相关字段注释
- 优化采购台账服务中的 DTO 转 Entity 代码位置
已添加1个文件
已修改3个文件
187 ■■■■■ 文件已修改
doc/20260507_采购多文件分析确认接口传参类型约束.md 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260507_²É¹º¶àÎļþ·ÖÎöÈ·ÈϽӿڴ«²ÎÀàÐÍÔ¼Êø.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,166 @@
# é‡‡è´­å¤šæ–‡ä»¶åˆ†æžç¡®è®¤æŽ¥å£ä¼ å‚类型约束(`purchase_ledger`)
## 1. é€‚用接口
- `POST /purchase-ai/analyze-files/confirm`
- `businessType = purchase_ledger`
> æœ¬æ–‡ç”¨äºŽçº¦æŸå‰ç«¯æäº¤åˆ°ç¡®è®¤æŽ¥å£çš„ `payload` ç±»åž‹ï¼Œé¿å… `Cannot deserialize ...` è¿™ç±»ååºåˆ—化异常。
## 2. é¡¶å±‚请求体
```json
{
  "businessType": "purchase_ledger",
  "payload": {
    "purchaseLedgers": []
  }
}
```
## 3. payload ç»“æž„
推荐统一使用批量结构(即使只有 1 æ¡ï¼‰ï¼š
```json
{
  "purchaseLedgers": [
    {
      "purchaseContractNumber": "CG-2026-001",
      "supplierId": 10001,
      "entryDate": "2026-05-07",
      "type": 2,
      "approvalStatus": 1,
      "productData": [
        {
          "productCategory": "钢材",
          "specificationModel": "Q235-A",
          "unit": "吨",
          "quantity": 10,
          "taxInclusiveUnitPrice": 1200,
          "taxInclusiveTotalPrice": 12000,
          "taxRate": 13,
          "type": 2
        }
      ]
    }
  ]
}
```
后端也兼容“单条直传”(`payload` ç›´æŽ¥æ˜¯ä¸€æ¡ `PurchaseLedgerDto`),但不建议新前端继续使用。
## 4. Java ç±»åž‹åˆ° JSON ç±»åž‹æ˜ å°„规则
- `Long` / `Integer`:传 **number**(整数),不要传 `"pending"`、`"1级"` è¿™ç±»å­—符串。
- `BigDecimal`:传 **number**(可小数),不要传带逗号、单位的字符串(如 `"12,000元"`)。
- `Date`:传 **string**,格式固定 `yyyy-MM-dd`。
- `Boolean`:传 `true/false`,不要传 `"true"`、`"是"`。
- `List<T>`:传数组 `[]`。
## 5. `PurchaseLedgerDto` å­—段类型约束(确认接口可识别字段)
| å­—段 | Java ç±»åž‹ | JSON ç±»åž‹ | è¯´æ˜Ž |
| --- | --- | --- | --- |
| entryDateStart | String | string | æŸ¥è¯¢åŒºé—´å¼€å§‹æ—¥æœŸï¼ˆ`yyyy-MM-dd`) |
| entryDateEnd | String | string | æŸ¥è¯¢åŒºé—´ç»“束日期(`yyyy-MM-dd`) |
| id | Long | number(integer) | å°è´¦ID |
| purchaseContractNumber | String | string | **必填**(主业务单号) |
| supplierId | Long | number(integer) | `supplierId` ä¸Ž `supplierName` äºŒé€‰ä¸€å¿…å¡« |
| supplierName | String | string | `supplierId` ä¸Ž `supplierName` äºŒé€‰ä¸€å¿…å¡« |
| recorderId | Long | number(integer) | å½•入人ID |
| recorderName | String | string | å½•入人名称 |
| salesContractNo | String | string | é”€å”®åˆåŒå· |
| salesContractNoId | Long | number(integer) | é”€å”®åˆåŒID |
| projectName | String | string | é¡¹ç›®åç§° |
| entryDate | Date | string(`yyyy-MM-dd`) | å½•入日期;缺省时后端会补当天 |
| executionDate | Date | string(`yyyy-MM-dd`) | ç­¾è®¢æ—¥æœŸ |
| remarks | String | string | å¤‡æ³¨ |
| attachmentMaterials | String | string | é™„件说明/路径 |
| createdAt | Date | string(`yyyy-MM-dd`) | åˆ›å»ºæ—¥æœŸ |
| updatedAt | Date | string(`yyyy-MM-dd`) | æ›´æ–°æ—¥æœŸ |
| salesLedgerId | Long | number(integer) | é”€å”®å°è´¦ID |
| hasChildren | Boolean | boolean | æ˜¯å¦æœ‰å­é¡¹ |
| Type | Integer | number(integer) | åŽ†å²å­—æ®µï¼ˆä¸æŽ¨èæ–°å‰ç«¯ä½¿ç”¨ï¼‰ |
| productData | List<SalesLedgerProduct> | array | äº§å“æ˜Žç»†ï¼Œè§ä¸‹èŠ‚ |
| tempFileIds | List<String> | array[string] | ä¸´æ—¶æ–‡ä»¶ID列表 |
| SalesLedgerFiles | List<CommonFile> | array[object] | åŽ†å²å…¼å®¹å­—æ®µ |
| phoneNumber | String | string | ä¸šåŠ¡å‘˜æ‰‹æœºå· |
| businessPersonId | Long | number(integer) | ä¸šåŠ¡å‘˜ID |
| productId | Long | number(integer) | äº§å“ID |
| productModelId | Long | number(integer) | äº§å“è§„æ ¼ID |
| invoiceNumber | String | string | å‘票号 |
| invoiceAmount | BigDecimal | number | å‘票金额 |
| ticketRegistrationId | Long | number(integer) | æ¥ç¥¨ç™»è®°ID |
| contractAmount | BigDecimal | number | åˆåŒé‡‘额 |
| receiptPaymentAmount | BigDecimal | number | æ¥ç¥¨é‡‘额 |
| unReceiptPaymentAmount | BigDecimal | number | æœªæ¥ç¥¨é‡‘额 |
| type | Integer | number(integer) | å°è´¦ç±»åž‹ï¼Œé‡‡è´­å›ºå®š `2`(缺省时后端补 `2`) |
| paymentMethod | String | string | ä»˜æ¬¾æ–¹å¼ |
| approvalStatus | Integer | number(integer) | å®¡æ‰¹çŠ¶æ€ï¼Œ**严禁传字符串**(如 `"pending"`) |
| templateName | String | string | æ¨¡æ¿åç§° |
### å®¡æ‰¹çŠ¶æ€å»ºè®®å€¼ï¼ˆ`approvalStatus`)
建议按数字传值:
- `1`:待审核
- `2`:审核中
- `3`:审核通过
- `4`:审核拒绝/失败
- `5`:模板数据(历史定义)
## 6. `productData`(`SalesLedgerProduct`)建议字段及类型
| å­—段 | Java ç±»åž‹ | JSON ç±»åž‹ | è¯´æ˜Ž |
| --- | --- | --- | --- |
| productCategory | String | string | **必填**,产品大类/名称 |
| specificationModel | String | string | **必填**,规格型号 |
| unit | String | string | **必填**,单位 |
| quantity | BigDecimal | number | **必填**,数量 |
| taxRate | BigDecimal | number | ç¨ŽçŽ‡ï¼ˆå¦‚ `13`) |
| taxInclusiveUnitPrice | BigDecimal | number | **必填**,含税单价 |
| taxInclusiveTotalPrice | BigDecimal | number | **必填**,含税总价 |
| taxExclusiveTotalPrice | BigDecimal | number | ä¸å«ç¨Žæ€»ä»·ï¼ˆå¯ä¸ä¼ ï¼ŒåŽç«¯å¯æŽ¨å¯¼ï¼‰ |
| invoiceType | String | string | å‘票类型 |
| productId | Long | number(integer) | äº§å“ID |
| productModelId | Long | number(integer) | äº§å“åž‹å·ID |
| isChecked | Boolean | boolean | æ˜¯å¦è´¨æ£€ |
| type | Integer | number(integer) | é‡‡è´­äº§å“å›ºå®š `2`(建议传 `2`) |
## 7. å¿…填与后端默认行为
- å°è´¦ä¸»è¡¨å¿…填:`purchaseContractNumber`,以及 `supplierId` / `supplierName` äºŒé€‰ä¸€ã€‚
- äº§å“æ˜Žç»†è‹¥ä¼ äº† `productData`,则每条产品必填:`productCategory`、`specificationModel`、`unit`、`quantity`、`taxInclusiveUnitPrice`、`taxInclusiveTotalPrice`。
- `entryDate` ä¸ºç©ºæ—¶ï¼ŒåŽç«¯è¡¥å½“天日期。
- `type` ä¸ºç©ºæ—¶ï¼ŒåŽç«¯è¡¥ `2`。
## 8. å‰ç«¯é«˜é¢‘错误示例
错误(会触发反序列化异常):
```json
{
  "approvalStatus": "pending",
  "type": "采购",
  "supplierId": "供应商A"
}
```
正确:
```json
{
  "approvalStatus": 1,
  "type": 2,
  "supplierId": 10001
}
```
## 9. æäº¤å‰è‡ªæ£€æ¸…单
1. æ‰€æœ‰ `Long/Integer/BigDecimal` å­—段都为数字,不是业务词字符串。
2. æ‰€æœ‰æ—¥æœŸå­—段都是 `yyyy-MM-dd`。
3. `approvalStatus` ä»…传数字状态码。
4. `supplierId` ä¸Ž `supplierName` è‡³å°‘有一个有效值。
5. `productData` ä¸­å¿…填列齐全且为正确类型。
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -80,11 +80,9 @@
                .map(ApproveProcessConfigNodeVo::getApproverId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (list.isEmpty()) {
            throw new RuntimeException("流程不存在");
        }
        // æ— å®¡æ ¸äººé€»è¾‘添加
        if (CollectionUtils.isEmpty(nodeIds)) {
            autoPassPurchaseApproveIfNoApprover(approveProcessVO);
            autoPassPurchaseApproveIfNoApprover(approveProcessVO); // é‡‡è´­å•无审核人逻辑
            return;
        }
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(nodeIds);
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
@@ -48,11 +48,11 @@
     */
    @Excel(name = "供应商名称")
    private String supplierName;
     /**
     * æ˜¯å¦ç™½åå•
     */
    @Excel(name = "是否白名单")
    private Integer isWhite;
//     /**
//     * æ˜¯å¦ç™½åå•
//     */
//    @Excel(name = "是否白名单")
//    private Integer isWhite;
    /**
     * å½•入人姓名id
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -132,6 +132,8 @@
    @Transactional(rollbackFor = Exception.class)
    public int addOrEditPurchase(PurchaseLedgerDto purchaseLedgerDto) throws Exception {
        PurchaseLedger purchaseLedger = new PurchaseLedger();
        // DTO转Entity
        BeanUtils.copyProperties(purchaseLedgerDto, purchaseLedger);
        SalesLedger salesLedger = salesLedgerMapper.selectById(purchaseLedgerDto.getSalesLedgerId());
        //录入人
        SysUser sysUser = userMapper.selectUserById(purchaseLedgerDto.getRecorderId());
@@ -146,9 +148,6 @@
        SupplierManage supplierManage = supplierManageMapper.selectById(purchaseLedgerDto.getSupplierId());
        // DTO转Entity
        BeanUtils.copyProperties(purchaseLedgerDto, purchaseLedger);
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (ObjectUtils.isNotEmpty(loginUser) && null != loginUser.getTenantId()) {
            purchaseLedger.setTenantId(loginUser.getTenantId());