Browse Source

2025/12/27

25057 3 months ago
parent
commit
f68025092b
100 changed files with 7351 additions and 2355 deletions
  1. 29 0
      README.md
  2. 772 0
      docs/esign-contract-template-usage.md
  3. 739 0
      docs/esign-integration-guide.md
  4. 418 0
      docs/esign-oauth2-handwritten-sign.md
  5. 72 0
      docs/esign-sdk-install.md
  6. 67 0
      docs/wechat-subscribe-flow.md
  7. 353 0
      docs/前端分块上传实现说明.md
  8. BIN
      lib/paas-sdk-3.1.4.jar
  9. BIN
      ngrok.exe
  10. 230 0
      ngrok诊断指南.md
  11. 312 0
      ngrok问题排查指南.md
  12. 198 138
      pom.xml
  13. 10 0
      src/main/java/com/loan/system/LoanSystemApplication.java
  14. 7 0
      src/main/java/com/loan/system/config/FileUploadConfig.java
  15. 44 0
      src/main/java/com/loan/system/config/OssConfig.java
  16. 6 3
      src/main/java/com/loan/system/config/WebMvcConfiguration.java
  17. 20 0
      src/main/java/com/loan/system/constant/ContractConstant.java
  18. 6 0
      src/main/java/com/loan/system/constant/ContractTemplateIdConstant.java
  19. 0 1
      src/main/java/com/loan/system/constant/JwtClaimsConstant.java
  20. 2 20
      src/main/java/com/loan/system/constant/MessageConstant.java
  21. 38 33
      src/main/java/com/loan/system/controller/admin/AdminController.java
  22. 6 6
      src/main/java/com/loan/system/controller/admin/DetailsController.java
  23. 14 0
      src/main/java/com/loan/system/controller/admin/MaterialsController.java
  24. 229 252
      src/main/java/com/loan/system/controller/wechat/ApprovalController.java
  25. 109 209
      src/main/java/com/loan/system/controller/wechat/CollateralController.java
  26. 25 64
      src/main/java/com/loan/system/controller/wechat/ContractController.java
  27. 2 1
      src/main/java/com/loan/system/controller/wechat/CustomerController.java
  28. 87 390
      src/main/java/com/loan/system/controller/wechat/DisbursementController.java
  29. 413 0
      src/main/java/com/loan/system/controller/wechat/EsignController.java
  30. 495 0
      src/main/java/com/loan/system/controller/wechat/EsignTestController.java
  31. 107 234
      src/main/java/com/loan/system/controller/wechat/LoanController.java
  32. 0 345
      src/main/java/com/loan/system/controller/wechat/LoanController_copy.java
  33. 166 0
      src/main/java/com/loan/system/controller/wechat/MessageSendController.java
  34. 353 0
      src/main/java/com/loan/system/controller/wechat/OssFileController.java
  35. 117 394
      src/main/java/com/loan/system/controller/wechat/RepaymentController.java
  36. 261 14
      src/main/java/com/loan/system/controller/wechat/StatisticsController.java
  37. 2 2
      src/main/java/com/loan/system/controller/wechat/StepController.java
  38. 2 5
      src/main/java/com/loan/system/controller/wechat/UploadController.java
  39. 31 19
      src/main/java/com/loan/system/controller/wechat/UserController.java
  40. 33 0
      src/main/java/com/loan/system/controller/wechat/test.java
  41. 2 4
      src/main/java/com/loan/system/domain/dto/ApprovalRecordDTO.java
  42. 21 0
      src/main/java/com/loan/system/domain/dto/ChannelPushDTO.java
  43. 28 0
      src/main/java/com/loan/system/domain/dto/ClearDetailDTO.java
  44. 8 1
      src/main/java/com/loan/system/domain/dto/CollateralDTO.java
  45. 1 0
      src/main/java/com/loan/system/domain/dto/CollateralPlanApprovalDTO.java
  46. 3 1
      src/main/java/com/loan/system/domain/dto/CollateralPlanDTO.java
  47. 39 0
      src/main/java/com/loan/system/domain/dto/CompleteMultipartUploadDTO.java
  48. 5 0
      src/main/java/com/loan/system/domain/dto/ContractDTO.java
  49. 16 0
      src/main/java/com/loan/system/domain/dto/ContractWrapperDTO.java
  50. 4 0
      src/main/java/com/loan/system/domain/dto/CustomerDTO.java
  51. 19 0
      src/main/java/com/loan/system/domain/dto/DisburseBankInfoDTO.java
  52. 4 7
      src/main/java/com/loan/system/domain/dto/DisbursementDTO.java
  53. 11 4
      src/main/java/com/loan/system/domain/dto/DisbursementRecordDTO.java
  54. 1 2
      src/main/java/com/loan/system/domain/dto/DisbursementStartDTO.java
  55. 76 0
      src/main/java/com/loan/system/domain/dto/EsignCreateFlowDTO.java
  56. 43 0
      src/main/java/com/loan/system/domain/dto/EsignFillTemplateDTO.java
  57. 68 0
      src/main/java/com/loan/system/domain/dto/EsignTemplateFillDTO.java
  58. 5 5
      src/main/java/com/loan/system/domain/dto/LoanCaseDTO.java
  59. 23 0
      src/main/java/com/loan/system/domain/dto/LocationDatumDTO.java
  60. 25 0
      src/main/java/com/loan/system/domain/dto/RepayBankInfoDTO.java
  61. 21 6
      src/main/java/com/loan/system/domain/dto/RepaymentRecordDTO.java
  62. 64 0
      src/main/java/com/loan/system/domain/dto/TemplateMessageSendDTO.java
  63. 3 3
      src/main/java/com/loan/system/domain/entity/ApprovalRecord.java
  64. 53 0
      src/main/java/com/loan/system/domain/entity/ChannelPush.java
  65. 57 0
      src/main/java/com/loan/system/domain/entity/ClearDetail.java
  66. 26 4
      src/main/java/com/loan/system/domain/entity/Collateral.java
  67. 21 3
      src/main/java/com/loan/system/domain/entity/CollateralPlan.java
  68. 22 4
      src/main/java/com/loan/system/domain/entity/Contract.java
  69. 50 0
      src/main/java/com/loan/system/domain/entity/ContractDisbursement.java
  70. 3 0
      src/main/java/com/loan/system/domain/entity/ContractRepayment.java
  71. 11 2
      src/main/java/com/loan/system/domain/entity/Customer.java
  72. 50 0
      src/main/java/com/loan/system/domain/entity/DisburseBankInfo.java
  73. 12 27
      src/main/java/com/loan/system/domain/entity/Disbursement.java
  74. 22 13
      src/main/java/com/loan/system/domain/entity/DisbursementRecord.java
  75. 153 0
      src/main/java/com/loan/system/domain/entity/EsignFlow.java
  76. 9 3
      src/main/java/com/loan/system/domain/entity/LoanCase.java
  77. 46 0
      src/main/java/com/loan/system/domain/entity/LocationDatum.java
  78. 3 0
      src/main/java/com/loan/system/domain/entity/PawnTicketInfo.java
  79. 50 0
      src/main/java/com/loan/system/domain/entity/RepayBankInfo.java
  80. 49 14
      src/main/java/com/loan/system/domain/entity/RepaymentRecord.java
  81. 2 2
      src/main/java/com/loan/system/domain/entity/Step.java
  82. 33 0
      src/main/java/com/loan/system/domain/entity/UserRecommender.java
  83. 19 12
      src/main/java/com/loan/system/domain/enums/ContractEnum.java
  84. 9 0
      src/main/java/com/loan/system/domain/enums/DecisionEnum.java
  85. 13 13
      src/main/java/com/loan/system/domain/enums/ExceptionEnum.java
  86. 2 1
      src/main/java/com/loan/system/domain/enums/RoleEnum.java
  87. 196 50
      src/main/java/com/loan/system/domain/enums/StepPropertyEnum.java
  88. 14 11
      src/main/java/com/loan/system/domain/pojo/ContractInformation.java
  89. 9 0
      src/main/java/com/loan/system/domain/pojo/DateAndAmountPOJO.java
  90. 1 2
      src/main/java/com/loan/system/domain/vo/ApprovalRecordVO.java
  91. 23 0
      src/main/java/com/loan/system/domain/vo/ChannelPushVO.java
  92. 25 0
      src/main/java/com/loan/system/domain/vo/ClearDetailVO.java
  93. 7 1
      src/main/java/com/loan/system/domain/vo/CollateralPlanVO.java
  94. 11 1
      src/main/java/com/loan/system/domain/vo/CollateralVO.java
  95. 17 0
      src/main/java/com/loan/system/domain/vo/ComplementVO.java
  96. 9 2
      src/main/java/com/loan/system/domain/vo/ContractVO.java
  97. 4 0
      src/main/java/com/loan/system/domain/vo/CustomerVO.java
  98. 20 0
      src/main/java/com/loan/system/domain/vo/DisburseBankInfoVO.java
  99. 14 20
      src/main/java/com/loan/system/domain/vo/DisbursementDetailVO.java
  100. 21 7
      src/main/java/com/loan/system/domain/vo/DisbursementRecordVO.java

+ 29 - 0
README.md

@@ -1,2 +1,31 @@
 # loan_system
 
+## OSS版本控制功能说明
+
+本系统现已支持阿里云OSS版本控制功能,可以防止文件意外删除或覆盖,提高数据安全性。
+
+### 功能特性
+
+1. **启用版本控制** - 保护存储桶中的文件不被意外删除或覆盖
+2. **禁用版本控制** - 在不需要版本控制时可以禁用以节省成本
+3. **查看版本控制状态** - 随时检查当前存储桶的版本控制状态
+
+### 如何使用
+
+通过以下REST API接口管理OSS版本控制:
+
+- `POST /wechat/ossfile/versioning/enable` - 启用版本控制
+- `POST /wechat/ossfile/versioning/disable` - 禁用版本控制
+- `GET /wechat/ossfile/versioning/status` - 获取版本控制状态
+
+### 版本控制的好处
+
+1. **防止意外删除** - 即使文件被删除,也可以恢复到之前的版本
+2. **防止意外覆盖** - 文件被覆盖时,旧版本仍可访问
+3. **审计跟踪** - 可以追踪文件的历史变更记录
+
+### 注意事项
+
+1. 启用版本控制会增加存储成本,因为每个文件的每个版本都会占用存储空间
+2. 建议定期清理不需要的旧版本文件以控制成本
+3. 删除启用了版本控制的文件实际上只是添加了一个删除标记,文件本身仍然存在

+ 772 - 0
docs/esign-contract-template-usage.md

@@ -0,0 +1,772 @@
+# e签宝合同模板填充使用说明
+
+## 功能概述
+
+实现了两种合同模板填充方式:
+1. **本地Word模板填充**:使用poi-tl填充本地Word模板,转换为PDF后上传到e签宝
+2. **e签宝工作台模板填充**:直接使用e签宝工作台中已有的模板,通过API填充字段并生成文件(推荐)
+
+## 主要功能
+
+### 方式一:本地Word模板(适用于开发和测试)
+
+1. **Word模板填充**:使用poi-tl库填充Word模板中的变量
+2. **PDF转换**:将填充后的Word文档转换为PDF格式(使用LibreOffice)
+3. **文件上传**:将PDF文件上传到e签宝平台,获得fileId
+4. **签署流程创建**:基于上传的文件创建e签宝签署流程
+
+### 方式二:e签宝工作台模板(推荐用于生产环境)
+
+1. **获取模板列表**:从e签宝工作台获取已创建的模板列表
+2. **获取模板详情**:查看模板的详细信息和控件配置
+3. **模板字段填充**:使用e签宝API直接填充模板中的控件字段
+4. **生成文件**:自动生成填充后的PDF文件,获得fileId
+5. **签署流程创建**:基于生成的文件创建e签宝签署流程
+
+## 核心组件
+
+### 1. EsignContractUtil(工具类)
+
+提供以下静态方法:
+
+- `fillTemplateAndUpload()`: 填充模板、转换PDF、上传到e签宝(一步完成)
+- `fillWordTemplate()`: 仅填充Word模板,返回字节数组
+- `fillTemplateAndConvertToPdf()`: 填充模板并转换为PDF
+- `fillTemplateAndSavePdf()`: 填充模板并保存为PDF文件
+
+### 2. EsignService(服务接口)
+
+- `uploadDocument()`: 上传文件到e签宝,返回fileId
+- `createSignFlow()`: 创建签署流程
+
+### 3. API接口
+
+#### 3.1 完整流程接口(推荐)
+
+**接口地址**: `POST /api/wechat/esign/flow/createFromTemplate`
+
+**功能**: 填充模板、上传文件、创建签署流程,一步完成
+
+**请求体示例**:
+```json
+{
+  "contractId": 123,
+  "caseId": 456,
+  "templatePath": "template1.docx",
+  "contractName": "浙江宝路同典当抵押合同",
+  "contract": {
+    "contractNo": "HT20250101001",
+    "name": "张三",
+    "idNumber": "330123199001011234",
+    "mobile": "13800138000",
+    "contractAmount": 100000.0,
+    "contractPeriod": 30,
+    "amountRate": 1.5,
+    "serviceCost": 2.0,
+    "loanRate": 3.0,
+    "bankName": "中国工商银行",
+    "bankAccount": "6222021234567890123",
+    "address": "杭州市西湖区文三路123号",
+    "collateralNumber": "101",
+    "collateralArea": "100平方米",
+    "contractType": "抵押合同",
+    "contractAttr": "不动产抵押",
+    "leastDay": 30,
+    "contactAddress1": "杭州市西湖区",
+    "contactAddress2": "杭州市西湖区",
+    "userMobile": "13900139000",
+    "signLocation1": "杭州",
+    "signLocation2": "杭州"
+  },
+  "customerName": "张三",
+  "customerMobile": "13800138000",
+  "customerIdNumber": "330123199001011234",
+  "businessName": "浙江宝路同典当有限公司",
+  "businessMobile": "13900139000",
+  "businessIdNumber": "",
+  "signDeadline": 1735632000000,
+  "remark": "测试合同"
+}
+```
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "message": "模板填充并创建签署流程成功",
+  "data": "flowId123456789"
+}
+```
+
+#### 3.2 仅上传接口
+
+**接口地址**: `POST /api/wechat/esign/template/upload`
+
+**功能**: 仅填充模板并上传到e签宝,不创建签署流程
+
+**请求体**: 与完整流程接口相同
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "message": "模板填充并上传成功",
+  "data": "fileId123456789"
+}
+```
+
+#### 3.3 创建签署流程接口
+
+**接口地址**: `POST /api/wechat/esign/flow/create`
+
+**功能**: 使用已上传的文件ID创建签署流程
+
+**请求体示例**:
+```json
+{
+  "contractId": 123,
+  "caseId": 456,
+  "fileId": "fileId123456789",
+  "documentName": "浙江宝路同典当抵押合同.pdf",
+  "customerName": "张三",
+  "customerMobile": "13800138000",
+  "customerIdNumber": "330123199001011234",
+  "businessName": "浙江宝路同典当有限公司",
+  "businessMobile": "13900139000",
+  "signDeadline": 1735632000000
+}
+```
+
+## Word模板制作
+
+### 支持的变量类型
+
+1. **文本变量**: 使用 `{{variableName}}` 格式
+2. **图片变量**: 使用 `{{@pictureVariable}}` 格式(用于签名图片)
+
+### 模板变量列表
+
+根据 `ContractInformation` 类,支持的变量包括:
+
+| 变量名 | 说明 | 示例值 |
+|--------|------|--------|
+| contractNo | 合同编号 | HT20250101001 |
+| customerName | 客户姓名 | 张三 |
+| customerIdNumber | 客户身份证号 | 330123199001011234 |
+| customerMobile | 客户手机号 | 13800138000 |
+| contractType | 业务类型 | 抵押合同 |
+| contractAttr | 业务属性 | 不动产抵押 |
+| contractAmount | 合同金额 | 100000.0 |
+| contractPeriod | 合同期限(天) | 30 |
+| interestRate | 当金利率 | 1.5% |
+| serviceCost | 综合服务费 | 2.0% |
+| loanRate | 借款利率 | 3.0% |
+| bankName | 客户银行 | 中国工商银行 |
+| bankAccount | 客户银行账号 | 6222021234567890123 |
+| collateralAddress | 押品地址 | 杭州市西湖区文三路123号 |
+| numberAndArea | 押品编号和面积 | 101 100平方米 |
+| contactAddress1 | 客户联系地址 | 杭州市西湖区 |
+| contactAddress2 | 业务方联系地址 | 杭州市西湖区 |
+| userMobile | 业务方手机号 | 13900139000 |
+| signLocation1 | 客户签署地点 | 杭州 |
+| signLocation2 | 业务方签署地点 | 杭州 |
+| signature1 | 客户签名图片 | (图片) |
+| signature2 | 业务方签名图片 | (图片) |
+| startDateTime | 借款日期 | 2025-01-01 |
+| endDateTime | 还款日期 | 2025-01-31 |
+
+### 模板示例
+
+在Word模板中使用以下格式:
+
+```
+合同编号:{{contractNo}}
+客户姓名:{{customerName}}
+身份证号:{{customerIdNumber}}
+合同金额:{{contractAmount}}元
+合同期限:{{contractPeriod}}天
+当金利率:{{interestRate}}
+综合服务费:{{serviceCost}}
+借款利率:{{loanRate}}
+押品地址:{{collateralAddress}}
+押品信息:{{numberAndArea}}
+客户签名:{{@signature1}}
+业务方签名:{{@signature2}}
+```
+
+## 配置说明
+
+### application-dev.yaml / application-prod.yaml
+
+```yaml
+system:
+  esign:
+    app-id: your-app-id
+    app-secret: your-app-secret
+    api-url: https://smlopenapi.esign.cn
+    project-id: your-project-id
+    enabled: true
+
+upload:
+  templatePath: /app/upload/template/  # 模板文件路径
+
+libreoffice:
+  remote-url: http://libreoffice-server:8080  # 或本地模式
+  office-home: ""  # 留空使用远程模式
+```
+
+## 使用示例
+
+### Java代码示例
+
+```java
+@Autowired
+private EsignService esignService;
+
+// 方式1: 使用工具类(推荐)
+ContractInformation contract = new ContractInformation();
+contract.setContractNo("HT20250101001");
+contract.setName("张三");
+// ... 设置其他字段
+
+String templatePath = "/app/upload/template/template1.docx";
+String fileId = EsignContractUtil.fillTemplateAndUpload(
+    templatePath, 
+    contract, 
+    "浙江宝路同典当抵押合同",
+    esignService
+);
+
+// 方式2: 分步执行
+// 1. 填充模板
+byte[] docxBytes = EsignContractUtil.fillWordTemplate(templatePath, contract);
+
+// 2. 转换为PDF
+byte[] pdfBytes = LibreOfficePdfUtil.convertDocxToPdf(docxBytes);
+
+// 3. 上传到e签宝
+String fileId = esignService.uploadDocument(pdfBytes, "合同.pdf", "application/pdf");
+
+// 4. 创建签署流程
+EsignCreateFlowDTO flowDTO = new EsignCreateFlowDTO();
+flowDTO.setFileId(fileId);
+flowDTO.setDocumentName("合同.pdf");
+// ... 设置其他字段
+String flowId = esignService.createSignFlow(flowDTO);
+```
+
+## 注意事项
+
+1. **模板路径**: 
+   - 如果使用相对路径(如 `template1.docx`),会自动拼接配置的 `upload.templatePath`
+   - 如果使用绝对路径,直接使用该路径
+
+2. **PDF转换**: 
+   - 需要确保LibreOffice服务正常运行
+   - 开发环境可以使用本地LibreOffice
+   - 生产环境建议使用远程LibreOffice服务
+
+3. **文件上传**: 
+   - e签宝上传分为三步:获取上传URL -> 上传到OSS -> 通知上传完成
+   - 上传成功后返回fileId,后续需要使用fileId创建签署流程
+
+4. **错误处理**: 
+   - 所有步骤都有异常处理和日志记录
+   - 如果某个步骤失败,会返回详细的错误信息
+
+5. **签名图片**: 
+   - 签名图片路径应该是文件的绝对路径或相对路径
+   - 如果图片加载失败,会记录警告日志但不会中断流程
+
+## 常见问题
+
+### Q1: 模板填充后格式错乱?
+A: 检查Word模板中的变量格式是否正确,确保使用 `{{variableName}}` 格式。
+
+### Q2: PDF转换失败?
+A: 检查LibreOffice服务是否正常运行,查看日志中的错误信息。
+
+### Q3: 上传到e签宝失败?
+A: 检查e签宝配置是否正确(app-id、app-secret),确保网络连接正常。
+
+### Q4: 如何自定义模板变量?
+A: 修改 `EsignContractUtil.fillWordTemplate()` 方法中的变量映射部分。
+
+---
+
+## 方式二:使用e签宝工作台模板(推荐)
+
+### 准备工作
+
+1. **在e签宝工作台创建模板**:
+   - 登录e签宝工作台(https://smlopen.esign.cn)
+   - 进入"合同模板"管理页面
+   - 点击"新建模板",上传您的合同文件(支持Word或PDF格式)
+   - 在模板编辑器中,添加需要填充的控件:
+     - 文本控件:用于填充文本字段(如合同编号、客户姓名等)
+     - 为每个控件设置唯一的**控件ID(key)**,这个ID将用于API填充
+   - 保存模板,记录模板ID(docTemplateId)
+
+2. **了解模板控件ID**:
+   - 在模板详情中可以查看所有控件的ID
+   - 也可以通过API获取模板详情查看控件配置
+
+### API接口
+
+#### 1. 获取模板列表
+
+**接口地址**: `GET /api/wechat/esign/template/list`
+
+**请求参数**:
+- `pageNum` (可选): 页码,默认1
+- `pageSize` (可选): 每页大小,默认20
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "message": "获取模板列表成功",
+  "data": {
+    "list": [
+      {
+        "docTemplateId": "template123456",
+        "docTemplateName": "浙江宝路同典当抵押合同",
+        "docTemplateType": "DOCX",
+        "createTime": "2025-01-01 10:00:00"
+      }
+    ],
+    "total": 10,
+    "pageNum": 1,
+    "pageSize": 20
+  }
+}
+```
+
+#### 2. 获取模板详情
+
+**接口地址**: `GET /api/wechat/esign/template/{templateId}`
+
+**功能**: 获取模板的详细信息,包括所有控件的配置
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "message": "获取模板详情成功",
+  "data": {
+    "docTemplateId": "template123456",
+    "docTemplateName": "浙江宝路同典当抵押合同",
+    "structComponents": [
+      {
+        "key": "contractNo",
+        "componentType": "TEXT",
+        "componentName": "合同编号"
+      },
+      {
+        "key": "customerName",
+        "componentType": "TEXT",
+        "componentName": "客户姓名"
+      }
+    ]
+  }
+}
+```
+
+#### 3. 完整流程:填充模板并创建签署流程(推荐)
+
+**接口地址**: `POST /api/wechat/esign/flow/createFromEsignTemplate`
+
+**功能**: 一步完成模板填充、文件生成、签署流程创建
+
+**请求体示例**:
+```json
+{
+  "contractId": 123,
+  "caseId": 456,
+  "templateId": "template123456",
+  "contractName": "浙江宝路同典当抵押合同",
+  "formFields": {
+    "contractNo": "HT20250101001",
+    "customerName": "张三",
+    "customerIdNumber": "330123199001011234",
+    "customerMobile": "13800138000",
+    "contractAmount": "100000",
+    "contractPeriod": "30",
+    "interestRate": "1.5",
+    "bankName": "中国工商银行",
+    "bankAccount": "6222021234567890123"
+  },
+  "customerName": "张三",
+  "customerMobile": "13800138000",
+  "customerIdNumber": "330123199001011234",
+  "businessName": "浙江宝路同典当有限公司",
+  "businessMobile": "13900139000",
+  "signDeadline": 1735632000000
+}
+```
+
+**请求体说明**:
+- `templateId`: e签宝工作台中的模板ID(必填)
+- `formFields`: 字段映射(key为模板控件ID,value为填充值)
+  - 如果`formFields`为空,可以传入`contract`对象,系统会自动映射常见字段
+- `customerName`, `customerMobile`等: 用于创建签署流程的签署人信息
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "message": "使用e签宝模板创建签署流程成功",
+  "data": "flowId123456789"
+}
+```
+
+#### 4. 仅填充模板生成文件
+
+**接口地址**: `POST /api/wechat/esign/template/fill`
+
+**功能**: 仅填充模板并生成文件,不创建签署流程
+
+**请求体**: 与完整流程接口相同(但不创建流程)
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "message": "模板填充并生成文件成功",
+  "data": "fileId123456789"
+}
+```
+
+### 自动字段映射
+
+如果您不想手动构建`formFields`,可以传入`contract`对象,系统会自动将常见字段映射到e签宝模板控件。
+
+**支持的自动映射字段**:
+
+| ContractInformation字段 | 映射到的模板控件ID |
+|------------------------|-------------------|
+| contractNo | contractNo, 合同编号 |
+| name | customerName, 客户姓名, name |
+| idNumber | customerIdNumber, 客户身份证号, idNumber |
+| mobile | customerMobile, 客户手机号, mobile |
+| contractAmount | contractAmount, 合同金额, 金额 |
+| contractPeriod | contractPeriod, 合同期限, 期限 |
+| amountRate | interestRate, 当金利率 |
+| serviceCost | serviceCost, 综合服务费 |
+| loanRate | loanRate, 借款利率 |
+| bankName | bankName, 银行名称 |
+| bankAccount | bankAccount, 银行账号 |
+| address | collateralAddress, 押品地址, address |
+| contractType | contractType, 业务类型 |
+| contractAttr | contractAttr, 业务属性 |
+| startDateTime | startDateTime, 借款日期 |
+| endDateTime | endDateTime, 还款日期 |
+
+**使用示例**:
+```json
+{
+  "templateId": "template123456",
+  "contractName": "浙江宝路同典当抵押合同",
+  "contract": {
+    "contractNo": "HT20250101001",
+    "name": "张三",
+    "idNumber": "330123199001011234",
+    "mobile": "13800138000",
+    "contractAmount": 100000.0,
+    "contractPeriod": 30
+  },
+  "customerName": "张三",
+  "customerMobile": "13800138000",
+  "businessName": "浙江宝路同典当有限公司",
+  "businessMobile": "13900139000"
+}
+```
+
+### Java代码示例
+
+```java
+@Autowired
+private EsignService esignService;
+
+// 方式1: 使用手动字段映射
+Map<String, String> formFields = new HashMap<>();
+formFields.put("contractNo", "HT20250101001");
+formFields.put("customerName", "张三");
+formFields.put("contractAmount", "100000");
+
+String fileId = esignService.fillTemplateAndGenerateFile(
+    "template123456",
+    "合同.pdf",
+    formFields
+);
+
+// 方式2: 使用自动字段映射
+ContractInformation contract = new ContractInformation();
+contract.setContractNo("HT20250101001");
+contract.setName("张三");
+// ... 设置其他字段
+
+Map<String, String> formFields = EsignContractUtil.convertContractToTemplateFields(contract);
+String fileId = esignService.fillTemplateAndGenerateFile(
+    "template123456",
+    "合同.pdf",
+    formFields
+);
+```
+
+### e签宝模板控件命名建议
+
+为了使用自动字段映射功能,建议在e签宝工作台中创建模板时,使用以下控件ID:
+
+**合同基本信息**:
+- `contractNo` 或 `合同编号`
+- `contractAmount` 或 `合同金额`
+- `contractPeriod` 或 `合同期限`
+
+**客户信息**:
+- `customerName` 或 `客户姓名` 或 `name`
+- `customerIdNumber` 或 `客户身份证号` 或 `idNumber`
+- `customerMobile` 或 `客户手机号` 或 `mobile`
+
+**利率信息**:
+- `interestRate` 或 `当金利率`
+- `serviceCost` 或 `综合服务费`
+- `loanRate` 或 `借款利率`
+
+**银行信息**:
+- `bankName` 或 `银行名称`
+- `bankAccount` 或 `银行账号`
+
+**押品信息**:
+- `collateralAddress` 或 `押品地址` 或 `address`
+- `numberAndArea` 或 `押品信息`
+
+**日期信息**:
+- `startDateTime` 或 `借款日期`
+- `endDateTime` 或 `还款日期`
+
+### 两种方式对比
+
+| 特性 | 本地Word模板 | e签宝工作台模板 |
+|------|------------|---------------|
+| 模板管理 | 需要自行管理模板文件 | e签宝工作台统一管理 |
+| 模板编辑 | 需要使用Word编辑器 | e签宝可视化编辑器 |
+| 字段填充 | 使用变量名 `{{variable}}` | 使用控件ID(key) |
+| PDF转换 | 需要LibreOffice服务 | e签宝自动转换 |
+| 适用场景 | 开发和测试 | 生产环境(推荐) |
+| 模板更新 | 需要重新上传文件 | 在e签宝工作台直接更新 |
+
+### 使用e签宝模板的注意事项
+
+1. **模板ID获取**:
+   - 模板ID可以从e签宝工作台查看,也可以通过API获取模板列表
+   - 模板ID是固定的,不会因为模板更新而改变
+
+2. **控件ID(key)**:
+   - 控件ID是在e签宝工作台创建模板时设置的
+   - 控件ID必须唯一,且一旦设置不建议修改
+   - 如果修改了控件ID,需要同步更新API调用中的字段映射
+
+3. **字段映射**:
+   - `formFields`中的key必须与模板中的控件ID完全一致
+   - 如果控件ID使用中文,key也必须使用中文
+   - 建议使用英文控件ID,便于维护
+
+4. **自动映射**:
+   - 自动映射功能基于常见的字段名规则
+   - 如果模板控件ID不符合命名规则,需要手动构建`formFields`
+   - 可以通过获取模板详情API查看实际的控件ID
+
+5. **模板更新**:
+   - 在e签宝工作台更新模板后,不需要修改代码
+   - 只要控件ID不变,API调用就可以正常工作
+
+## 完整业务流程说明
+
+### 方式一:使用e签宝工作台模板(推荐)
+
+#### 准备工作(一次性)
+
+1. **在e签宝工作台创建模板**:
+   - 登录e签宝工作台,上传合同文件
+   - 在模板编辑器中添加文本控件
+   - 为每个控件设置控件ID(如 `contractNo`、`customerName`)
+   - **记录模板ID(docTemplateId)**,例如:`template123456`
+
+#### 业务流程(每次签署合同)
+
+**步骤1:使用模板ID填充字段并创建签署流程**
+
+```http
+POST /api/wechat/esign/flow/createFromEsignTemplate
+```
+
+这个接口会**一步完成**以下操作:
+- ✅ 根据 `templateId` 填充模板字段(使用 `formFields` 或 `contract`)
+- ✅ 生成填充后的PDF文件
+- ✅ 创建签署流程
+- ✅ 返回 `flowId`
+
+**请求示例**:
+```json
+{
+  "contractId": 123,
+  "caseId": 456,
+  "templateId": "template123456",
+  "contractName": "浙江宝路同典当抵押合同",
+  "formFields": {
+    "contractNo": "HT20250101001",
+    "customerName": "张三",
+    "customerMobile": "13800138000"
+  },
+  "customerName": "张三",
+  "customerMobile": "13800138000",
+  "businessName": "浙江宝路同典当有限公司",
+  "businessMobile": "13900139000"
+}
+```
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "message": "使用e签宝模板创建签署流程成功",
+  "data": "flowId123456789"
+}
+```
+
+**步骤2:获取签署链接**
+
+获取客户签署链接:
+```http
+GET /api/wechat/esign/flow/{flowId}/customer/sign-url?mobile=13800138000&name=张三
+```
+
+获取业务方签署链接:
+```http
+GET /api/wechat/esign/flow/{flowId}/business/sign-url?mobile=13900139000&name=公司名称
+```
+
+**步骤3:启动签署流程**
+
+```http
+POST /api/wechat/esign/flow/{flowId}/start
+```
+
+启动后,签署人才能通过签署链接进行签署。
+
+**步骤4:签署和查询**
+
+- 客户和业务方通过签署链接完成签署
+- 查询签署状态:`GET /api/wechat/esign/flow/{flowId}/status`
+- 下载已签署文档:`GET /api/wechat/esign/flow/{flowId}/download`
+
+---
+
+### 方式二:分步执行(如果需要更精细的控制)
+
+如果不想一步完成,可以分步执行:
+
+**步骤1:填充模板生成文件**
+```http
+POST /api/wechat/esign/template/fill
+```
+返回 `fileId`
+
+**步骤2:使用fileId创建签署流程**
+```http
+POST /api/wechat/esign/flow/create
+```
+传入 `fileId`,返回 `flowId`
+
+**步骤3-5**:同方式一的步骤2-4
+
+---
+
+### 完整流程图
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│  准备工作(一次性)                                          │
+│  ┌───────────────────────────────────────────────────────┐  │
+│  │ 1. 在e签宝工作台创建模板                               │  │
+│  │ 2. 设置控件ID(如:contractNo, customerName)         │  │
+│  │ 3. 记录模板ID(templateId)                           │  │
+│  └───────────────────────────────────────────────────────┘  │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  业务流程(每次签署)                                        │
+│  ┌───────────────────────────────────────────────────────┐  │
+│  │ 步骤1: 填充模板并创建签署流程                         │  │
+│  │ POST /api/wechat/esign/flow/createFromEsignTemplate  │  │
+│  │ 输入: templateId + formFields/contract               │  │
+│  │ 输出: flowId                                         │  │
+│  └───────────────────────────────────────────────────────┘  │
+│                            ↓                                 │
+│  ┌───────────────────────────────────────────────────────┐  │
+│  │ 步骤2: 获取签署链接                                   │  │
+│  │ GET /api/wechat/esign/flow/{flowId}/customer/sign-url│  │
+│  │ GET /api/wechat/esign/flow/{flowId}/business/sign-url│  │
+│  └───────────────────────────────────────────────────────┘  │
+│                            ↓                                 │
+│  ┌───────────────────────────────────────────────────────┐  │
+│  │ 步骤3: 启动签署流程                                   │  │
+│  │ POST /api/wechat/esign/flow/{flowId}/start           │  │
+│  └───────────────────────────────────────────────────────┘  │
+│                            ↓                                 │
+│  ┌───────────────────────────────────────────────────────┐  │
+│  │ 步骤4: 客户和业务方签署                               │  │
+│  │ 通过签署链接完成签署                                  │  │
+│  └───────────────────────────────────────────────────────┘  │
+│                            ↓                                 │
+│  ┌───────────────────────────────────────────────────────┐  │
+│  │ 步骤5: 查询状态和下载                                 │  │
+│  │ GET /api/wechat/esign/flow/{flowId}/status           │  │
+│  │ GET /api/wechat/esign/flow/{flowId}/download         │  │
+│  └───────────────────────────────────────────────────────┘  │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 关键概念说明
+
+1. **templateId(模板ID)**:
+   - 这是e签宝工作台中模板的唯一标识
+   - 在创建模板时获得,**不会改变**
+   - 不需要从合同列表获取,**模板ID是固定的**
+
+2. **flowId(流程ID)**:
+   - 每次创建签署流程时生成的唯一标识
+   - 用于后续的签署链接获取、状态查询等操作
+   - 一个模板ID可以创建多个flowId(对应多个合同)
+
+3. **fileId(文件ID)**:
+   - 填充模板后生成的PDF文件在e签宝中的标识
+   - 如果使用 `createFromEsignTemplate` 接口,fileId会自动处理,无需关心
+
+### 常见误解
+
+❌ **错误理解**:
+1. 先填充模板字段
+2. 从合同列表中获取填充完的templateId
+3. 开始签署合同
+
+✅ **正确理解**:
+1. **templateId是固定的**(在e签宝工作台创建模板时获得)
+2. 调用 `createFromEsignTemplate` 接口时,传入templateId和要填充的数据
+3. 接口会:填充模板 → 生成文件 → 创建签署流程,返回flowId
+4. 使用flowId获取签署链接、启动流程、完成签署
+
+---
+
+## 后续流程
+
+模板填充和签署流程创建成功后,可以:
+
+1. 获取签署链接:调用 `/api/wechat/esign/flow/{flowId}/customer/sign-url`
+2. 启动签署流程:调用 `/api/wechat/esign/flow/{flowId}/start`
+3. 查询流程状态:调用 `/api/wechat/esign/flow/{flowId}/status`
+4. 下载已签署文档:调用 `/api/wechat/esign/flow/{flowId}/download`
+
+详细接口说明请参考 `EsignController.java`。
+

+ 739 - 0
docs/esign-integration-guide.md

@@ -0,0 +1,739 @@
+# e签宝电子签署集成实现文档
+
+## 一、概述
+
+本文档详细说明如何在贷款管理系统中集成e签宝电子签署功能,实现合同的在线电子签署。系统采用前后端分离架构,后端使用Spring Boot,前端使用uniapp开发微信小程序。
+
+## 二、准备工作
+
+### 2.1 注册e签宝账号
+
+1. 访问e签宝官网:https://www.esign.cn
+2. 注册企业账号并完成实名认证
+3. 创建应用,获取以下信息:
+   - AppId(应用ID)
+   - AppSecret(应用密钥)
+   - ProjectId(项目ID)
+   - API网关地址(生产环境:https://smlopenapi.esign.cn)
+
+### 2.2 配置回调地址
+
+在e签宝控制台配置回调地址:
+- 回调URL:`https://your-domain.com/api/wechat/esign/callback`
+- 回调方式:HTTP POST
+- 回调内容:JSON格式
+
+## 三、后端实现
+
+### 3.1 依赖配置
+
+已在`pom.xml`中添加e签宝SDK依赖:
+
+```xml
+<dependency>
+    <groupId>com.esign</groupId>
+    <artifactId>esign-sdk-java</artifactId>
+    <version>3.6.0</version>
+</dependency>
+```
+
+### 3.2 配置文件
+
+在`application-dev.yaml`中配置e签宝参数:
+
+```yaml
+system:
+  esign:
+    app-id: your-app-id
+    app-secret: your-app-secret
+    api-url: https://smlopenapi.esign.cn
+    project-id: your-project-id
+    enabled: true
+```
+
+### 3.3 核心类说明
+
+#### 3.3.1 实体类
+
+**EsignFlow** (`src/main/java/com/loan/system/domain/entity/EsignFlow.java`)
+- 存储签署流程信息
+- 包含流程ID、合同ID、签署状态等字段
+
+#### 3.3.2 服务类
+
+**EsignService** (`src/main/java/com/loan/system/service/EsignService.java`)
+- 提供签署流程的创建、启动、查询等核心功能
+
+**EsignServiceImpl** (`src/main/java/com/loan/system/service/Impl/EsignServiceImpl.java`)
+- 实现e签宝API调用逻辑
+- 处理签署流程状态同步
+
+#### 3.3.3 控制器
+
+**EsignController** (`src/main/java/com/loan/system/controller/wechat/EsignController.java`)
+- 提供RESTful API接口
+- 供前端调用
+
+### 3.4 数据库表结构
+
+系统会自动创建`esign_flow`表,主要字段:
+
+```sql
+CREATE TABLE `esign_flow` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `flow_id` varchar(100) NOT NULL COMMENT 'e签宝流程ID',
+  `contract_id` bigint(20) COMMENT '合同ID',
+  `case_id` bigint(20) COMMENT '案件ID',
+  `flow_status` int(11) COMMENT '流程状态:0-草稿 1-签署中 2-签署完成 3-签署失败 4-已撤销 5-已过期 6-已拒签',
+  `flow_status_desc` varchar(50) COMMENT '流程状态描述',
+  `document_id` varchar(100) COMMENT '签署文档ID',
+  `document_url` varchar(500) COMMENT '签署文档下载地址',
+  `document_name` varchar(200) COMMENT '签署文档名称',
+  `customer_account_id` varchar(100) COMMENT '客户签署人账号ID',
+  `business_account_id` varchar(100) COMMENT '业务方签署人账号ID',
+  `customer_sign_status` int(11) COMMENT '客户签署状态:0-待签署 1-已签署 2-已拒签 3-已过期',
+  `business_sign_status` int(11) COMMENT '业务方签署状态',
+  `customer_sign_time` varchar(50) COMMENT '客户签署时间',
+  `business_sign_time` varchar(50) COMMENT '业务方签署时间',
+  `create_time` varchar(50) COMMENT '创建时间',
+  `update_time` varchar(50) COMMENT '更新时间',
+  `is_delete` tinyint(1) DEFAULT 0 COMMENT '是否删除',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `idx_flow_id` (`flow_id`),
+  KEY `idx_contract_id` (`contract_id`),
+  KEY `idx_case_id` (`case_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+## 四、前端实现(uniapp)
+
+### 4.1 签署流程页面
+
+创建签署页面 `pages/contract/sign.vue`:
+
+```vue
+<template>
+  <view class="sign-container">
+    <view class="contract-info">
+      <text class="title">合同签署</text>
+      <text class="contract-name">{{ contractName }}</text>
+    </view>
+    
+    <view class="sign-status">
+      <text>签署状态:{{ signStatusText }}</text>
+    </view>
+    
+    <view class="sign-buttons" v-if="showSignButton">
+      <button class="sign-btn" @click="handleSign">开始签署</button>
+    </view>
+    
+    <view class="sign-progress" v-if="flowDetail">
+      <view class="progress-item">
+        <text>客户签署:{{ customerSignStatusText }}</text>
+      </view>
+      <view class="progress-item">
+        <text>业务方签署:{{ businessSignStatusText }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      contractId: null,
+      flowId: null,
+      contractName: '',
+      signStatusText: '待签署',
+      showSignButton: true,
+      flowDetail: null,
+      customerSignStatusText: '待签署',
+      businessSignStatusText: '待签署'
+    }
+  },
+  onLoad(options) {
+    this.contractId = options.contractId;
+    this.loadFlowDetail();
+  },
+  methods: {
+    // 加载签署流程详情
+    async loadFlowDetail() {
+      try {
+        const res = await this.$http.get(`/wechat/esign/flow/contract/${this.contractId}`);
+        if (res.code === 200) {
+          this.flowDetail = res.data;
+          this.flowId = res.data.flowId;
+          this.updateSignStatus(res.data);
+        }
+      } catch (error) {
+        console.error('加载流程详情失败', error);
+        uni.showToast({
+          title: '加载失败',
+          icon: 'none'
+        });
+      }
+    },
+    
+    // 更新签署状态
+    updateSignStatus(flowDetail) {
+      const statusMap = {
+        0: '草稿',
+        1: '签署中',
+        2: '签署完成',
+        3: '签署失败',
+        4: '已撤销',
+        5: '已过期',
+        6: '已拒签'
+      };
+      this.signStatusText = statusMap[flowDetail.flowStatus] || '未知';
+      
+      const signStatusMap = {
+        0: '待签署',
+        1: '已签署',
+        2: '已拒签',
+        3: '已过期'
+      };
+      this.customerSignStatusText = signStatusMap[flowDetail.customerSignStatus] || '未知';
+      this.businessSignStatusText = signStatusMap[flowDetail.businessSignStatus] || '未知';
+      
+      // 如果流程已完成或已撤销,隐藏签署按钮
+      if (flowDetail.flowStatus === 2 || flowDetail.flowStatus === 4) {
+        this.showSignButton = false;
+      }
+    },
+    
+    // 处理签署
+    async handleSign() {
+      try {
+        // 获取用户信息
+        const userInfo = uni.getStorageSync('userInfo');
+        
+        // 获取签署链接
+        const res = await this.$http.get(`/wechat/esign/flow/${this.flowId}/customer/sign-url`, {
+          params: {
+            mobile: userInfo.mobile,
+            name: userInfo.name
+          }
+        });
+        
+        if (res.code === 200) {
+          const signUrl = res.data;
+          // 在小程序webview中打开签署页面
+          uni.navigateTo({
+            url: `/pages/webview/webview?url=${encodeURIComponent(signUrl)}&title=合同签署`
+          });
+        } else {
+          uni.showToast({
+            title: res.msg || '获取签署链接失败',
+            icon: 'none'
+          });
+        }
+      } catch (error) {
+        console.error('获取签署链接失败', error);
+        uni.showToast({
+          title: '获取签署链接失败',
+          icon: 'none'
+        });
+      }
+    },
+    
+    // 轮询查询签署状态
+    startPolling() {
+      this.pollTimer = setInterval(async () => {
+        if (this.flowId) {
+          try {
+            const res = await this.$http.get(`/wechat/esign/flow/${this.flowId}/status`);
+            if (res.code === 200) {
+              this.updateSignStatus(res.data);
+              // 如果签署完成,停止轮询
+              if (res.data.flowStatus === 2) {
+                clearInterval(this.pollTimer);
+                uni.showToast({
+                  title: '签署完成',
+                  icon: 'success'
+                });
+                // 返回合同列表
+                setTimeout(() => {
+                  uni.navigateBack();
+                }, 1500);
+              }
+            }
+          } catch (error) {
+            console.error('查询状态失败', error);
+          }
+        }
+      }, 3000); // 每3秒查询一次
+    }
+  },
+  
+  onShow() {
+    // 页面显示时开始轮询
+    if (this.flowId) {
+      this.startPolling();
+    }
+  },
+  
+  onHide() {
+    // 页面隐藏时停止轮询
+    if (this.pollTimer) {
+      clearInterval(this.pollTimer);
+    }
+  }
+}
+</script>
+
+<style scoped>
+.sign-container {
+  padding: 20rpx;
+}
+
+.contract-info {
+  background: #fff;
+  padding: 30rpx;
+  border-radius: 10rpx;
+  margin-bottom: 20rpx;
+}
+
+.title {
+  font-size: 36rpx;
+  font-weight: bold;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.contract-name {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.sign-status {
+  background: #fff;
+  padding: 20rpx 30rpx;
+  border-radius: 10rpx;
+  margin-bottom: 20rpx;
+}
+
+.sign-buttons {
+  margin: 40rpx 0;
+}
+
+.sign-btn {
+  width: 100%;
+  background: #007aff;
+  color: #fff;
+  border-radius: 10rpx;
+  padding: 25rpx 0;
+}
+
+.sign-progress {
+  background: #fff;
+  padding: 30rpx;
+  border-radius: 10rpx;
+}
+
+.progress-item {
+  padding: 15rpx 0;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.progress-item:last-child {
+  border-bottom: none;
+}
+</style>
+```
+
+### 4.2 WebView页面
+
+创建WebView页面用于显示签署页面 `pages/webview/webview.vue`:
+
+```vue
+<template>
+  <view class="webview-container">
+    <web-view :src="url"></web-view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      url: ''
+    }
+  },
+  onLoad(options) {
+    this.url = decodeURIComponent(options.url || '');
+    if (options.title) {
+      uni.setNavigationBarTitle({
+        title: decodeURIComponent(options.title)
+      });
+    }
+  }
+}
+</script>
+
+<style scoped>
+.webview-container {
+  width: 100%;
+  height: 100vh;
+}
+</style>
+```
+
+### 4.3 创建签署流程
+
+在合同详情页面,添加创建签署流程的功能:
+
+```javascript
+// 创建签署流程
+async createSignFlow() {
+  try {
+    uni.showLoading({
+      title: '创建中...'
+    });
+    
+    // 获取合同信息
+    const contractInfo = this.contractDetail;
+    // 获取客户信息
+    const customerInfo = this.customerInfo;
+    
+    const params = {
+      contractId: this.contractId,
+      caseId: this.caseId,
+      documentUrl: contractInfo.filePath, // 合同文件URL
+      documentName: contractInfo.contractName,
+      customerName: customerInfo.name,
+      customerMobile: customerInfo.mobile,
+      customerIdNumber: customerInfo.idNumber,
+      businessName: '业务方名称', // 从配置或用户信息获取
+      businessMobile: '业务方手机号',
+      signDeadline: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7天后过期
+    };
+    
+    const res = await this.$http.post('/wechat/esign/flow/create', params);
+    
+    uni.hideLoading();
+    
+    if (res.code === 200) {
+      uni.showToast({
+        title: '创建成功',
+        icon: 'success'
+      });
+      // 启动签署流程
+      await this.startSignFlow(res.data);
+      // 跳转到签署页面
+      uni.navigateTo({
+        url: `/pages/contract/sign?contractId=${this.contractId}`
+      });
+    } else {
+      uni.showToast({
+        title: res.msg || '创建失败',
+        icon: 'none'
+      });
+    }
+  } catch (error) {
+    uni.hideLoading();
+    console.error('创建签署流程失败', error);
+    uni.showToast({
+      title: '创建失败',
+      icon: 'none'
+    });
+  }
+},
+
+// 启动签署流程
+async startSignFlow(flowId) {
+  try {
+    const res = await this.$http.post(`/wechat/esign/flow/${flowId}/start`);
+    if (res.code !== 200) {
+      console.error('启动签署流程失败', res);
+    }
+  } catch (error) {
+    console.error('启动签署流程失败', error);
+  }
+}
+```
+
+## 五、完整签署流程
+
+### 5.1 流程步骤
+
+1. **创建签署流程**
+   - 前端调用 `/wechat/esign/flow/create` 接口
+   - 后端创建e签宝签署流程,返回flowId
+   - 保存流程信息到数据库
+
+2. **启动签署流程**
+   - 前端调用 `/wechat/esign/flow/{flowId}/start` 接口
+   - 后端调用e签宝API启动流程
+
+3. **获取签署链接**
+   - 前端调用 `/wechat/esign/flow/{flowId}/customer/sign-url` 接口
+   - 后端返回签署链接
+
+4. **打开签署页面**
+   - 前端在WebView中打开签署链接
+   - 用户在e签宝页面完成签署
+
+5. **查询签署状态**
+   - 前端轮询调用 `/wechat/esign/flow/{flowId}/status` 接口
+   - 后端同步e签宝流程状态
+
+6. **签署完成**
+   - e签宝回调 `/wechat/esign/callback` 接口
+   - 后端更新流程状态
+   - 前端显示签署完成
+
+7. **下载已签署文档**
+   - 前端调用 `/wechat/esign/flow/{flowId}/download` 接口
+   - 后端返回文档下载地址
+
+### 5.2 时序图
+
+```
+前端                后端                e签宝
+ |                  |                   |
+ |--创建流程-------->|                   |
+ |                  |--创建流程--------->|
+ |                  |<--返回flowId------|
+ |<--返回flowId------|                   |
+ |                  |                   |
+ |--启动流程-------->|                   |
+ |                  |--启动流程--------->|
+ |                  |<--启动成功--------|
+ |<--启动成功--------|                   |
+ |                  |                   |
+ |--获取签署链接---->|                   |
+ |                  |--获取签署链接---->|
+ |                  |<--返回签署链接----|
+ |<--返回签署链接----|                   |
+ |                  |                   |
+ |--打开WebView-----|                   |
+ |                  |                   |
+ |                  |<--用户签署--------|
+ |                  |                   |
+ |                  |--回调通知--------->|
+ |                  |                   |
+ |--查询状态-------->|                   |
+ |                  |--查询状态--------->|
+ |                  |<--返回状态--------|
+ |<--返回状态--------|                   |
+ |                  |                   |
+ |--下载文档-------->|                   |
+ |                  |--获取下载地址----->|
+ |                  |<--返回下载地址----|
+ |<--返回下载地址----|                   |
+```
+
+## 六、API接口说明
+
+### 6.1 创建签署流程
+
+**接口地址:** `POST /wechat/esign/flow/create`
+
+**请求参数:**
+```json
+{
+  "contractId": 123,
+  "caseId": 456,
+  "documentUrl": "https://example.com/contract.pdf",
+  "documentName": "借款合同",
+  "customerName": "张三",
+  "customerMobile": "13800138000",
+  "customerIdNumber": "110101199001011234",
+  "businessName": "业务方",
+  "businessMobile": "13900139000",
+  "businessIdNumber": "110101199001011235",
+  "signDeadline": 1640995200000,
+  "remark": "备注信息"
+}
+```
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "msg": "创建签署流程成功",
+  "data": "flowId123456"
+}
+```
+
+### 6.2 启动签署流程
+
+**接口地址:** `POST /wechat/esign/flow/{flowId}/start`
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "msg": "启动签署流程成功"
+}
+```
+
+### 6.3 获取客户签署链接
+
+**接口地址:** `GET /wechat/esign/flow/{flowId}/customer/sign-url`
+
+**请求参数:**
+- `mobile`: 手机号(必填)
+- `name`: 姓名(必填)
+- `accountId`: 账号ID(可选)
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "msg": "获取成功",
+  "data": "https://esign.cn/sign/xxx"
+}
+```
+
+### 6.4 查询流程状态
+
+**接口地址:** `GET /wechat/esign/flow/{flowId}/status`
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "msg": "查询成功",
+  "data": {
+    "flowStatus": 2,
+    "flowStatusDesc": "签署完成",
+    "signers": [...]
+  }
+}
+```
+
+### 6.5 下载已签署文档
+
+**接口地址:** `GET /wechat/esign/flow/{flowId}/download`
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "msg": "获取下载地址成功",
+  "data": "https://esign.cn/download/xxx"
+}
+```
+
+### 6.6 撤销签署流程
+
+**接口地址:** `POST /wechat/esign/flow/{flowId}/revoke`
+
+**请求参数:**
+- `revokeReason`: 撤销原因(必填)
+
+**返回结果:**
+```json
+{
+  "code": 200,
+  "msg": "撤销成功"
+}
+```
+
+### 6.7 e签宝回调接口
+
+**接口地址:** `POST /wechat/esign/callback`
+
+**请求参数(e签宝回调):**
+```json
+{
+  "flowId": "flowId123456",
+  "action": "SIGN_FLOW_FINISH",
+  "flowStatus": 2,
+  "signerAccountId": "accountId123",
+  "timestamp": 1640995200000
+}
+```
+
+## 七、注意事项
+
+### 7.1 合同文件上传
+
+在创建签署流程前,需要先将合同文件上传到e签宝:
+1. 调用e签宝文件上传接口
+2. 获取文件ID(fileId)
+3. 使用fileId创建签署流程
+
+### 7.2 签署位置配置
+
+签署位置(坐标)需要根据实际合同模板调整:
+- 在`EsignServiceImpl.createSignFields()`方法中配置
+- 支持多页文档,需要指定页码
+- 支持多个签署区域
+
+### 7.3 错误处理
+
+1. **网络错误**:实现重试机制
+2. **API错误**:记录错误日志,返回友好提示
+3. **签署超时**:设置合理的签署截止时间
+4. **回调失败**:实现回调重试机制
+
+### 7.4 安全性
+
+1. **接口鉴权**:所有接口需要JWT认证
+2. **参数校验**:验证手机号、身份证号格式
+3. **回调验证**:验证e签宝回调签名
+4. **数据加密**:敏感信息加密存储
+
+### 7.5 性能优化
+
+1. **状态轮询**:使用合理的轮询间隔(建议3-5秒)
+2. **缓存机制**:缓存访问令牌,避免频繁获取
+3. **异步处理**:回调处理使用异步方式
+4. **数据库索引**:为flowId、contractId等字段建立索引
+
+## 八、测试建议
+
+### 8.1 单元测试
+
+- 测试服务类方法
+- 测试API接口
+- 测试异常情况
+
+### 8.2 集成测试
+
+- 测试完整签署流程
+- 测试回调处理
+- 测试并发场景
+
+### 8.3 沙箱环境
+
+- 使用e签宝沙箱环境进行测试
+- 验证各种签署场景
+- 测试异常情况处理
+
+## 九、常见问题
+
+### 9.1 签署链接无法打开
+
+- 检查URL是否正确编码
+- 检查小程序是否配置了业务域名
+- 检查网络连接
+
+### 9.2 签署状态不同步
+
+- 检查回调接口是否正常
+- 检查轮询逻辑是否正确
+- 检查数据库更新是否成功
+
+### 9.3 文档下载失败
+
+- 检查文档是否已签署完成
+- 检查下载地址是否有效
+- 检查文件权限
+
+## 十、参考资料
+
+- [e签宝开放平台文档](https://open.esign.cn/)
+- [e签宝Java SDK文档](https://open.esign.cn/doc/detail?id=opendoc%2Fjava_sdk%2Fjava_sdk)
+- [e签宝API参考](https://open.esign.cn/doc/detail?id=opendoc%2Fapi%2Fapi)
+
+---
+
+**文档版本:** v1.0  
+**最后更新:** 2024-01-01  
+**维护人员:** 开发团队
+

+ 418 - 0
docs/esign-oauth2-handwritten-sign.md

@@ -0,0 +1,418 @@
+# e签宝OAuth2.0手写签署流程说明
+
+## 概述
+
+本文档说明如何使用e签宝官方SDK(paas-sdk-3.1.4.jar)实现OAuth2.0鉴权方式的手写签署功能。
+
+## OAuth2.0鉴权机制
+
+### 1. 认证流程
+
+e签宝使用OAuth2.0客户端凭证模式(client_credentials)进行API鉴权:
+
+```
+┌──────────┐                  ┌──────────┐                  ┌──────────┐
+│  应用    │                  │ e签宝API │                  │ e签宝    │
+│          │                  │  网关    │                  │ 认证服务  │
+└────┬─────┘                  └────┬─────┘                  └────┬─────┘
+     │                              │                              │
+     │ 1. 请求access_token          │                              │
+     │ (AppId + AppSecret)          │                              │
+     ├─────────────────────────────>│                              │
+     │                              ├─────────────────────────────>│
+     │                              │                              │
+     │                              │ 2. 返回access_token          │
+     │                              │<─────────────────────────────┤
+     │ 3. 返回access_token          │                              │
+     │<─────────────────────────────┤                              │
+     │                              │                              │
+     │ 4. 使用access_token调用API   │                              │
+     ├─────────────────────────────>│                              │
+     │                              │                              │
+     │ 5. 返回API响应               │                              │
+     │<─────────────────────────────┤                              │
+     │                              │                              │
+```
+
+### 2. 获取Access Token
+
+**请求方式**: GET  
+**请求URL**: `{apiUrl}/v1/oauth2/access_token`
+
+**请求参数**:
+- `appId`: 应用ID
+- `secret`: 应用密钥
+- `grantType`: 固定值 `client_credentials`
+
+**请求头**:
+```
+X-Tsign-Open-App-Id: {appId}
+X-Tsign-Open-App-Secret: {secret}
+Content-Type: application/json
+```
+
+**响应示例**:
+```json
+{
+    "code": 0,
+    "message": "成功",
+    "data": {
+        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+        "expiresIn": 7200,
+        "refreshToken": "refresh_token_string"
+    }
+}
+```
+
+**Token有效期**: 通常为7200秒(2小时),需要定期刷新
+
+### 3. 使用Access Token调用API
+
+所有API请求需要在请求头中携带access_token:
+
+**请求头**:
+```
+X-Tsign-Open-App-Id: {appId}
+X-Tsign-Open-Token: {access_token}
+Content-Type: application/json; charset=UTF-8
+```
+
+## 手写签署流程
+
+### 完整业务流程
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│  步骤1: OAuth2.0获取访问令牌                                │
+│  POST /v1/oauth2/access_token                              │
+│  输入: AppId + AppSecret                                    │
+│  输出: access_token                                         │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤2: 创建或获取签署人账号                                │
+│  POST /v3/accounts/createByThirdPartyUserId                │
+│  输入: mobile + name + idNumber                             │
+│  输出: accountId                                            │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤3: 上传合同文件(如需)                                │
+│  POST /v3/files/getUploadUrl                               │
+│  输入: fileName + fileSize                                  │
+│  输出: uploadUrl + fileId                                   │
+│  然后: PUT uploadUrl (上传文件)                             │
+│  最后: POST /v3/files/{fileId}/uploadComplete              │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤4: 创建签署流程                                        │
+│  POST /v3/signflows                                        │
+│  输入: docs + signers (包含手写签署区域)                    │
+│  输出: flowId                                               │
+│                                                             │
+│  关键配置:                                                 │
+│  - signFieldStyle: 1 (手写签名)                             │
+│  - signFieldType: 5 (签名类型)                              │
+│  - autoExecute: false (手动签署)                            │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤5: 启动签署流程                                        │
+│  POST /v3/signflows/{flowId}/start                         │
+│  输入: flowId                                               │
+│  输出: 成功/失败                                            │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤6: 获取手写签署链接                                    │
+│  GET /v3/signflows/{flowId}/signers/{accountId}/signUrl    │
+│  输入: flowId + accountId                                   │
+│  输出: signUrl (签署人通过此链接进行手写签名)               │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤7: 签署人进行手写签署                                  │
+│  打开signUrl,在签署页面进行手写签名                        │
+│  - 可以在手机或PC端进行手写                                 │
+│  - 支持触摸屏手写和鼠标手写                                 │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤8: 查询签署状态                                        │
+│  GET /v3/signflows/{flowId}                                │
+│  输入: flowId                                               │
+│  输出: flowStatus (0-草稿, 1-签署中, 2-签署完成, ...)      │
+└─────────────────────────────────────────────────────────────┘
+                            ↓
+┌─────────────────────────────────────────────────────────────┐
+│  步骤9: 下载已签署文档                                      │
+│  GET /v3/signflows/{flowId}/documents                      │
+│  输入: flowId                                               │
+│  输出: 已签署的PDF文档下载地址                              │
+└─────────────────────────────────────────────────────────────┘
+```
+
+## 代码实现
+
+### 1. OAuth2.0工具类 (EsignSdkUtil)
+
+```java
+package com.loan.system.utils;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * e签宝OAuth2.0鉴权工具类
+ * 使用OAuth2.0客户端凭证模式(client_credentials)获取访问令牌
+ */
+@Slf4j
+public class EsignSdkUtil {
+    
+    private static volatile String cachedToken;
+    private static volatile long tokenExpireTime;
+    
+    /**
+     * 获取OAuth2.0访问令牌
+     */
+    public static String getAccessToken(String appId, String appSecret, String apiUrl) {
+        // 检查缓存的token是否仍然有效
+        if (cachedToken != null && System.currentTimeMillis() < (tokenExpireTime - 5 * 60 * 1000)) {
+            return cachedToken;
+        }
+        
+        String url = apiUrl + "/v1/oauth2/access_token";
+        String getUrl = url + "?appId=" + appId + "&secret=" + appSecret + "&grantType=client_credentials";
+        
+        HttpResponse response = HttpRequest.get(getUrl)
+                .header("X-Tsign-Open-App-Id", appId)
+                .header("X-Tsign-Open-App-Secret", appSecret)
+                .execute();
+        
+        JSONObject result = JSONUtil.parseObj(response.body());
+        if (result.getInt("code") == 0) {
+            JSONObject data = result.getJSONObject("data");
+            String token = data.getStr("token");
+            Integer expiresIn = data.getInt("expiresIn", 7200);
+            
+            cachedToken = token;
+            tokenExpireTime = System.currentTimeMillis() + expiresIn * 1000L;
+            
+            return token;
+        }
+        
+        return null;
+    }
+}
+```
+
+### 2. 创建手写签署流程
+
+在 `EsignServiceImpl.createSignFlow()` 方法中,关键配置如下:
+
+```java
+// 创建手写签署区域
+private List<Map<String, Object>> createSignFields(int signOrder, String signerName) {
+    List<Map<String, Object>> fields = new ArrayList<>();
+    Map<String, Object> field = new HashMap<>();
+    field.put("fileId", 1); // 文档序号
+    field.put("signerName", signerName);
+    field.put("autoExecute", false); // 不自动执行,需要手动签署
+    field.put("signFieldType", 5); // 5-签名类型
+    field.put("signFieldStyle", 1); // 1-手写签名(重要!)
+    field.put("signFieldStyleType", 1); // 1-手写签名样式
+    field.put("page", 1); // 页码
+    field.put("x", 100); // X坐标
+    field.put("y", 200); // Y坐标
+    field.put("width", 200); // 宽度
+    field.put("height", 50); // 高度
+    fields.add(field);
+    return fields;
+}
+```
+
+### 3. 获取手写签署链接
+
+```java
+@Override
+public String getCustomerSignUrl(String flowId, String accountId, String mobile, String name) {
+    // 使用OAuth2.0鉴权获取签署链接
+    String url = esignProperties.getApiUrl() + "/v3/signflows/" + flowId 
+            + "/signers/" + accountId + "/signUrl";
+    
+    JSONObject params = new JSONObject();
+    params.put("accountId", accountId);
+    params.put("urlType", 1); // 1-移动端H5,2-PC端
+    
+    // sendRequest方法内部使用OAuth2.0 token
+    JSONObject result = sendRequest(url, params);
+    if (result != null && result.getInt("code") == 0) {
+        return result.getJSONObject("data").getStr("signUrl");
+    }
+    return null;
+}
+```
+
+## 关键配置说明
+
+### 手写签署关键参数
+
+| 参数 | 值 | 说明 |
+|------|-----|------|
+| `signFieldStyle` | `1` | 手写签名样式(必须为1才能手写签署) |
+| `signFieldType` | `5` | 签名类型 |
+| `signFieldStyleType` | `1` | 手写签名样式类型 |
+| `autoExecute` | `false` | 不自动执行,需要签署人手动签署 |
+| `urlType` | `1` 或 `2` | 签署链接类型:1-移动端H5,2-PC端 |
+
+### OAuth2.0 Token管理
+
+1. **Token缓存**: 实现token缓存机制,避免频繁请求
+2. **自动刷新**: 在token过期前5分钟自动刷新
+3. **错误处理**: token失效时自动重新获取
+
+## API接口说明
+
+### 1. 创建签署流程
+
+**接口**: `POST /v3/signflows`
+
+**请求体关键字段**:
+```json
+{
+    "autoExecute": false,
+    "businessScene": "合同签署",
+    "docs": [
+        {
+            "fileId": "文件ID",
+            "fileName": "合同.pdf"
+        }
+    ],
+    "signers": [
+        {
+            "signerAccount": {
+                "signerAccountId": "签署人账号ID",
+                "mobile": "手机号",
+                "name": "姓名"
+            },
+            "signOrder": 1,
+            "signFields": [
+                {
+                    "fileId": 1,
+                    "signFieldStyle": 1,
+                    "signFieldType": 5,
+                    "signFieldStyleType": 1,
+                    "page": 1,
+                    "x": 100,
+                    "y": 200,
+                    "width": 200,
+                    "height": 50
+                }
+            ]
+        }
+    ]
+}
+```
+
+### 2. 获取签署链接
+
+**接口**: `GET /v3/signflows/{flowId}/signers/{accountId}/signUrl`
+
+**请求参数**:
+- `accountId`: 签署人账号ID
+- `urlType`: 链接类型(1-移动端,2-PC端)
+
+**响应**:
+```json
+{
+    "code": 0,
+    "data": {
+        "signUrl": "https://..."
+    }
+}
+```
+
+### 3. 启动签署流程
+
+**接口**: `POST /v3/signflows/{flowId}/start`
+
+启动后,签署人才能通过签署链接进行签署。
+
+## 使用SDK的说明
+
+### 当前实现方式
+
+当前代码使用HTTP方式直接调用e签宝API,但使用了OAuth2.0标准鉴权方式:
+- 使用 `EsignSdkUtil` 工具类获取OAuth2.0 token
+- 所有API请求都携带OAuth2.0 token
+- 符合OAuth2.0标准规范
+
+### 使用官方SDK(可选)
+
+如果希望完全使用官方SDK(paas-sdk-3.1.4.jar),可以参考以下方式:
+
+1. **初始化SDK客户端**:
+```java
+// 根据SDK文档初始化ServiceClient
+ServiceClient client = EsignsdkServiceFactory.buildServiceClient(oauth2Config);
+```
+
+2. **调用SDK方法**:
+```java
+// 使用SDK的方法替代HTTP请求
+client.createSignFlow(params);
+client.getSignUrl(flowId, accountId);
+```
+
+**注意**: SDK的具体API请参考e签宝官方文档或联系技术支持。
+
+## 配置说明
+
+在 `application-dev.yaml` 中配置:
+
+```yaml
+system:
+  esign:
+    app-id: 7439093682  # e签宝应用ID
+    app-secret: da96bb269786338c156050cfa23eba4f  # e签宝应用密钥
+    api-url: https://smlopenapi.esign.cn  # API地址(沙箱环境)
+    # api-url: https://openapi.esign.cn  # 生产环境
+    enabled: true
+```
+
+## 常见问题
+
+### 1. Token过期问题
+
+**问题**: API调用返回401或token过期错误
+
+**解决**: 
+- Token有效期为2小时,系统会自动刷新
+- 如果出现问题,可以调用 `EsignSdkUtil.clearCachedToken()` 清除缓存,重新获取
+
+### 2. 手写签署不显示
+
+**问题**: 签署链接打开后没有手写签署选项
+
+**解决**:
+- 确保 `signFieldStyle=1`(手写签名)
+- 确保 `signFieldType=5`(签名类型)
+- 确保流程已启动(调用start接口)
+
+### 3. 签署位置不正确
+
+**问题**: 手写签署区域位置不对
+
+**解决**:
+- 调整 `x`、`y`、`width`、`height` 参数
+- 注意坐标是从PDF左上角开始计算的像素值
+- 建议先在e签宝工作台测试签署位置
+
+## 参考资料
+
+- e签宝开放平台文档: https://open.esign.cn/doc
+- OAuth2.0规范: https://oauth.net/2/
+- e签宝SDK文档: 联系e签宝技术支持获取
+

+ 72 - 0
docs/esign-sdk-install.md

@@ -0,0 +1,72 @@
+# e签宝SDK安装指南
+
+## 问题说明
+如果Maven仓库无法访问,会出现以下错误:
+```
+Failed to read artifact descriptor for com.esign:esign-sdk-java:jar:3.6.0
+Could not transfer artifact ... 不知道这样的主机。 (mvn.esign.cn)
+```
+
+## 解决方案
+
+### 方案1:手动安装到本地Maven仓库(推荐)
+
+1. **下载SDK**
+   - 访问 e签宝开放平台:https://open.esign.cn/
+   - 登录后进入"开发文档" -> "SDK下载"
+   - 下载 `esign-sdk-java-3.6.0.jar`
+
+2. **安装到本地仓库**
+   ```bash
+   mvn install:install-file ^
+     -Dfile=esign-sdk-java-3.6.0.jar ^
+     -DgroupId=com.esign ^
+     -DartifactId=esign-sdk-java ^
+     -Dversion=3.6.0 ^
+     -Dpackaging=jar
+   ```
+
+### 方案2:使用system scope(临时方案)
+
+1. **下载SDK到项目lib目录**
+   - 在项目根目录创建 `lib` 文件夹
+   - 将 `esign-sdk-java-3.6.0.jar` 放入 `lib` 目录
+
+2. **修改pom.xml**
+   - 注释掉原有的依赖
+   - 取消注释 `system scope` 的依赖配置(在pom.xml中已提供)
+
+### 方案3:配置代理(如果可以访问外网)
+
+如果您的网络需要代理,在 `~/.m2/settings.xml` 中配置:
+```xml
+<settings>
+  <proxies>
+    <proxy>
+      <id>myproxy</id>
+      <active>true</active>
+      <protocol>http</protocol>
+      <host>your-proxy-host</host>
+      <port>your-proxy-port</port>
+    </proxy>
+  </proxies>
+</settings>
+```
+
+## 验证安装
+
+运行以下命令验证依赖是否已正确解析:
+```bash
+mvn dependency:tree -Dincludes=com.esign:esign-sdk-java
+```
+
+如果看到依赖树中包含 `esign-sdk-java:jar:3.6.0`,说明安装成功。
+
+## 注意事项
+
+1. **版本号**:确保下载的SDK版本与pom.xml中的版本一致(当前为3.6.0)
+2. **网络问题**:如果公司内网无法访问e签宝仓库,优先使用方案1
+3. **Maven仓库**:方案1会将jar安装到本地Maven仓库(通常在 `~/.m2/repository`)
+
+
+

+ 67 - 0
docs/wechat-subscribe-flow.md

@@ -0,0 +1,67 @@
+## 微信小程序订阅消息推送流程(前后端协同)
+
+### 前置条件
+- 后端配置 `system.wechat.appid`、`system.wechat.secret`,并确保服务器可访问微信开放接口。
+- 小程序后台已创建订阅消息模板并拿到 `templateId`。
+- 前端页面在需要推送的位置调用 `wx.requestSubscribeMessage` 完成用户授权。
+
+### 交互时序
+1. **前端获取用户身份**
+   - 调用 `wx.login` 得到 `code`。
+   - 将 `code` 发送给后端(可复用现有登录接口),后端换取 `session_key` 和 `openid`,并存入 Redis `wx_session_id_{sessionId}`。
+2. **(可选)获取手机号**
+   - 前端携带 `code` 调用 `POST /api/wx/phone`,后端使用 `getAccessToken` + `getUserPhoneNumber` 获取并返回手机号。
+3. **用户授权订阅**
+   - 前端调用 `wx.requestSubscribeMessage({tmplIds: [templateId]})`。
+4. **后端发送订阅消息**
+   - 前端将 `openid`、`templateId`、`page`、`data` 传给 `POST /api/wx/send`。
+   - 后端封装 `TemplateMessage`,通过 `WxService.sendTemplateMessage` 调用微信 `message/subscribe/send` 完成推送。
+
+### 后端接口(SendMessageController)
+- `GET /api/wx/access-token`:调试用,查看当前 token。
+- `POST /api/wx/phone`:请求体 `{ "code": "<wx.login 返回的 code>" }`,返回 `{ "phoneNumber": "..." }`。
+- `POST /api/wx/send`:请求体示例:
+  ```json
+  {
+    "openid": "用户openid",
+    "templateId": "模板ID",
+    "page": "pages/index/index",
+    "data": {
+      "thing1": { "value": "张三" },
+      "time2":  { "value": "2024-01-01 12:00" }
+    },
+    "clientMsgId": "可选-幂等ID"
+  }
+  ```
+  返回 `{ "success": true/false }`。
+
+### 数据要点
+- `data` 的 key 必须与模板的关键词 ID 对应,value 为 `{ "value": "展示内容" }`。
+- `page` 需是已发布的小程序页面路径。
+- `clientMsgId` 可用于防止重复推送(微信侧支持幂等)。
+
+### 调试建议
+- 使用微信开发者工具调用接口时,需确保请求域名在小程序后台「开发设置-服务器域名」里配置。
+- 微信接口有调用频率限制;本例未做 token 缓存,生产环境需增加 Redis 缓存与过期处理。
+- 控制器仅用于联调,请根据需要加鉴权、限流与异常告警。 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 353 - 0
docs/前端分块上传实现说明.md

@@ -0,0 +1,353 @@
+# 微信小程序/UniApp 分块上传实现说明
+
+## 📋 概述
+
+分块上传不是固定的"三次请求",而是 **1 + N + 1** 次请求:
+- **1次** 初始化请求 - 获取 `uploadId`
+- **N次** 上传分块请求 - 每个分块一次请求(N = 文件大小 ÷ 分块大小)
+- **1次** 完成请求 - 合并所有分块并保存到数据库
+
+**示例:** 上传一个 10MB 的文件,分块大小为 2MB
+- 1次初始化
+- 5次上传分块(10MB ÷ 2MB = 5个分块)
+- 1次完成
+- **总共 7 次请求**
+
+## 🔄 完整流程
+
+```
+1. 用户选择文件
+   ↓
+2. 调用初始化接口 → 获取 uploadId 和 fileName
+   ↓
+3. 将文件分成 N 个分块
+   ↓
+4. 循环上传每个分块 → 收集 PartETag
+   ├─ 分块1 → PartETag1
+   ├─ 分块2 → PartETag2
+   ├─ ...
+   └─ 分块N → PartETagN
+   ↓
+5. 调用完成接口 → 传入所有 PartETag → 合并文件并保存
+   ↓
+6. 上传完成
+```
+
+## 📱 微信小程序特殊处理
+
+微信小程序的 `wx.uploadFile` API 只能上传文件路径,不能直接上传 ArrayBuffer 或 Blob。因此需要:
+
+1. **读取文件** → 使用 `wx.getFileSystemManager().readFile()` 读取整个文件
+2. **切分文件** → 使用 `ArrayBuffer.slice()` 切分成多个分块
+3. **保存临时文件** → 将每个分块保存为临时文件(`wx.env.USER_DATA_PATH`)
+4. **上传分块** → 使用 `wx.uploadFile()` 上传临时文件
+5. **清理临时文件** → 上传完成后删除临时文件
+
+## 🚀 核心实现代码
+
+### 1. 分块上传工具函数
+
+创建文件:`utils/multipartUpload.js`
+
+```javascript
+/**
+ * 微信小程序/UniApp 分块上传实现
+ */
+
+const CHUNK_SIZE = 2 * 1024 * 1024; // 每个分块大小 2MB
+const BASE_URL = 'https://your-api-domain.com'; // 替换为你的后端API地址
+
+/**
+ * 分块上传主函数
+ */
+async function multipartUpload(filePathOrFile, caseId, fileType, originalName, isDelete = {}, onProgress) {
+  try {
+    // 1. 获取文件信息
+    let fileSize, filePath, fileObject;
+    
+    // #ifdef H5
+    if (filePathOrFile instanceof File) {
+      fileObject = filePathOrFile;
+      fileSize = fileObject.size;
+      filePath = null;
+    } else {
+      const fileInfo = await getFileInfo(filePathOrFile);
+      fileSize = fileInfo.size;
+      filePath = filePathOrFile;
+    }
+    // #endif
+    
+    // #ifdef MP-WEIXIN || APP-PLUS
+    const fileInfo = await getFileInfo(filePathOrFile);
+    fileSize = fileInfo.size;
+    filePath = filePathOrFile;
+    fileObject = null;
+    // #endif
+    
+    const totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
+    
+    // 2. 初始化分块上传
+    const initResult = await initMultipartUpload(caseId, fileType, originalName);
+    if (!initResult.success) {
+      throw new Error('初始化分块上传失败: ' + initResult.message);
+    }
+    
+    const { uploadId, fileName, randomUUID, ext } = initResult.data;
+    
+    // 3. 上传所有分块
+    const partETags = [];
+    let uploadedSize = 0;
+    const tempFiles = [];
+    
+    for (let partNumber = 1; partNumber <= totalChunks; partNumber++) {
+      const start = (partNumber - 1) * CHUNK_SIZE;
+      const end = Math.min(start + CHUNK_SIZE, fileSize);
+      const chunkSize = end - start;
+      
+      // 读取分块数据
+      let chunkData;
+      // #ifdef H5
+      if (fileObject) {
+        chunkData = await readFileChunkFromFile(fileObject, start, end);
+      } else {
+        chunkData = await readFileChunkToTempFile(filePath, start, end, partNumber);
+      }
+      // #endif
+      // #ifdef MP-WEIXIN || APP-PLUS
+      chunkData = await readFileChunkToTempFile(filePath, start, end, partNumber);
+      // #endif
+      
+      if (chunkData.tempFilePath) {
+        tempFiles.push(chunkData.tempFilePath);
+      }
+      
+      // 上传分块(支持重试)
+      const partETag = await uploadPartWithRetry(
+        fileName, uploadId, partNumber, chunkData, chunkSize, 3
+      );
+      
+      partETags.push({
+        partNumber: partETag.partNumber,
+        eTag: partETag.eTag
+      });
+      
+      // 清理临时文件
+      if (chunkData.tempFilePath) {
+        try {
+          // #ifdef MP-WEIXIN
+          wx.getFileSystemManager().unlinkSync(chunkData.tempFilePath);
+          // #endif
+          // #ifdef APP-PLUS
+          uni.getFileSystemManager().unlinkSync(chunkData.tempFilePath);
+          // #endif
+        } catch (e) {
+          console.warn('清理临时文件失败:', e);
+        }
+      }
+      
+      // 更新进度
+      uploadedSize += chunkSize;
+      const progress = Math.round((uploadedSize / fileSize) * 100);
+      if (onProgress) {
+        onProgress({
+          progress: progress,
+          uploaded: uploadedSize,
+          total: fileSize,
+          currentChunk: partNumber,
+          totalChunks: totalChunks
+        });
+      }
+    }
+    
+    // 4. 完成分块上传
+    const completeResult = await completeMultipartUpload(
+      caseId, fileType, fileName, uploadId, randomUUID, ext,
+      originalName, fileSize, isDelete, partETags
+    );
+    
+    if (!completeResult.success) {
+      await abortMultipartUpload(fileName, uploadId);
+      throw new Error('完成分块上传失败: ' + completeResult.message);
+    }
+    
+    return { success: true, data: completeResult.data };
+    
+  } catch (error) {
+    console.error('分块上传失败:', error);
+    return { success: false, message: error.message || '上传失败' };
+  }
+}
+
+// 辅助函数实现...
+// (完整代码请参考项目中的实现)
+
+export default { multipartUpload, CHUNK_SIZE };
+```
+
+### 2. 页面使用示例
+
+```vue
+<template>
+  <view class="upload-container">
+    <button @click="chooseFile">选择文件</button>
+    <button @click="startUpload" :disabled="uploading">开始上传</button>
+    
+    <view v-if="uploading" class="progress">
+      <view class="progress-bar">
+        <view class="progress-fill" :style="{ width: progress + '%' }"></view>
+      </view>
+      <text>{{ progress }}%</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import multipartUpload from '@/utils/multipartUpload.js';
+
+export default {
+  data() {
+    return {
+      selectedFile: null,
+      uploading: false,
+      progress: 0
+    };
+  },
+  methods: {
+    chooseFile() {
+      // #ifdef MP-WEIXIN
+      wx.chooseMedia({
+        count: 1,
+        success: (res) => {
+          this.selectedFile = {
+            path: res.tempFiles[0].tempFilePath,
+            name: res.tempFiles[0].name,
+            size: res.tempFiles[0].size
+          };
+        }
+      });
+      // #endif
+      
+      // #ifdef H5
+      const input = document.createElement('input');
+      input.type = 'file';
+      input.onchange = (e) => {
+        const file = e.target.files[0];
+        if (file) {
+          this.selectedFile = {
+            path: file,
+            name: file.name,
+            size: file.size,
+            file: file
+          };
+        }
+      };
+      input.click();
+      // #endif
+    },
+    
+    async startUpload() {
+      if (!this.selectedFile) return;
+      
+      this.uploading = true;
+      this.progress = 0;
+      
+      const fileParam = this.selectedFile.file || this.selectedFile.path;
+      const result = await multipartUpload.multipartUpload(
+        fileParam,
+        123, // caseId
+        'document', // fileType
+        this.selectedFile.name,
+        {}, // isDelete
+        (progressInfo) => {
+          this.progress = progressInfo.progress;
+        }
+      );
+      
+      this.uploading = false;
+      
+      if (result.success) {
+        uni.showToast({ title: '上传成功', icon: 'success' });
+      } else {
+        uni.showToast({ title: result.message, icon: 'none' });
+      }
+    }
+  }
+};
+</script>
+```
+
+## 🔧 后端接口说明
+
+### 1. 初始化分块上传
+
+**接口:** `POST /wechat/file/multipart/init/{caseId}/{type}`
+
+**参数:** `originalName` (String)
+
+**返回:**
+```json
+{
+  "code": 200,
+  "data": {
+    "uploadId": "xxx",
+    "fileName": "123/document/uuid.ext",
+    "randomUUID": "uuid",
+    "ext": "jpg"
+  }
+}
+```
+
+### 2. 上传分块
+
+**接口:** `POST /wechat/file/multipart/upload`
+
+**参数(FormData):**
+- `fileName` (String)
+- `uploadId` (String)
+- `partNumber` (Integer)
+- `chunk` (File)
+
+**返回:**
+```json
+{
+  "code": 200,
+  "data": {
+    "partNumber": 1,
+    "eTag": "xxx"
+  }
+}
+```
+
+### 3. 完成分块上传
+
+**接口:** `POST /wechat/file/multipart/complete/{caseId}/{type}`
+
+**请求体(JSON):**
+```json
+{
+  "fileName": "123/document/uuid.ext",
+  "uploadId": "xxx",
+  "randomUUID": "uuid",
+  "ext": "jpg",
+  "originalName": "原始文件名.jpg",
+  "fileSize": 1024000,
+  "isDelete": {},
+  "partETagsList": [
+    {"partNumber": 1, "eTag": "xxx"},
+    {"partNumber": 2, "eTag": "yyy"}
+  ]
+}
+```
+
+## ⚙️ 配置参数
+
+- **分块大小:** 默认 2MB,可根据需要调整
+- **重试次数:** 默认每个分块最多重试 3 次
+
+## ⚠️ 注意事项
+
+1. 微信小程序需要将分块保存为临时文件
+2. 上传完成后记得清理临时文件
+3. 建议添加错误处理和重试机制
+4. 大文件建议使用更大的分块大小
+

BIN
lib/paas-sdk-3.1.4.jar


BIN
ngrok.exe


+ 230 - 0
ngrok诊断指南.md

@@ -0,0 +1,230 @@
+# ngrok 问题诊断指南
+
+## 问题现象
+**没有任何日志输出**,说明请求根本没有到达 Spring Boot 应用。
+
+## 快速诊断步骤
+
+### 1. 确认后端应用正在运行
+
+**Windows PowerShell 或 CMD:**
+```bash
+netstat -ano | findstr :8080
+```
+
+**应该看到类似输出:**
+```
+TCP    0.0.0.0:8080           0.0.0.0:0              LISTENING       12345
+```
+
+**如果没有输出** → 后端应用没有启动,请先启动应用!
+
+---
+
+### 2. 测试本地接口(绕过ngrok)
+
+**使用 curl 测试:**
+```bash
+curl -X POST "http://localhost:8080/api/wechat/login" -H "Content-Type: application/json" -d "{\"code\":\"test\"}"
+```
+
+**或者使用 Postman:**
+- URL: `http://localhost:8080/api/wechat/login`
+- Method: POST
+- Body: `{"code":"test"}`
+
+**如果本地测试也失败** → 问题在后端应用本身,不是ngrok的问题
+**如果本地测试成功** → 继续下一步,问题在ngrok配置
+
+---
+
+### 3. 检查ngrok配置
+
+#### 3.1 确认ngrok正在运行
+```bash
+netstat -ano | findstr :4040
+```
+
+#### 3.2 打开ngrok Web界面
+在浏览器打开:**http://localhost:4040**
+
+#### 3.3 检查ngrok转发配置
+在ngrok Web界面顶部,应该显示:
+```
+Forwarding  https://xxx.ngrok-free.dev -> http://localhost:8080
+```
+
+**关键检查点:**
+- ✅ 转发的目标端口必须是 **8080**
+- ✅ 转发的目标地址必须是 **http://localhost:8080** 或 **http://127.0.0.1:8080**
+- ❌ 如果显示的是其他端口(如8081)→ **ngrok配置错误!**
+
+---
+
+### 4. 检查ngrok启动命令
+
+**正确的ngrok启动命令应该是:**
+```bash
+ngrok http 8080
+```
+
+**或者指定hostname:**
+```bash
+ngrok http 8080 --hostname=your-domain.ngrok-free.dev
+```
+
+**常见错误:**
+- ❌ `ngrok http 8081` → 端口错误
+- ❌ `ngrok http localhost:8080` → 格式错误(不需要localhost)
+- ❌ `ngrok tcp 8080` → 协议错误(应该用http)
+
+---
+
+### 5. 检查ngrok Traffic Inspector
+
+在 **http://localhost:4040** 页面:
+
+#### 5.1 查看请求列表
+- 左侧应该显示所有通过ngrok的请求
+- 如果列表为空 → ngrok没有收到任何请求(前端问题)
+
+#### 5.2 点击一个请求查看详情
+查看以下信息:
+
+**Request URL:**
+- 应该是:`https://xxx.ngrok-free.dev/api/wechat/case/stepCode/dealings?stepCode=500`
+- 注意:URL中**必须包含** `/api`(这是你的context-path)
+
+**Response Status:**
+- `200` → 请求成功
+- `401` → 认证失败(Token问题)
+- `404` → 路径不存在(可能是URL错误)
+- `502 Bad Gateway` → **ngrok无法连接到后端!**
+- `504 Gateway Timeout` → 后端响应超时
+- 没有响应 → 请求可能被ngrok拦截
+
+**Response Body:**
+- 如果是HTML页面(包含"ngrok"字样)→ ngrok显示警告页面,需要点击"Visit Site"
+- 如果是JSON → 正常响应
+
+---
+
+### 6. ngrok常见问题及解决方案
+
+#### 问题1:502 Bad Gateway
+**原因:** ngrok无法连接到 `http://localhost:8080`
+
+**解决方案:**
+1. 确认后端应用正在运行(步骤1)
+2. 尝试使用 `127.0.0.1` 而不是 `localhost`:
+   ```bash
+   ngrok http 127.0.0.1:8080
+   ```
+3. 检查Windows防火墙是否阻止了8080端口
+
+#### 问题2:ngrok显示警告页面
+**原因:** ngrok免费版会显示警告页面
+
+**解决方案:**
+1. 在请求头中添加:`ngrok-skip-browser-warning: true`
+2. 或者在浏览器中点击"Visit Site"按钮
+3. 或者使用ngrok付费版本
+
+#### 问题3:请求被ngrok拦截或缓存
+**原因:** ngrok可能缓存了响应
+
+**解决方案:**
+1. 在请求头中添加:`Cache-Control: no-cache`
+2. 重启ngrok:按 `Ctrl+C` 停止,然后重新启动
+
+#### 问题4:URL路径错误
+**原因:** 前端请求的URL不正确
+
+**检查点:**
+- 后端context-path是 `/api`
+- 完整URL应该是:`https://xxx.ngrok-free.dev/api/wechat/...`
+- **注意:** URL中**必须包含** `/api`
+
+**常见错误:**
+- ❌ `https://xxx.ngrok-free.dev/wechat/...` → 缺少 `/api`
+- ✅ `https://xxx.ngrok-free.dev/api/wechat/...` → 正确
+
+---
+
+### 7. 完整测试流程
+
+#### 步骤1:启动后端应用
+```bash
+# 在IDEA中运行,或使用Maven
+mvn spring-boot:run
+```
+
+**检查启动日志:**
+- 应该看到:`Tomcat started on port(s): 8080 (http)`
+- 应该看到:`RequestLoggingFilter 初始化完成`
+
+#### 步骤2:启动ngrok
+```bash
+ngrok http 8080
+```
+
+**检查ngrok输出:**
+- 应该显示:`Forwarding  https://xxx.ngrok-free.dev -> http://localhost:8080`
+
+#### 步骤3:测试本地接口
+```bash
+curl -X GET "http://localhost:8080/api/wechat/case/stepCode/dealings?stepCode=500" -H "Authorization: HDU your-token"
+```
+
+**如果成功** → 后端正常,问题在ngrok
+**如果失败** → 后端有问题,检查后端日志
+
+#### 步骤4:测试ngrok接口
+```bash
+curl -X GET "https://xxx.ngrok-free.dev/api/wechat/case/stepCode/dealings?stepCode=500" -H "Authorization: HDU your-token" -H "ngrok-skip-browser-warning: true"
+```
+
+**检查:**
+- 后端日志是否有输出?
+- ngrok Traffic Inspector中是否有请求记录?
+
+---
+
+## 最可能的原因
+
+根据你的情况(完全没有日志),最可能的原因是:
+
+1. **ngrok配置错误** - 转发的端口不对(如8081而不是8080)
+2. **ngrok没有正确启动** - ngrok进程异常
+3. **URL路径错误** - 前端请求的URL缺少 `/api`
+4. **ngrok无法连接后端** - 防火墙或网络问题
+
+---
+
+## 立即执行的检查
+
+1. ✅ 运行 `诊断脚本.bat`(已创建)
+2. ✅ 打开 http://localhost:4040 查看ngrok状态
+3. ✅ 检查ngrok启动命令:`ngrok http 8080`
+4. ✅ 检查前端请求的URL是否包含 `/api`
+5. ✅ 查看ngrok Traffic Inspector中的请求详情
+
+---
+
+## 如果仍然无法解决
+
+请提供以下信息:
+
+1. **后端启动日志**(特别是端口监听信息)
+2. **ngrok启动输出**(Forwarding那一行)
+3. **ngrok Traffic Inspector截图**(请求详情页面)
+4. **前端请求代码**(URL和请求头)
+5. **运行 `诊断脚本.bat` 的输出**
+
+
+
+
+
+
+
+

+ 312 - 0
ngrok问题排查指南.md

@@ -0,0 +1,312 @@
+# ngrok内网穿透请求无法到达后端问题排查指南
+
+## 问题描述
+- ✅ 请求能到达ngrok(在ngrok Traffic Inspector中可以看到)
+- ✅ 登录接口 `/api/wechat/login` 可以正常访问
+- ❌ 其他接口如 `/api/wechat/case/stepCode/dealings` 无法到达后端
+
+## 可能原因分析
+
+### 1. **JWT Token认证问题(最可能)**
+
+**问题**:除了登录接口外,所有 `/wechat/**` 路径都需要JWT token认证。
+
+**检查方法**:
+- 查看后端日志,看是否有 "jwt校验" 相关的日志
+- 如果看到 "jwt校验失败" 或返回401状态码,说明请求被拦截器拦截了
+
+**解决方案**:
+- 确保请求头中包含 `Authorization` 字段
+- Token值格式应该是:`HDU xxxxxx`(根据JWT配置)
+
+### 2. **ngrok路径配置问题**
+
+**问题**:ngrok可能配置了路径重写或只转发特定路径。
+
+**检查ngrok配置**:
+```bash
+# 检查ngrok配置文件(通常在用户目录下的.ngrok2/ngrok.yml)
+# 或者查看启动ngrok时的命令参数
+```
+
+**正确的ngrok配置应该是**:
+```yaml
+# ngrok.yml 示例
+tunnels:
+  backend:
+    addr: 8080  # 后端服务端口
+    proto: http
+    # 不要设置 bind_tls 或 path 参数,除非你确定需要
+```
+
+**启动命令应该是**:
+```bash
+ngrok http 8080
+# 或者
+ngrok http localhost:8080
+```
+
+**错误的配置示例**(会导致路径问题):
+```bash
+# ❌ 错误:如果设置了路径重写
+ngrok http 8080 --host-header=rewrite
+# 或者配置了 path 参数
+```
+
+### 3. **后端context-path配置**
+
+**当前配置**:
+- `context-path: /api`(在application-dev.yaml中)
+- 这意味着所有接口的实际路径都是 `/api/xxx`
+
+**完整路径映射**:
+- 登录接口:`http://ngrok域名/api/wechat/login` ✅
+- 问题接口:`http://ngrok域名/api/wechat/case/stepCode/dealings` ✅
+
+### 4. **请求路径不匹配**
+
+**检查点**:
+1. 前端请求的完整URL是什么?
+2. ngrok转发的目标地址是什么?
+
+**测试方法**:
+```bash
+# 直接测试本地后端(绕过ngrok)
+curl -X GET "http://localhost:8080/api/wechat/case/stepCode/dealings?stepCode=500" \
+  -H "Authorization: HDU your-token-here"
+
+# 测试ngrok转发
+curl -X GET "http://your-ngrok-domain.ngrok-free.dev/api/wechat/case/stepCode/dealings?stepCode=500" \
+  -H "Authorization: HDU your-token-here"
+```
+
+## 排查步骤
+
+### 步骤1:检查后端日志
+查看后端控制台日志,看是否有:
+- "拦截器拦截到请求" 日志 → 说明请求到达了后端
+- "jwt校验" 日志 → 说明进入了拦截器
+- 401错误 → 说明token验证失败
+
+### 步骤2:检查请求头
+确保请求包含:
+```
+Authorization: HDU xxxxxx
+Content-Type: application/json
+```
+
+### 步骤3:测试本地接口
+绕过ngrok,直接访问本地接口:
+```bash
+# 使用Postman或curl测试
+GET http://localhost:8080/api/wechat/case/stepCode/dealings?stepCode=500
+Headers:
+  Authorization: HDU your-token-here
+```
+
+### 步骤4:检查ngrok配置
+1. 查看ngrok启动命令
+2. 检查是否有路径重写配置
+3. 确认ngrok转发到正确的端口(8080)
+
+### 步骤5:检查ngrok Traffic Inspector
+在ngrok的Traffic Inspector中:
+1. 查看请求的完整路径
+2. 查看请求头(特别是Authorization)
+3. 查看响应状态码(如果是401,说明是认证问题)
+
+## 常见解决方案
+
+### 方案1:添加请求日志(已添加)
+已在拦截器中添加了详细的日志,重启后端后可以看到:
+- 请求URI
+- 请求方法
+- Token是否存在
+
+### 方案2:临时禁用拦截器(仅用于测试)
+如果需要测试,可以临时注释掉拦截器配置:
+
+```java
+// 在 WebMvcConfiguration.java 中临时注释
+// registry.addInterceptor(jwtTokenUserInterceptor)
+//         .addPathPatterns("/wechat/**")
+//         .excludePathPatterns("/wechat/login")
+//         .excludePathPatterns("/wechat/loginTest","/wechat/esign/callback")
+//         .excludePathPatterns("/wechat/file/download/**");
+```
+
+**注意**:测试完后记得恢复!
+
+### 方案3:检查ngrok的请求转发
+如果ngrok显示请求成功(200),但后端没有日志,可能是:
+- ngrok缓存了响应
+- ngrok配置了响应缓存
+- 网络问题导致请求未到达后端
+
+## 新增诊断工具
+
+### 1. RequestLoggingFilter(已添加)
+已在项目中添加了 `RequestLoggingFilter`,它会:
+- **在所有拦截器之前执行**,记录所有进入应用的请求
+- 记录请求URI、方法、客户端信息、请求头、请求参数
+- 如果这个Filter都没有日志输出,说明**请求根本没有到达Spring Boot应用**
+
+### 2. 拦截器日志增强(已添加)
+拦截器现在会记录:
+- 请求URI和HTTP方法
+- Token是否存在
+- JWT校验过程
+
+## 诊断步骤(按顺序执行)
+
+### 步骤1:确认后端应用正在运行
+```bash
+# Windows PowerShell
+netstat -ano | findstr :8080
+
+# 应该看到类似输出:
+# TCP    0.0.0.0:8080           0.0.0.0:0              LISTENING       12345
+```
+
+如果看不到8080端口在监听,说明后端应用没有启动或启动失败。
+
+### 步骤2:测试本地接口(绕过ngrok)
+使用Postman或curl直接测试本地接口:
+```bash
+# 测试登录接口(不需要token)
+curl -X POST "http://localhost:8080/api/wechat/login" \
+  -H "Content-Type: application/json" \
+  -d "{\"code\":\"test\"}"
+
+# 测试问题接口(需要token)
+curl -X GET "http://localhost:8080/api/wechat/case/stepCode/dealings?stepCode=500" \
+  -H "Authorization: HDU your-token-here"
+```
+
+**如果本地测试也失败**,说明是后端应用本身的问题,不是ngrok的问题。
+
+### 步骤3:检查ngrok配置和状态
+```bash
+# 检查ngrok是否正在运行
+# 在ngrok的web界面查看:http://localhost:4040
+
+# 检查ngrok转发的目标
+# 应该显示:Forwarding  https://xxx.ngrok-free.dev -> http://localhost:8080
+```
+
+**重要检查点**:
+- ngrok转发的目标端口是否正确(应该是8080)
+- ngrok转发的目标地址是否正确(应该是 `http://localhost:8080` 或 `http://127.0.0.1:8080`)
+
+### 步骤4:重启后端并查看日志
+1. **重启后端应用**
+2. **查看启动日志**,确认:
+   - "RequestLoggingFilter 初始化完成" - Filter已加载
+   - "开始注册自定义拦截器..." - 拦截器已注册
+   - 应用监听在8080端口
+
+3. **发送测试请求**(通过ngrok)
+4. **查看日志输出**:
+   - 如果看到 "========== 请求进入应用 ==========" → 请求到达了应用,继续查看后续日志
+   - 如果**完全没有日志** → 请求没有到达应用,问题在ngrok或网络层面
+
+### 步骤5:检查ngrok Traffic Inspector
+在 `http://localhost:4040` 查看:
+1. **请求是否出现在列表中**?
+   - 如果不在列表中 → ngrok没有收到请求(前端问题)
+   - 如果在列表中 → 继续下一步
+
+2. **查看请求详情**:
+   - **Request URL**: 完整的请求URL是什么?
+   - **Request Headers**: 是否包含 `Authorization` 头?
+   - **Response Status**: 返回的状态码是什么?
+     - `200` → 请求成功,检查响应内容
+     - `401` → 认证失败,检查token
+     - `404` → 路径不存在,检查URL
+     - `502 Bad Gateway` → ngrok无法连接到后端
+     - `504 Gateway Timeout` → 后端响应超时
+     - 没有响应 → 请求可能被ngrok拦截或缓存
+
+3. **查看Response**:
+   - 如果有响应内容,查看是什么
+   - 如果是HTML页面(ngrok警告页),说明需要点击"Visit Site"按钮
+
+### 步骤6:检查ngrok可能的拦截
+ngrok免费版可能会:
+1. **显示警告页面**:首次访问需要点击"Visit Site"按钮
+2. **限制请求频率**:频繁请求可能被限流
+3. **缓存响应**:可能返回缓存的响应而不是转发到后端
+
+**解决方案**:
+- 在请求头中添加:`ngrok-skip-browser-warning: true`
+- 或者使用ngrok的付费版本
+
+### 步骤7:检查防火墙和网络
+```bash
+# Windows防火墙可能阻止了8080端口
+# 检查Windows防火墙设置,确保8080端口允许入站连接
+```
+
+## 常见问题及解决方案
+
+### 问题1:完全没有日志输出
+**可能原因**:
+- ngrok没有正确转发请求
+- 后端应用没有启动
+- 防火墙阻止了连接
+- ngrok配置错误
+
+**解决方案**:
+1. 确认后端应用正在运行(步骤1)
+2. 确认ngrok配置正确(步骤3)
+3. 测试本地接口(步骤2)
+4. 检查防火墙设置
+
+### 问题2:看到Filter日志,但没有拦截器日志
+**可能原因**:
+- 请求路径不匹配拦截器配置
+- 请求被其他Filter拦截并返回
+
+**解决方案**:
+- 检查请求URI是否匹配 `/wechat/**` 模式
+- 检查是否有其他Filter或配置阻止了请求
+
+### 问题3:看到拦截器日志,但返回401
+**可能原因**:
+- Token不存在或格式错误
+- Token已过期
+- Token验证失败
+
+**解决方案**:
+- 检查请求头中的 `Authorization` 值
+- 确认Token格式:`HDU xxxxxx`(注意有空格)
+- 重新登录获取新Token
+
+### 问题4:ngrok显示502 Bad Gateway
+**可能原因**:
+- 后端应用没有运行
+- ngrok无法连接到localhost:8080
+- 端口被占用
+
+**解决方案**:
+- 确认后端应用正在运行
+- 尝试使用 `127.0.0.1:8080` 而不是 `localhost:8080`
+- 检查端口是否被占用
+
+## 下一步操作
+
+1. **重启后端服务**,查看新增的日志输出
+2. **执行步骤1-7的诊断**,记录结果
+3. **根据日志输出判断问题位置**:
+   - 没有Filter日志 → ngrok或网络问题
+   - 有Filter日志但没有拦截器日志 → 路径匹配问题
+   - 有拦截器日志但401 → Token问题
+   - 有拦截器日志且通过 → 检查Controller和业务逻辑
+
+## 联系信息
+如果问题仍未解决,请提供:
+1. **后端完整日志**(从启动到请求的完整日志)
+2. **ngrok Traffic Inspector截图**(请求详情页面)
+3. **前端请求代码**(特别是URL和请求头设置)
+4. **步骤1-7的诊断结果**
+

+ 198 - 138
pom.xml

@@ -1,58 +1,91 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
     <modelVersion>4.0.0</modelVersion>
+
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.3.3.RELEASE</version>
-        <relativePath/> <!-- lookup parent from repository -->
+        <relativePath/>
     </parent>
+
     <groupId>com.loan</groupId>
     <artifactId>system</artifactId>
     <version>0.0.1-SNAPSHOT</version>
-    <name>system</name>
-    <description>Demo project for Spring Boot</description>
 
     <properties>
-<!--        1.8-->
         <java.version>8</java.version>
         <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
         <org.lombok.version>1.18.20</org.lombok.version>
+        <commons-io.version>2.15.1</commons-io.version>
     </properties>
 
+    <!-- 配置 e签宝 Maven 仓库 -->
+    <repositories>
+        <repository>
+            <id>esign-public</id>
+            <name>Esign Public Repository</name>
+            <url>https://repo.esign.cn/repository/maven-public/</url>
+            <releases>
+                <enabled>true</enabled>
+                <updatePolicy>always</updatePolicy>
+            </releases>
+            <snapshots>
+                <enabled>true</enabled>
+                <updatePolicy>always</updatePolicy>
+            </snapshots>
+        </repository>
+    </repositories>
+
+    <!-- 统一管理 commons-io,避免所有冲突 -->
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>commons-io</groupId>
+                <artifactId>commons-io</artifactId>
+                <version>${commons-io.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
     <dependencies>
+        <!-- json -->
         <dependency>
             <groupId>org.json</groupId>
             <artifactId>json</artifactId>
-            <version>20231013</version> <!-- 使用最新版本 -->
+            <version>20231013</version>
         </dependency>
+
+        <!-- hutool -->
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
             <version>5.8.11</version>
         </dependency>
+
+        <!-- httpclient -->
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
             <version>4.5.13</version>
         </dependency>
+
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
             <version>1.2.83</version>
         </dependency>
+
         <dependency>
             <groupId>com.squareup.okhttp3</groupId>
             <artifactId>okhttp</artifactId>
             <version>4.10.0</version>
         </dependency>
 
-        <dependency>
-            <groupId>javax.annotation</groupId>
-            <artifactId>jsr250-api</artifactId>
-            <version>1.0</version>
-        </dependency>
+        <!-- spring -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-cache</artifactId>
@@ -75,49 +108,16 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
-        <dependency>
-            <groupId>cn.hutool</groupId>
-            <artifactId>hutool-all</artifactId>
-            <version>5.8.11</version>
-        </dependency>
-
-        <dependency>
-            <groupId>com.squareup.okhttp3</groupId>
-            <artifactId>okhttp</artifactId>
-            <version>4.10.0</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.json</groupId>
-            <artifactId>json</artifactId>
-            <version>20231013</version> <!-- 使用最新版本 -->
-        </dependency>
-
-
-        <!-- Apache POI 最新稳定版 -->
-<!--        <dependency>-->
-<!--            <groupId>org.apache.poi</groupId>-->
-<!--            <artifactId>poi</artifactId>-->
-<!--            <version>3.16</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>org.apache.poi</groupId>-->
-<!--            <artifactId>poi-ooxml</artifactId>-->
-<!--            <version>3.16</version>-->
-<!--        </dependency>-->
-
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-            <version>4.5.13</version>
-        </dependency>
 
+        <!-- lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>${org.lombok.version}</version>
             <optional>true</optional>
         </dependency>
+
+        <!-- test -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
@@ -129,174 +129,232 @@
                 </exclusion>
             </exclusions>
         </dependency>
+
+        <!-- aop -->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-aspects</artifactId>
             <version>5.3.23</version>
-<!--            <scope>test</scope>-->
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-test</artifactId>
-            <scope>test</scope>
         </dependency>
+
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.alibaba</groupId>
-            <artifactId>fastjson</artifactId>
-            <version>1.2.62</version>
         </dependency>
+
+        <!-- jwt -->
         <dependency>
             <groupId>io.jsonwebtoken</groupId>
             <artifactId>jjwt</artifactId>
             <version>0.9.1</version>
         </dependency>
+
+        <!-- mapstruct -->
         <dependency>
             <groupId>org.mapstruct</groupId>
             <artifactId>mapstruct</artifactId>
             <version>${org.mapstruct.version}</version>
         </dependency>
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>servlet-api</artifactId>
-            <version>2.5</version>
-        </dependency>
+
+        <!-- fileupload(必须排除旧commons-io) -->
         <dependency>
             <groupId>commons-fileupload</groupId>
             <artifactId>commons-fileupload</artifactId>
             <version>1.3.3</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
+
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
             <version>3.10</version>
         </dependency>
+
+        <!-- 强制使用唯一版本 -->
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <version>2.4</version>
         </dependency>
+
+        <!-- thumbnailator -->
         <dependency>
             <groupId>net.coobird</groupId>
             <artifactId>thumbnailator</artifactId>
             <version>0.4.12</version>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-aop</artifactId>
         </dependency>
-        <!--集成redis-->
+
+        <!-- redis -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-redis</artifactId>
             <version>1.4.1.RELEASE</version>
         </dependency>
+
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
         </dependency>
-        <!-- 操作 Excel2007以后 -->
-        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
-<!--        <dependency>-->
-<!--            <groupId>org.apache.poi</groupId>-->
-<!--            <artifactId>poi-ooxml</artifactId>-->
-<!--            <version>4.1.0</version>-->
-<!--        </dependency>-->
-        <!-- 操作 Excel2003-->
-        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
-<!--        <dependency>-->
-<!--            <groupId>org.apache.poi</groupId>-->
-<!--            <artifactId>poi</artifactId>-->
-<!--            <version>4.1.0</version>-->
-<!--        </dependency>-->
-        <!-- 生成 WORD 文档所需, 说明文档网址为:http://deepoove.com/poi-tl/-->
+
+        <!-- poi-tl(排除 commons-io) -->
         <dependency>
             <groupId>com.deepoove</groupId>
             <artifactId>poi-tl</artifactId>
             <version>1.9.1</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- Apache POI(排除 commons-io) -->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>4.1.2</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>4.1.2</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
-        <!--        WechatUtils 需要-->
-        <!-- https://mvnrepository.com/artifact/org.codehaus.xfire/xfire-all -->
+        <!-- xdocreport(可能引旧版本,也排除) -->
+        <dependency>
+            <groupId>fr.opensagres.xdocreport</groupId>
+            <artifactId>fr.opensagres.poi.xwpf.converter.core</artifactId>
+            <version>2.0.2</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>fr.opensagres.xdocreport</groupId>
+            <artifactId>fr.opensagres.poi.xwpf.converter.pdf</artifactId>
+            <version>2.0.2</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- xfire(非常旧,同样必须排除 commons-io) -->
         <dependency>
             <groupId>org.codehaus.xfire</groupId>
             <artifactId>xfire-all</artifactId>
             <version>1.2.5</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
-
-
+        <!-- 邮件 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-mail</artifactId>
         </dependency>
 
+        <!-- knife4j -->
         <dependency>
             <groupId>com.github.xiaoymin</groupId>
             <artifactId>knife4j-spring-boot-starter</artifactId>
             <version>3.0.2</version>
         </dependency>
 
-        <!-- swagger -->
-<!--        <dependency>-->
-<!--            <groupId>io.springfox</groupId>-->
-<!--            <artifactId>springfox-swagger-ui</artifactId>-->
-<!--            <version>2.9.2</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>io.springfox</groupId>-->
-<!--            <artifactId>springfox-swagger2</artifactId>-->
-<!--            <version>2.9.2</version>-->
-<!--        </dependency>-->
-<!--        &lt;!&ndash;  解决 Illegal DefaultValue null for parameter type integer    异常  &ndash;&gt;-->
-<!--        <dependency>-->
-<!--            <groupId>io.swagger</groupId>-->
-<!--            <artifactId>swagger-annotations</artifactId>-->
-<!--            <version>1.5.21</version>-->
-<!--        </dependency>-->
-<!--        <dependency>-->
-<!--            <groupId>io.swagger</groupId>-->
-<!--            <artifactId>swagger-models</artifactId>-->
-<!--            <version>1.5.21</version>-->
-<!--        </dependency>-->
         <dependency>
-            <groupId>org.mybatis</groupId>
-            <artifactId>mybatis</artifactId>
-            <version>3.5.10</version>
+            <groupId>org.jodconverter</groupId>
+            <artifactId>jodconverter-local</artifactId>
+            <version>4.4.2</version>
         </dependency>
         <dependency>
-            <groupId>com.github.pagehelper</groupId>
-            <artifactId>pagehelper</artifactId>
-            <version>5.2.1</version>
-        </dependency>
-        <dependency>
-            <groupId>org.mybatis</groupId>
-            <artifactId>mybatis-spring</artifactId>
-            <version>2.0.7</version>
+            <groupId>org.jodconverter</groupId>
+            <artifactId>jodconverter-remote</artifactId>
+            <version>4.4.2</version>
         </dependency>
         <dependency>
             <groupId>javax.activation</groupId>
             <artifactId>activation</artifactId>
             <version>1.1.1</version>
         </dependency>
-        <!-- https://mvnrepository.com/artifact/javax.activation/activation -->
+
+        <!-- e签宝SDK 
+             如果Maven仓库无法访问,请使用以下方式之一:
+             1. 手动安装到本地仓库:
+                mvn install:install-file -Dfile=esign-sdk-java-3.6.0.jar 
+                -DgroupId=com.esign -DartifactId=esign-sdk-java -Dversion=3.6.0 -Dpackaging=jar
+             2. 或者使用system scope指向本地jar文件(取消下面的注释):
+        -->
 <!--        <dependency>-->
-<!--            <groupId>javax.activation</groupId>-->
-<!--            <artifactId>activation</artifactId>-->
-<!--            <version>1.0.2</version>-->
+<!--            <groupId>com.esign</groupId>-->
+<!--            <artifactId>esign-sdk-java</artifactId>-->
+<!--            <version>3.6.0</version>-->
 <!--        </dependency>-->
+        <dependency>
+            <groupId>esign-cn</groupId>
+            <artifactId>paas-sdk</artifactId>
+            <version>3.1.4</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/lib/paas-sdk-3.1.4.jar</systemPath>
+        </dependency>
+        <!--
+        备用方案:如果Maven仓库无法访问,取消下面注释并注释上面的依赖
+        <dependency>
+            <groupId>com.esign</groupId>
+            <artifactId>esign-sdk-java</artifactId>
+            <version>3.6.0</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/lib/esign-sdk-java-3.6.0.jar</systemPath>
+        </dependency>
+        -->
 
-
-
-
+        <!-- 阿里云OSS SDK -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.10</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
+            <scope>compile</scope>
+            <version>2.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.10.2</version>
+        </dependency>
 
     </dependencies>
 
@@ -306,16 +364,18 @@
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
+
             <plugin>
-                    <groupId>org.mybatis.generator</groupId>
-                    <artifactId>mybatis-generator-maven-plugin</artifactId>
-                    <version>1.3.6</version>
-                    <configuration>
-                        <configurationFile>GeneratorMapper.xml</configurationFile>
-                        <verbose>true</verbose>
-                        <overwrite>true</overwrite>
-                    </configuration>
+                <groupId>org.mybatis.generator</groupId>
+                <artifactId>mybatis-generator-maven-plugin</artifactId>
+                <version>1.3.6</version>
+                <configuration>
+                    <configurationFile>GeneratorMapper.xml</configurationFile>
+                    <verbose>true</verbose>
+                    <overwrite>true</overwrite>
+                </configuration>
             </plugin>
+
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
@@ -340,4 +400,4 @@
         </plugins>
     </build>
 
-</project>
+</project>

+ 10 - 0
src/main/java/com/loan/system/LoanSystemApplication.java

@@ -1,5 +1,6 @@
 package com.loan.system;
 
+import com.loan.system.utils.LibreOfficePdfUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.BeanFactory;
 import org.springframework.beans.factory.ListableBeanFactory;
@@ -11,6 +12,9 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
 //@MapperScan(value = {"com/hdu/maintenance/mapper"})
 @SpringBootApplication(exclude = SecurityAutoConfiguration.class)
 @EnableCaching
@@ -24,4 +28,10 @@ public class LoanSystemApplication {
 
     }
 
+
+    @PreDestroy
+    public void stopLibreOffice() throws Exception {
+        LibreOfficePdfUtil.stopOffice();
+    }
+
 }

+ 7 - 0
src/main/java/com/loan/system/config/FileUploadConfig.java

@@ -17,10 +17,17 @@ public class FileUploadConfig {
     @Value("${upload.extensions}")
     private String allowedExtensions;
 
+    @Value("${upload.templatePath}")
+    private String contractPath;
+
     public String getUploadDir() {
         return uploadDir.endsWith("/")? uploadDir : uploadDir + "/";
     }
 
+    public String getContractPath() {
+        return contractPath.endsWith("/")? contractPath : contractPath + "/";
+    }
+
     /** 把 yml 中的扩展名字符串解析成 Set<String> */
     public Set<String> getAllowedExtensions() {
         return Arrays.stream(allowedExtensions.split(","))

+ 44 - 0
src/main/java/com/loan/system/config/OssConfig.java

@@ -0,0 +1,44 @@
+package com.loan.system.config;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class OssConfig {
+
+    @Value("${oss.endpoint}")
+    private String endpoint;
+
+    @Value("${oss.access-key-id}")
+    private String accessKeyId;
+
+    @Value("${oss.access-key-secret}")
+    private String accessKeySecret;
+
+    @Value("${oss.bucket-name}")
+    private String bucketName;
+
+    @Bean
+    public OSS ossClient() {
+        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
+    }
+
+    public String getEndpoint() {
+        return endpoint;
+    }
+
+    public String getAccessKeyId() {
+        return accessKeyId;
+    }
+
+    public String getAccessKeySecret() {
+        return accessKeySecret;
+    }
+
+    public String getBucketName() {
+        return bucketName;
+    }
+}

+ 6 - 3
src/main/java/com/loan/system/config/WebMvcConfiguration.java

@@ -7,8 +7,10 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.http.converter.HttpMessageConverter;
 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@@ -41,14 +43,14 @@ public class WebMvcConfiguration extends WebMvcConfigurationSupport {
         log.info("开始注册自定义拦截器...");
         registry.addInterceptor(jwtTokenAdminInterceptor)
                 .addPathPatterns("/admin/**")
-                .excludePathPatterns("/admin/login");
+                .excludePathPatterns("/admin/login","/admin/esign/callback");
 
         registry.addInterceptor(jwtTokenUserInterceptor)
                 .addPathPatterns("/wechat/**")
                 .excludePathPatterns("/wechat/login")
-                .excludePathPatterns("/wechat/loginTest");
+                .excludePathPatterns("/wechat/loginTest","/wechat/esign/callback")
+                .excludePathPatterns("/wechat/file/download/**");
     }
-
     /*
      * 通过knife4j生成接口文档
      * 1.导入 knife4j 的依赖
@@ -102,6 +104,7 @@ public class WebMvcConfiguration extends WebMvcConfigurationSupport {
         log.info("设置静态资源映射");
         registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
         registry.addResourceHandler("/wechat/files/**").addResourceLocations("file:/app/upload/");
+        // 将文件请求重定向到OSS
         //registry.addResourceHandler("/wechat/files/**").addResourceLocations("classpath:/file_store");
         registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
     }

+ 20 - 0
src/main/java/com/loan/system/constant/ContractConstant.java

@@ -0,0 +1,20 @@
+package com.loan.system.constant;
+
+
+public class ContractConstant {
+    // 出借人/抵押权人/合同落款甲方
+    public static final String LENDER = "浙江宝路通典当有限责任公司";
+    // 营业执照
+    public static final String BUSINESS_LICENSE = "9133000059854129XJ";
+    // 联系地址
+    public static final String CONTACT_ADDRESS = "杭州市上城区万潮星汇大厦3号楼90";
+    // 签订地址
+    public static final String SIGNING_ADDRESS = "杭州";
+    //合同利率与月综服务费
+    public static final String CONTRACT_RATE = "5.4%";
+    //至少天数
+    public static final Integer LEAST_DAYS = 3;
+
+    public static final String PAGE_1 = "二";
+
+}

+ 6 - 0
src/main/java/com/loan/system/constant/ContractTemplateIdConstant.java

@@ -0,0 +1,6 @@
+package com.loan.system.constant;
+
+public class ContractTemplateIdConstant {
+    private static final String templateId_1 = "b9b739793e3a4154905a6c3d3aab2521";//抵押合同
+    private static final String templateId_2 = "1234567890";//质押合同
+}

+ 0 - 1
src/main/java/com/loan/system/constant/JwtClaimsConstant.java

@@ -4,7 +4,6 @@ public class JwtClaimsConstant {
 
     public static final String EMP_ID = "empId";
     public static final String USER_ID = "userId";
-    public static final String CUSTOMER_ID = "customerId";
     public static final String PHONE = "phone";
     public static final String OPENID = "openid";
     public static final String isCustomer = "isCustomer";

+ 2 - 20
src/main/java/com/loan/system/constant/MessageConstant.java

@@ -5,24 +5,6 @@ package com.loan.system.constant;
  */
 public class MessageConstant {
 
-    public static final String PASSWORD_ERROR = "密码错误";
-    public static final String ACCOUNT_NOT_FOUND = "账号不存在";
-    public static final String ACCOUNT_LOCKED = "账号被锁定";
-    public  static final  String ALREADY_EXISTS="已存在";
-    public static final String UNKNOWN_ERROR = "未知错误";
-    public static final String USER_NOT_LOGIN = "用户未登录";
-    public static final String CATEGORY_BE_RELATED_BY_SETMEAL = "当前分类关联了套餐,不能删除";
-    public static final String CATEGORY_BE_RELATED_BY_DISH = "当前分类关联了菜品,不能删除";
-    public static final String SHOPPING_CART_IS_NULL = "购物车数据为空,不能下单";
-    public static final String ADDRESS_BOOK_IS_NULL = "用户地址为空,不能下单";
-    public static final String LOGIN_FAILED = "登录失败";
-    public static final String UPLOAD_FAILED = "文件上传失败";
-    public static final String SETMEAL_ENABLE_FAILED = "套餐内包含未启售菜品,无法启售";
-    public static final String PASSWORD_EDIT_FAILED = "密码修改失败";
-    public static final String DISH_ON_SALE = "起售中的菜品不能删除";
-    public static final String SETMEAL_ON_SALE = "起售中的套餐不能删除";
-    public static final String DISH_BE_RELATED_BY_SETMEAL = "当前菜品关联了套餐,不能删除";
-    public static final String ORDER_STATUS_ERROR = "订单状态错误";
-    public static final String ORDER_NOT_FOUND = "订单不存在";
-
+    public static final String TEMPLATE_ID_ONE = "XyQm6-LgN2k3uJLlldCH9ipaaeNJXP7e3yb_QebIMJ8";
+    public static final String TEMPLATE_ID_TWO = "XyQm6-LgN2k3uJLlldCH9rxFDKchGQBIvpCioPSCSJo";
 }

+ 38 - 33
src/main/java/com/loan/system/controller/admin/AdminController.java

@@ -13,6 +13,7 @@ import com.loan.system.properties.JwtProperties;
 import com.loan.system.repository.RoleRepository;
 import com.loan.system.service.CustomerService;
 import com.loan.system.service.Impl.UserServiceImpl;
+import com.loan.system.service.RecommenderService;
 import com.loan.system.service.UserService;
 import com.loan.system.service.WxService;
 import com.loan.system.utils.JwtUtil;
@@ -39,10 +40,8 @@ import java.util.Map;
 public class AdminController {//包含内部人员、外部人员
     @Autowired
     private UserService userService;
-
     @Autowired
-    private RoleRepository roleRepository;
-
+    private RecommenderService recommenderService;
     @Autowired
     private CustomerService customerService;
 
@@ -65,9 +64,24 @@ public class AdminController {//包含内部人员、外部人员
         else
             userLoginVO=userService.codeLogin(userLoginDTO.getTel(),userLoginDTO.getValidCode());
 
+
         return ResultUtil.success("success", userLoginVO);
     }
 
+    @GetMapping("/users")
+    @ApiOperation("查询所有用户")
+    public Result findAllUsers(@RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "10") Integer pageSize,@RequestParam(required = false) Boolean isDelete){
+        return ResultUtil.success("success", userService.getAllUsers(pageNum,pageSize,isDelete));
+    }
+
+    @PostMapping
+    @ApiOperation("新增用户")
+    @PreAuthorize("@pms.hasRole('SYSTEM_ADMIN')")
+    public Result addUser(@RequestBody UserDTO user){
+        userService.addUser(user);
+
+        return ResultUtil.success("success");
+    }
 
     @PutMapping("/role/{id}")
     @ApiOperation("更新用户角色")
@@ -81,12 +95,11 @@ public class AdminController {//包含内部人员、外部人员
         return ResultUtil.success("success");
     }
 
-    @PostMapping
-    @ApiOperation("新增用户")
-    @PreAuthorize("@pms.hasRole('SYSTEM_ADMIN')")
-    public Result addUser(@RequestBody UserDTO user){
-        userService.addUser(user);
-
+    @PutMapping("/{id}")
+    @ApiOperation("更新用户信息")
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN,APPROVER,BACK_OFFICE,LEAD_SALES,ASSIST_SALES,FINANCE')")
+    public Result updateUser(@PathVariable("id") Long id,@RequestBody UserDTO user){
+        userService.updateUserById(id,user);
         return ResultUtil.success("success");
     }
 
@@ -104,22 +117,26 @@ public class AdminController {//包含内部人员、外部人员
         return ResultUtil.success("success");
     }
 
-    @PutMapping("/{id}")
-    @ApiOperation("更新用户信息")
-    @PreAuthorize("@pms.hasRole('SYSTEM_ADMIN')")
-    public Result updateUser(@PathVariable("id") Long id,@RequestBody UserDTO user){
-        userService.updateUserById(id,user);
+    @PostMapping("/addRecommender")
+    @ApiOperation("绑定用户与推荐人")
+    public Result addRecommender( @RequestParam Long recommenderId , @RequestBody UserDTO userDTO){
+        if (ObjectUtils.isEmpty(userDTO))
+            throw new DescribeException(ExceptionEnum.INPUT_ERROR);
+        if (recommenderService.getRecommenderById(recommenderId)== null)
+            throw new DescribeException(ExceptionEnum.RECOMMENDER_NOT_EXIST);
+
+        userService.bindUserAndRecommender(recommenderId, userDTO);
         return ResultUtil.success("success");
     }
 
 
-    @PostMapping("/role")
-    @ApiOperation("添加用户角色")
-    @PreAuthorize("@pms.hasRole('SYSTEM_ADMIN')")
-    public Result addRole(@RequestParam Long userId, @RequestBody Role role){
-        roleRepository.save(role);
-        return ResultUtil.success("success");
-    }
+//    @PostMapping("/role")
+//    @ApiOperation("添加用户角色")
+//    @PreAuthorize("@pms.hasRole('SYSTEM_ADMIN')")
+//    public Result addRole(@RequestParam Long userId, @RequestBody Role role){
+//        roleRepository.save(role);
+//        return ResultUtil.success("success");
+//    }
 
     @GetMapping("/customers")
     @ApiOperation("查询所有客户")
@@ -140,18 +157,6 @@ public class AdminController {//包含内部人员、外部人员
 //        return ResultUtil.success("success", customerService.getCustomerByKey(key,false));
 //    }
 
-    @GetMapping("/users")
-    @ApiOperation("查询所有用户")
-    public Result findAllUsers(@RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "10") Integer pageSize,@RequestParam(required = false) Boolean isDelete){
-        return ResultUtil.success("success", userService.getAllUsers(pageNum,pageSize,isDelete));
-    }
-
-//    @GetMapping("/users/sales")
-//    @ApiOperation("查询所有业务员")
-//    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER')")
-//    public Result findAllSales(){
-//        return ResultUtil.success("success", userService.getAllSalesByIsDelete(false));
-//    }
 
 
 }

+ 6 - 6
src/main/java/com/loan/system/controller/admin/DetailsController.java

@@ -113,8 +113,8 @@ public class DetailsController {
 
                     loanCaseDetail.setChannelName(loanCase.getChannelName());//9.
                     List<String> userNames=new ArrayList<>();
-                    StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(), caseId);
-                    StepVO stepVO1 = stepService.findByStepNameAndCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(), caseId);
+                    StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getCode(), caseId);
+                    StepVO stepVO1 = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PRE_TRIAL.getCode(), caseId);
                     if(stepVO!=null){
                         userNames.add(userService.findByIdAndIsDelete(stepVO.getUserId1()).getRealName());
                     }
@@ -218,8 +218,8 @@ public class DetailsController {
             disbursementDetail.setCustomerName(customerService.findByCustomerIdAndIsDelete(loanCase.getCustomerId(), false).getName());//4.
             disbursementDetail.setPawnAmount(contractVO.getContractAmount());//5.
             List<String> userNames=new ArrayList<>();
-            StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(), caseId);
-            StepVO stepVO1 = stepService.findByStepNameAndCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(), caseId);
+            StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getCode(), caseId);
+            StepVO stepVO1 = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PRE_TRIAL.getCode(), caseId);
             if(stepVO!=null){
                 userNames.add(userService.findByIdAndIsDelete(stepVO.getUserId1()).getRealName());
             }
@@ -323,8 +323,8 @@ public class DetailsController {
                     if(Math.abs(loanCase.getTotalLoanAmount() - amount)<Double.MIN_VALUE)
                         repaymentDetail.setInterestAmount(contract.getInterestAmount());//9.
                     List<String> userNames=new ArrayList<>();
-                    StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(), caseId);
-                    StepVO stepVO1 = stepService.findByStepNameAndCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(), caseId);
+                    StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getCode(), caseId);
+                    StepVO stepVO1 = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PRE_TRIAL.getCode(), caseId);
                     if(stepVO!=null){
                         userNames.add(userService.findByIdAndIsDelete(stepVO.getUserId1()).getRealName());
                     }

+ 14 - 0
src/main/java/com/loan/system/controller/admin/MaterialsController.java

@@ -1,6 +1,12 @@
 package com.loan.system.controller.admin;
 
+import com.loan.system.domain.pojo.Result;
+import com.loan.system.service.DocumentService;
+import com.loan.system.utils.ResultUtil;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -8,4 +14,12 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("/admin/materials")
 @Api(tags = "资料归档管理")
 public class MaterialsController {
+    @Autowired
+    private DocumentService documentService;
+
+    @GetMapping
+    @ApiOperation("所有附件信息")
+    private Result getAllFiles(){
+        return ResultUtil.success("success",documentService.findAll());
+    }
 }

+ 229 - 252
src/main/java/com/loan/system/controller/wechat/ApprovalController.java

@@ -29,14 +29,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.*;
 
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletRequest;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.HashMap;
+
 import java.util.List;
-import java.util.Map;
 
 import static com.loan.system.domain.enums.ExceptionEnum.*;
 
@@ -44,7 +38,6 @@ import static com.loan.system.domain.enums.ExceptionEnum.*;
 @RequestMapping("/wechat/")
 @Api(tags = "业务审核接口")
 public class ApprovalController {
-    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
     @Autowired
     private LoanService loanCaseService;
     @Autowired
@@ -52,7 +45,7 @@ public class ApprovalController {
     @Autowired
     private StepService stepService;
     @Autowired
-    private StringRedisTemplate stringRedisTemplate;
+   private UserService userService;
 
     @GetMapping("preApproval/detail")
     @ApiOperation("获取预审审详情")
@@ -61,142 +54,62 @@ public class ApprovalController {
         if (ObjectUtils.isEmpty(caseId) || !loanCaseService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(PROJECT_NOT_EXIST);
 
-        return ResultUtil.success("success",approvalService.findByCaseIdAndStepNameAndIsDelete(caseId,StepPropertyEnum.PRE_TRIAL.getLabel()));
+        return ResultUtil.success("success", approvalService.getPreDetail(caseId));
     }
 
     @PostMapping("/preapproval-assist/pass")
-    @PreAuthorize("@pms.hasRole('ASSIST_SALES')")
+    @PreAuthorize("@pms.hasAnyRoles('ASSIST_SALES','LEAD_SALES')")
     @ApiOperation("副业务员审核")
-    public Result pre_approval_save_primary(@RequestBody PreApprovalDTO preApprovalDTO) {
-        if(ObjectUtils.isEmpty(preApprovalDTO)||ObjectUtils.isEmpty(preApprovalDTO.getCaseId()))
+    public Result preApprovalSavePrimary(@RequestBody PreApprovalDTO preApprovalDTO) {
+        if (ObjectUtils.isEmpty(preApprovalDTO) || ObjectUtils.isEmpty(preApprovalDTO.getCaseId()))
             throw new DescribeException(INPUT_ERROR);
 
-        Long userId= BaseContext.getCurrentId();
-        Long caseId=preApprovalDTO.getCaseId();
-        LoanCase loanCase = loanCaseService.findLoanCaseByIdAndIsDelete(caseId,false);
-        if(loanCase == null||loanCase.getIsDelete()){
-            return ResultUtil.error(PROJECT_NOT_EXIST.getCode(), PROJECT_NOT_EXIST.getMsg());
+        Long userId = BaseContext.getCurrentId();
+        Long caseId = preApprovalDTO.getCaseId();
+        if (!loanCaseService.existsByIdAndIsDelete(caseId)) {
+            throw new DescribeException(PROJECT_NOT_EXIST);
         }
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(), caseId);
-        if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+
+        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PRE_TRIAL.getCode(), caseId);
+        if (ObjectUtils.isEmpty(step) || !step.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
         //如果当前业务员与第一个业务员相同 或 当前业务员与第二个业务员不同 则报错
-        if((step.getUserId1()!=null && step.getUserId1().equals(userId)) || (step.getUserId2()!=null&&userId!=step.getUserId2()))
+        StepVO preStep = stepService.findByStepCodeAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getCode(), caseId);
+        if ((preStep.getUserId1() != null && preStep.getUserId1().equals(userId)) || (step.getUserId1() != null && userId != step.getUserId1()))
             throw new DescribeException(STEP_USER_NOT_EXPECTED);
 
-        //添加记录
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.PRE_TRIAL.getLabel(),DecisionEnum.PASS.getMsg(),preApprovalDTO.getComments());
-
-        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.PRE_TRIAL.getLabel(),userId,caseId);
-        //更新步骤状态
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.PRE_TRIAL_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.PRE_TRIAL.getLabel(), caseId);
-        stepService.tryStartStep(StepPropertyEnum.PRE_TRIAL.getLabel(),caseId);
-        loanCaseService.updateUpdatetimeByIdAndIsDeleted(caseId,false);
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
-
-        return ResultUtil.success("success","预审通过");
-    }
+        approvalService.preApprovalSavePrimary(userId, caseId, preApprovalDTO);
 
-//    @PostMapping("/approval-lead/{caseId}/pass")
-//    @PreAuthorize("@pms.hasRole('LEAD_SALES')")
-//    @ApiOperation("主业务员审核")
-//    public Result pre_approval_save_assist(PreApprovalDTO preApprovalDTO, HttpServletRequest request) {
-//        //请求头根据token获取用户信息
-//        //String token = request.getHeader("Authorization").substring(7); // 去掉"Bearer "前缀
-//        String token = request.getHeader("Authorization");
-//        Long userId=Long.parseLong(JwtUtil.parseJWT(jwtProperties.getUserSecretKey(),token).get("userId").toString());
-//        log.info("用户id: {}", userId);
-//        User user = userService.findByIdAndIsDelete(userId);
-//        log.info("用户: {}", user.getMobile());
-//        LoanCase loanCase = loanCaseService.findByIdAndIsDelete(preApprovalDTO.getCaseId());
-//        if(loanCase == null){
-//            return ResultUtil.error(PROJECT_NOT_EXIST.getCode(), PROJECT_NOT_EXIST.getMsg());
-//        }
-//        // TODO: 使用userId进行后续操作
-//        // 创建新的审批记录
-//        ApprovalRecord approvalRecord = new ApprovalRecord();
-//        approvalRecord.setCaseId(preApprovalDTO.getCaseId());
-//        approvalRecord.setStepName(StepPropertyEnum.PRE_TRIAL.getLabel());
-//        approvalRecord.setApproverId(userId);
-//        approvalRecord.setDecision(DecisionEnum.PASS.getMsg());
-//        approvalRecord.setComments(preApprovalDTO.getComments());
-//        approvalRecord.setCreateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
-//        approvalRecord.setUpdateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
-//        approvalRecord.setIsDelete(false);
-//        // 保存到数据库
-//        approvalService.save(approvalRecord);
-//        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.PRE_TRIAL.getLabel(),userId,preApprovalDTO.getCaseId());
-//        Step step = stepService.findByStepNameAndCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(),preApprovalDTO.getCaseId());
-//        if(step.getUserId1()!=null && step.getUserId2()!=null){
-//            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.PRE_TRIAL_PARENT.getLabel(),preApprovalDTO.getCaseId());
-//            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.PRE_TRIAL.getLabel(),preApprovalDTO.getCaseId());
-//            stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(), StepPropertyEnum.APPROVAL_PARENT.getLabel(),preApprovalDTO.getCaseId());
-//            stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(), StepPropertyEnum.APPROVAL.getLabel(),preApprovalDTO.getCaseId());
-//            //TODO: 添加业务逻辑 ,通知下一个环节审批人
-//        }
-//        log.info("业务id: {}", preApprovalDTO.getCaseId());
-//        loanCaseService.updateUpdatetimeByIdAndIsDeleted(preApprovalDTO.getCaseId(),false);
-//        return ResultUtil.success("success","审批通过");
-//    }
+        return ResultUtil.success("success", "预审通过");
+    }
 
     @PostMapping("/preapproval-records/reject")
-    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES','ASSIST_SALES')")
+    @PreAuthorize("@pms.hasAnyRoles('ASSIST_SALES','LEAD_SALES')")
     @ApiOperation("业务预审拒绝")
-    public Result pre_approval_reject(@RequestBody PreApprovalDTO preApprovalDTO) {
-        if(ObjectUtils.isEmpty(preApprovalDTO)||ObjectUtils.isEmpty(preApprovalDTO.getCaseId()))
+    public Result preApprovalReject(@RequestBody PreApprovalDTO preApprovalDTO) {
+        if (ObjectUtils.isEmpty(preApprovalDTO) || ObjectUtils.isEmpty(preApprovalDTO.getCaseId()))
             throw new DescribeException(INPUT_ERROR);
 
-        Long userId= BaseContext.getCurrentId();
+        Long userId = BaseContext.getCurrentId();
         Long caseId = preApprovalDTO.getCaseId();
 
-        LoanCase loanCase = loanCaseService.findLoanCaseByIdAndIsDelete(caseId,false);
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PRE_TRIAL.getCode(), caseId);
+        if (ObjectUtils.isEmpty(step) || !step.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        //判断过去处理人与目前处理人是否一致
-        if(step.getUserId2()!=null&&userId!=step.getUserId2())
+        //如果当前业务员与第一个业务员相同 或 当前业务员与第二个业务员不同 则报错
+        StepVO preStep = stepService.findByStepCodeAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getCode(), caseId);
+        if ((preStep.getUserId1() != null && preStep.getUserId1().equals(userId)) || (step.getUserId1() != null && userId != step.getUserId1()))
             throw new DescribeException(STEP_USER_NOT_EXPECTED);
 
-        if(loanCase == null||loanCase.getIsDelete()){
-            return ResultUtil.error(PROJECT_NOT_EXIST.getCode(), PROJECT_NOT_EXIST.getMsg());
+        if (!loanCaseService.existsByIdAndIsDelete(caseId)) {
+            throw new DescribeException(PROJECT_NOT_EXIST);
         }
-        //添加记录
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.PRE_TRIAL.getLabel(),DecisionEnum.REJECT.getMsg(),preApprovalDTO.getComments());
-        RedisData redisData = new RedisData(stringRedisTemplate);
-        redisData.setRejectApprovalRecord(caseId,StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),preApprovalDTO.getComments());
-
-        stepService.updateUserId2ByCaseIdAndStepName(StepPropertyEnum.PRE_TRIAL.getLabel(),userId,caseId);
-        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.PRE_TRIAL_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.PRE_TRIAL.getLabel(),caseId);
-        loanCaseService.updateUpdatetimeByIdAndIsDeleted(caseId,false);
-        //TODO:微信推送预审拒绝消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(caseId);
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
-
-        return ResultUtil.success("success","预审拒绝");
+
+        approvalService.preApprovalReject(caseId, userId, preApprovalDTO);
+
+        return ResultUtil.success("success", "预审拒绝");
     }
 
     @GetMapping("approval/detail")
@@ -206,168 +119,232 @@ public class ApprovalController {
         if (ObjectUtils.isEmpty(caseId) || !loanCaseService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(PROJECT_NOT_EXIST);
 
-        return ResultUtil.success("success",approvalService.findByCaseIdAndStepNameAndIsDelete(caseId,StepPropertyEnum.APPROVAL.getLabel()));
+        return ResultUtil.success("success",approvalService.getApprovalDetail(caseId));
     }
 
-    @PostMapping("/approval/pass")
+    @PostMapping("/approval1/pass")
     @PreAuthorize("@pms.hasRole('APPROVER')")
     @ApiOperation("业务审批通过")
-    public Result approval_pass(@RequestBody ApprovalDTO approvalDTO) {
-        if(ObjectUtils.isEmpty(approvalDTO)||approvalDTO.getCaseId() == null)
-            throw new DescribeException(INPUT_ERROR);
+    public Result approvalPass1(@RequestBody ApprovalDTO approvalDTO) {
+        validateApprovalDTO(approvalDTO,StepPropertyEnum.APPROVAL.getCode());
 
-        Long userId= BaseContext.getCurrentId();
-        Long caseId = approvalDTO.getCaseId();
-        //审批业务逻辑
-        LoanCase loanCase = loanCaseService.findLoanCaseByIdAndIsDelete(caseId, false);
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.APPROVAL.getLabel(),caseId);
-        //判断过去处理人与目前处理人是否一致
-        if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
-            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+        approvalService.approvalPass1(approvalDTO);
 
-        if(step.getUserId1()!=null&&step.getUserId2()!=null&&userId != step.getUserId2()&&userId!=step.getUserId1())
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
+        return ResultUtil.success("success", "审批通过");
 
-        if(loanCase == null || loanCase.getIsDelete()){
-            throw  new DescribeException(PROJECT_NOT_EXIST);
-        }
+    }
 
-        List<ApprovalRecordVO> records = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getLabel(), userId);
-        ApprovalRecordVO record = ObjectUtils.isEmpty(records)?null:records.get(records.size()-1);//获取当前审批人上次的记录
-        if(step.getUserId1()== null){
-            stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.APPROVAL.getLabel(),userId,caseId);
-        }else if(step.getUserId2()== null ){
-            //如果用户1审批过且通过,无法重复审批
-            if(step.getUserId1() == userId && record!=null && record.getDecision().equals(DecisionEnum.PASS.getMsg()))
-                throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
-            else if(step.getUserId1()!= userId)
-                stepService.updateUserId2ByCaseIdAndStepName(StepPropertyEnum.APPROVAL.getLabel(),userId,caseId);
-        }else {
-            List<ApprovalRecordVO> approvalRecords1 = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getLabel(), step.getUserId1());
-            ApprovalRecordVO approvalRecord1 = approvalRecords1.get(approvalRecords1.size()-1);
-            List<ApprovalRecordVO> approvalRecords2 = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getLabel(), step.getUserId2());
-            ApprovalRecordVO approvalRecord2 = approvalRecords2.get(approvalRecords2.size()-1);
-            if(step.getUserId1()== userId){//仅版本一样或版本小于可新增
-              if(approvalRecord1.getVersion()> approvalRecord2.getVersion() && approvalRecord1.getDecision().equals(DecisionEnum.PASS.getMsg()))
-                  throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
-            }else{
-                if(approvalRecord1.getVersion()< approvalRecord2.getVersion() && approvalRecord2.getDecision().equals(DecisionEnum.PASS.getMsg()))
-                    throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
-            }
-        }
+    @PostMapping("/approval1/reject")
+    @PreAuthorize("@pms.hasRole('APPROVER')")
+    @ApiOperation("业务审批拒绝")
+    public Result approvalReject1(@RequestBody ApprovalDTO approvalDTO) {
+        validateApprovalDTO(approvalDTO,StepPropertyEnum.APPROVAL.getCode());
 
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.APPROVAL.getLabel(),DecisionEnum.PASS.getMsg(),approvalDTO.getComments());
-
-        step=stepService.findByStepNameAndCaseId(StepPropertyEnum.APPROVAL.getLabel(), caseId);
-        if(step.getUserId1()!=null&&step.getUserId2()!=null){
-            List<ApprovalRecordVO> records1 = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getLabel(), step.getUserId1());
-            ApprovalRecordVO record1 = records1.get(records1.size()-1);//获取当前审批人上次的记录
-            List<ApprovalRecordVO> records2 = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getLabel(), step.getUserId2());
-            ApprovalRecordVO record2 = records2.get(records2.size()-1);//获取当前审批人上次的记录
-
-            //TODO:同一阶段双人审批通过,才结束
-            if(record1.getVersion()==record2.getVersion() && record1.getDecision().equals(DecisionEnum.PASS.getMsg()) && record2.getDecision().equals(DecisionEnum.PASS.getMsg())){
-                stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.APPROVAL_PARENT.getLabel(),caseId);
-                stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.APPROVAL.getLabel(),caseId);
-
-                stepService.tryStartStep(StepPropertyEnum.APPROVAL.getLabel(), caseId);
-                //TODO微信推送审批通过消息和通知下一环节
-            }
-        }else{
-            //TODO微信推送另一个审批人审批
-        }
+        approvalService.approvalReject1( approvalDTO);
 
-        return ResultUtil.success("success","审批通过");
+        return ResultUtil.success("success", "审批驳回");
+    }
+
+    @PostMapping("/approval2/pass")
+    @PreAuthorize("@pms.hasRole('APPROVER')")
+    @ApiOperation("业务审批通过")
+    public Result approvalPass2(@RequestBody ApprovalDTO approvalDTO) {
+        validateApprovalDTO(approvalDTO,StepPropertyEnum.APPROVAL_2.getCode());
+
+        approvalService.approvalPass2( approvalDTO);
+
+        return ResultUtil.success("success", "审批通过");
 
     }
 
-    @PostMapping("/approval/reject")
+    @PostMapping("/approval2/reject")
     @PreAuthorize("@pms.hasRole('APPROVER')")
     @ApiOperation("业务审批拒绝")
-    public Result approval_reject(@RequestBody ApprovalDTO approvalDTO) {
-        if(ObjectUtils.isEmpty(approvalDTO)||approvalDTO.getCaseId() == null)
-            throw new DescribeException(INPUT_ERROR);
-        Long userId= BaseContext.getCurrentId();
+    public Result approvalReject2(@RequestBody ApprovalDTO approvalDTO) {
+        validateApprovalDTO(approvalDTO,StepPropertyEnum.APPROVAL_2.getCode());
+
+        approvalService.approvalReject2(approvalDTO);
+
+        return ResultUtil.success("success", "审批驳回");
+    }
+
+    @PostMapping("/approval1/terminate")
+    @PreAuthorize("@pms.hasRole('APPROVER')")
+    @ApiOperation("业务审批终结")
+    public Result approvalEnd(@RequestBody ApprovalDTO approvalDTO) {
+        validateApprovalDTO(approvalDTO,StepPropertyEnum.APPROVAL.getCode());
+
         Long caseId = approvalDTO.getCaseId();
-        //审批业务逻辑
-        LoanCase loanCase = loanCaseService.findLoanCaseByIdAndIsDelete(approvalDTO.getCaseId(),false);
-        if(loanCase == null||loanCase.getIsDelete()){
-            return ResultUtil.error(PROJECT_NOT_EXIST.getCode(), PROJECT_NOT_EXIST.getMsg());
-        }
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.APPROVAL.getLabel(), approvalDTO.getCaseId());
-        if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
-            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+        approvalService.addApprovalRecord(caseId, StepPropertyEnum.APPROVAL.getCode(), DecisionEnum.TERMINATE.getMsg(), approvalDTO.getComments());
 
-        //判断过去处理人与目前处理人是否一致
-        if(step.getUserId1()!=null&&step.getUserId2()!=null&&userId!=step.getUserId2()&&userId!=step.getUserId1())
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
+        //项目终结
+        loanCaseService.updateIsCompleteByCaseId(DecisionEnum.TERMINATE.getMsg(), caseId);
 
-        List<ApprovalRecordVO> records = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getLabel(), userId);
-        ApprovalRecordVO record = ObjectUtils.isEmpty(records)?null:records.get(records.size()-1);//获取当前审批人上次的记录
-        if(step.getUserId1()== null){
-            stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.APPROVAL.getLabel(),userId,caseId);
-        }else if(step.getUserId2()== null ){
-            //如果用户1审批过且通过,无法重复审批
-            if(step.getUserId1() == userId && record!=null && record.getDecision().equals(DecisionEnum.PASS.getMsg()))
-                throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
-            else if(step.getUserId1()!= userId)
-                stepService.updateUserId2ByCaseIdAndStepName(StepPropertyEnum.APPROVAL.getLabel(),userId,caseId);
-        }
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.APPROVAL.getLabel(),DecisionEnum.REJECT.getMsg(),approvalDTO.getComments());
-        RedisData redisData = new RedisData(stringRedisTemplate);
-        redisData.setRejectApprovalRecord(caseId,StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),approvalDTO.getComments());
-
-        //更新环节状态
-        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.PRE_TRIAL_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.PRE_TRIAL.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.APPROVAL_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.APPROVAL.getLabel(),caseId);
         //TODO微信推送审批驳回消息和通知重新受理(到业务受理)
 
-        return ResultUtil.success("success","审批驳回");
+        return ResultUtil.success("success", "审批终结");
     }
-    @PostMapping("/approval/terminate")
+
+    @PostMapping("/approval2/terminate")
     @PreAuthorize("@pms.hasRole('APPROVER')")
     @ApiOperation("业务审批终结")
-    public Result approval_end(@RequestBody ApprovalDTO approvalDTO) {
-        if (ObjectUtils.isEmpty(approvalDTO)||approvalDTO.getCaseId() == null)
+    public Result approvalEnd2(@RequestBody ApprovalDTO approvalDTO) {
+        validateApprovalDTO(approvalDTO,StepPropertyEnum.APPROVAL_2.getCode());
+
+        Long caseId = approvalDTO.getCaseId();
+        approvalService.addApprovalRecord(caseId, StepPropertyEnum.APPROVAL_2.getCode(), DecisionEnum.TERMINATE.getMsg(), approvalDTO.getComments());
+
+        //项目终结
+        loanCaseService.updateIsCompleteByCaseId(DecisionEnum.TERMINATE.getMsg(), caseId);
+
+        //TODO微信推送审批驳回消息和通知重新受理(到业务受理)
+
+        return ResultUtil.success("success", "审批终结");
+    }
+
+    private void validateApprovalDTO(ApprovalDTO approvalDTO,Integer stepCode) {
+        if (ObjectUtils.isEmpty(approvalDTO) || approvalDTO.getCaseId() == null)
             throw new DescribeException(INPUT_ERROR);
-        Long userId= BaseContext.getCurrentId();
+
+        Long userId = BaseContext.getCurrentId();
         Long caseId = approvalDTO.getCaseId();
-        //审批业务逻辑
-        LoanCase loanCase = loanCaseService.findLoanCaseByIdAndIsDelete(approvalDTO.getCaseId(),false);
-        if(loanCase == null||loanCase.getIsDelete()){
-            return ResultUtil.error(PROJECT_NOT_EXIST.getCode(), PROJECT_NOT_EXIST.getMsg());
+        if (!loanCaseService.existsByIdAndIsDelete(caseId)) {
+            throw new DescribeException(PROJECT_NOT_EXIST);
         }
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.APPROVAL.getLabel(), approvalDTO.getCaseId());
-        if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+
+        StepVO step = stepService.findByStepCodeAndCaseId(stepCode, caseId);
+        if (ObjectUtils.isEmpty(step) || !step.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
+        if(stepCode.equals(StepPropertyEnum.APPROVAL_2.getCode())){
+            //保证两次审批不是同一个人
+            StepVO preStep = stepService.findByStepCodeAndCaseId(StepPropertyEnum.APPROVAL.getCode(), caseId);
+            if (preStep.getUserId1() != null && preStep.getUserId1().equals(userId))
+                throw new DescribeException(STEP_USER_NOT_EXPECTED);
+        }
         //判断过去处理人与目前处理人是否一致
-        if(step.getUserId1()!=null&&step.getUserId2()!=null&&userId!=step.getUserId2()&&userId!=step.getUserId1())
+        if (step.getUserId1() != null && userId != step.getUserId1())
             throw new DescribeException(STEP_USER_NOT_EXPECTED);
 
-        //审批记录处理
-        List<ApprovalRecordVO> records = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getLabel(), userId);
-        ApprovalRecordVO record = ObjectUtils.isEmpty(records)?null:records.get(records.size()-1);//获取当前审批人上次的记录
-        if(step.getUserId1()== null){
-            stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.APPROVAL.getLabel(),userId,caseId);
-        }else if(step.getUserId2()== null ){
-            //如果用户1审批过且通过,无法重复审批
-            if(step.getUserId1() == userId && record!=null && record.getDecision().equals(DecisionEnum.PASS.getMsg()))
-                throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
-            else if(step.getUserId1()!= userId)
-                stepService.updateUserId2ByCaseIdAndStepName(StepPropertyEnum.APPROVAL.getLabel(),userId,caseId);
-        }
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.APPROVAL.getLabel(),DecisionEnum.TERMINATE.getMsg(),approvalDTO.getComments());
-
-        //项目终结
-        loanCaseService.updateIsCompleteByCaseId(DecisionEnum.TERMINATE.getMsg(),caseId);
-
-        //TODO微信推送审批驳回消息和通知重新受理(到业务受理)
-
-        return ResultUtil.success("success","审批终结");
     }
+
+//    @PostMapping("/approval/pass")
+//    @PreAuthorize("@pms.hasRole('APPROVER')")
+//    @ApiOperation("业务审批通过")
+//    public Result approval_pass(@RequestBody ApprovalDTO approvalDTO) {
+//        if(ObjectUtils.isEmpty(approvalDTO)||approvalDTO.getCaseId() == null)
+//            throw new DescribeException(INPUT_ERROR);
+//
+//        Long userId= BaseContext.getCurrentId();
+//        Long caseId = approvalDTO.getCaseId();
+//        //审批业务逻辑
+//        LoanCase loanCase = loanCaseService.findLoanCaseByIdAndIsDelete(caseId, false);
+//        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.APPROVAL.getCode(),caseId);
+//        //判断过去处理人与目前处理人是否一致
+//        if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+//            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+//
+//        if(step.getUserId1()!=null&&step.getUserId2()!=null&&userId != step.getUserId2()&&userId!=step.getUserId1())
+//            throw new DescribeException(STEP_USER_NOT_EXPECTED);
+//
+//        if(loanCase == null || loanCase.getIsDelete()){
+//            throw  new DescribeException(PROJECT_NOT_EXIST);
+//        }
+//
+//        List<ApprovalRecordVO> records = approvalService.findByCaseIdAndStepCodeAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getCode(), userId);
+//        ApprovalRecordVO record = ObjectUtils.isEmpty(records)?null:records.get(records.size()-1);//获取当前审批人上次的记录
+//        if(step.getUserId1()== null){
+//            stepService.updateUserId1ByCaseIdAndStepCode(StepPropertyEnum.APPROVAL.getCode(),userId,caseId);
+//        }else if(step.getUserId2()== null ){
+//            //如果用户1审批过且通过,无法重复审批
+//            if(step.getUserId1() == userId && record!=null && record.getDecision().equals(DecisionEnum.PASS.getMsg()))
+//                throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
+//            else if(step.getUserId1()!= userId)
+//                stepService.updateUserId2ByCaseIdAndStepCode(StepPropertyEnum.APPROVAL.getCode(),userId,caseId);
+//        }else {
+//            List<ApprovalRecordVO> approvalRecords1 = approvalService.findByCaseIdAndStepCodeAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getCode(), step.getUserId1());
+//            ApprovalRecordVO approvalRecord1 = approvalRecords1.get(approvalRecords1.size()-1);
+//            List<ApprovalRecordVO> approvalRecords2 = approvalService.findByCaseIdAndStepCodeAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getCode(), step.getUserId2());
+//            ApprovalRecordVO approvalRecord2 = approvalRecords2.get(approvalRecords2.size()-1);
+//            if(step.getUserId1()== userId){//仅版本一样或版本小于可新增
+//                if(approvalRecord1.getVersion()> approvalRecord2.getVersion() && approvalRecord1.getDecision().equals(DecisionEnum.PASS.getMsg()))
+//                    throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
+//            }else{
+//                if(approvalRecord1.getVersion()< approvalRecord2.getVersion() && approvalRecord2.getDecision().equals(DecisionEnum.PASS.getMsg()))
+//                    throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
+//            }
+//        }
+//
+//        approvalService.addApprovalRecord(caseId,StepPropertyEnum.APPROVAL.getCode(),DecisionEnum.PASS.getMsg(),approvalDTO.getComments());
+//
+//        step=stepService.findByStepCodeAndCaseId(StepPropertyEnum.APPROVAL.getCode(), caseId);
+//        if(step.getUserId1()!=null&&step.getUserId2()!=null){
+//            List<ApprovalRecordVO> records1 = approvalService.findByCaseIdAndStepCodeAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getCode(), step.getUserId1());
+//            ApprovalRecordVO record1 = records1.get(records1.size()-1);//获取当前审批人上次的记录
+//            List<ApprovalRecordVO> records2 = approvalService.findByCaseIdAndStepCodeAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getCode(), step.getUserId2());
+//            ApprovalRecordVO record2 = records2.get(records2.size()-1);//获取当前审批人上次的记录
+//
+//            //TODO:同一阶段双人审批通过,才结束
+//            if(record1.getVersion()==record2.getVersion() && record1.getDecision().equals(DecisionEnum.PASS.getMsg()) && record2.getDecision().equals(DecisionEnum.PASS.getMsg())){
+//                stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.APPROVAL_PARENT.getCode(),caseId);
+//                stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.APPROVAL.getCode(),caseId);
+//
+//                stepService.tryStartStep(StepPropertyEnum.APPROVAL.getCode(), caseId);
+//                //TODO微信推送审批通过消息和通知下一环节
+//            }
+//        }else{
+//            //TODO微信推送另一个审批人审批
+//        }
+//
+//        return ResultUtil.success("success","审批通过");
+//
+//    }
+//
+//    @PostMapping("/approval/reject")
+//    @PreAuthorize("@pms.hasRole('APPROVER')")
+//    @ApiOperation("业务审批拒绝")
+//    public Result approval_reject(@RequestBody ApprovalDTO approvalDTO) {
+//        if(ObjectUtils.isEmpty(approvalDTO)||approvalDTO.getCaseId() == null)
+//            throw new DescribeException(INPUT_ERROR);
+//        Long userId= BaseContext.getCurrentId();
+//        Long caseId = approvalDTO.getCaseId();
+//        //审批业务逻辑
+//        LoanCase loanCase = loanCaseService.findLoanCaseByIdAndIsDelete(approvalDTO.getCaseId(),false);
+//        if(loanCase == null||loanCase.getIsDelete()){
+//            return ResultUtil.error(PROJECT_NOT_EXIST.getCode(), PROJECT_NOT_EXIST.getMsg());
+//        }
+//        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.APPROVAL.getCode(), approvalDTO.getCaseId());
+//        if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+//            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+//
+//        //判断过去处理人与目前处理人是否一致
+//        if(step.getUserId1()!=null&&step.getUserId2()!=null&&userId!=step.getUserId2()&&userId!=step.getUserId1())
+//            throw new DescribeException(STEP_USER_NOT_EXPECTED);
+//
+//        List<ApprovalRecordVO> records = approvalService.findByCaseIdAndStepCodeAndApproverIdAndIsDelete(caseId, StepPropertyEnum.APPROVAL.getCode(), userId);
+//        ApprovalRecordVO record = ObjectUtils.isEmpty(records)?null:records.get(records.size()-1);//获取当前审批人上次的记录
+//        if(step.getUserId1()== null){
+//            stepService.updateUserId1ByCaseIdAndStepCode(StepPropertyEnum.APPROVAL.getCode(),userId,caseId);
+//        }else if(step.getUserId2()== null ){
+//            //如果用户1审批过且通过,无法重复审批
+//            if(step.getUserId1() == userId && record!=null && record.getDecision().equals(DecisionEnum.PASS.getMsg()))
+//                throw new DescribeException(APPROVAL_USER_IS_COMPLETED);
+//            else if(step.getUserId1()!= userId)
+//                stepService.updateUserId2ByCaseIdAndStepCode(StepPropertyEnum.APPROVAL.getCode(),userId,caseId);
+//        }
+//        approvalService.addApprovalRecord(caseId,StepPropertyEnum.APPROVAL.getCode(),DecisionEnum.REJECT.getMsg(),approvalDTO.getComments());
+//        RedisData redisData = new RedisData(stringRedisTemplate);
+//        redisData.setRejectApprovalRecord(caseId,StepPropertyEnum.BUSINESS_ACCEPT.getCode(),approvalDTO.getComments());
+//
+//        //更新环节状态
+//        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT_PARENT.getCode(),caseId);
+//        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT.getCode(),caseId);
+//        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.PRE_TRIAL_PARENT.getCode(),caseId);
+//        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.PRE_TRIAL.getCode(),caseId);
+//        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.APPROVAL_PARENT.getCode(),caseId);
+//        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(),StepPropertyEnum.APPROVAL.getCode(),caseId);
+//        //TODO微信推送审批驳回消息和通知重新受理(到业务受理)
+//
+//        return ResultUtil.success("success","审批驳回");
+//    }
 }
 

+ 109 - 209
src/main/java/com/loan/system/controller/wechat/CollateralController.java

@@ -2,12 +2,10 @@ package com.loan.system.controller.wechat;
 
 import cn.hutool.core.bean.BeanUtil;
 import com.loan.system.context.BaseContext;
+import com.loan.system.domain.dto.ChannelPushDTO;
 import com.loan.system.domain.dto.CollateralPlanApprovalDTO;
 import com.loan.system.domain.dto.CollateralPlanDTO;
-import com.loan.system.domain.entity.ApprovalRecord;
-import com.loan.system.domain.entity.Collateral;
-import com.loan.system.domain.entity.CollateralPlan;
-import com.loan.system.domain.entity.User;
+import com.loan.system.domain.entity.*;
 import com.loan.system.domain.enums.*;
 import com.loan.system.domain.pojo.Result;
 import com.loan.system.domain.vo.*;
@@ -17,6 +15,7 @@ import com.loan.system.service.Impl.UserServiceImpl;
 import com.loan.system.utils.ResultUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,66 +34,40 @@ import java.util.List;
 import static com.loan.system.domain.enums.DecisionEnum.*;
 import static com.loan.system.domain.enums.ExceptionEnum.*;
 
+@Slf4j
 @RestController
 @RequestMapping("/wechat/collateral")
 @Api(tags = "抵押物接口")
 public class CollateralController {
+    //TODO:取/送证过程:1.上传计划 2.审批计划,并指派人员 3.确认计划 4.渠道推送并指派外部人员 5.渠道确认并填写意见与产调证明(更新押品状态)
+    //抵押物的入库与出库,需要渠道方确认了才能算正式
 
     @Autowired
     private CollateralService collateralService;
     @Autowired
     private CollateralPlanService collateralPlanService;
     @Autowired
-    private ApprovalService approvalService;
-    @Autowired
-    private UserService userService;
-    @Autowired
     private LoanService loanService;
     @Autowired
     private StepService stepService;
     @Autowired
     private DisbursementService disbursementService;
     @Autowired
-    private StringRedisTemplate redisTemplate;
+    private ChannelPushService channelPushService;
 
     @GetMapping("/caseInfo")
     @ApiOperation("查看客户与业务信息")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE','EXTERNAL')")
     public Result findCaseInfo(@RequestParam Long caseId){
         return ResultUtil.success("success",disbursementService.getLoanCaseAndCustomerByCaseId(caseId));
     }
 
     @GetMapping("/plan/{caseId}")
     @ApiOperation("查看项目抵押物计划")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findCollateralPlan(@PathVariable("caseId") Long caseId,@RequestParam String  flag){
-        List<CollateralPlan> collateralPlans = collateralPlanService.findByCaseIdAndFlagAndIsDelete(caseId,flag,false);
-
-        List<String> Roles=new ArrayList<>();
-        Roles.add(RoleEnum.LEAD_SALES.getMsg());
-        Roles.add(RoleEnum.ASSIST_SALES.getMsg());
-
-        // 创建VO列表
-        List<CollateralPlanVO> collateralPlanVOs = new ArrayList<>();
-        for(CollateralPlan plan : collateralPlans){
-            CollateralPlanVO vo = new CollateralPlanVO();
-            BeanUtil.copyProperties(plan, vo);
-            vo.setPlanUser(BeanUtil.copyProperties(userService.findByIdAndIsDelete(plan.getUserId()), UserVO.class));
-            vo.setCollaterals(collateralService.findByIds(getCollateralIds(plan.getCollateralIds())));
-
-            ApprovalRecord approvalRecord = approvalService.findByIdAndIsDelete(plan.getApprovalRecordId());
-            if (approvalRecord != null){
-                vo.setApprovalRecord(BeanUtil.copyProperties(approvalService.findByIdAndIsDelete(approvalRecord.getId()), ApprovalRecordVO.class));
-                vo.setApprovalUser(BeanUtil.copyProperties(userService.findByIdAndIsDelete(approvalRecord.getApproverId()), UserVO.class));
-            }
-            vo.setExecuteComments(plan.getOperatorComments());
-            vo.setExecuteUser(BeanUtil.copyProperties(userService.findByIdAndIsDelete(plan.getOperatorId()), UserVO.class));
-
-
-            collateralPlanVOs.add(vo);
-        }
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE','EXTERNAL')")
+    public Result listCollateralPlans(@PathVariable("caseId") Long caseId,@RequestParam String  flag){
 
-        return ResultUtil.success("success",collateralPlanVOs);
+        return ResultUtil.success("success",collateralPlanService.listCollateralPlans(caseId,flag));
     }
 
     @PostMapping("/plan/{planId}")
@@ -108,7 +81,7 @@ public class CollateralController {
         if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.PLAN_SUBMISSION.getLabel(), caseId);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PLAN_SUBMISSION.getCode(), caseId);
         if(ObjectUtils.isEmpty(stepVO)){
             if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
                 throw new DescribeException(STEP_HAS_NOT_PROCESS);
@@ -124,54 +97,7 @@ public class CollateralController {
         if (!info.isEmpty())
             throw new DescribeException(COLLATERAL_NOT_EXIST.getCode(),COLLATERAL_NOT_EXIST.getMsg()+ "{"+info+"}");
 
-        Long userid = BaseContext.getCurrentId();
-        CollateralPlan collateralPlan = CollateralPlan.builder()
-                .collateralIds(collateralPlanDTO.getCollateralIds())
-                .caseId(caseId)
-                .time(collateralPlanDTO.getCollateralTime())
-                .place(collateralPlanDTO.getCollateralPlace())
-                .flag(collateralPlanDTO.getFlag())
-                .userId(userid)
-                .comments(collateralPlanDTO.getComments())
-                .createTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
-                .updateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
-                .status(WAITING_APPROVAL.getMsg())
-                .isDelete(false)
-                .build();
-
-        if(planId  <= 0){
-            //TODO:若为驳回或未审批,则无法新增
-            List<CollateralPlan> plans = collateralPlanService.findByCaseIdAndFlagAndIsDelete(caseId,collateralPlanDTO.getFlag(),false);
-            if (!plans.isEmpty()) {
-                ApprovalRecord approvalRecord = approvalService.findByIdAndIsDelete(plans.get(plans.size() - 1).getApprovalRecordId());
-                if ((ObjectUtils.isEmpty(approvalRecord)||!approvalRecord.getDecision().equals(PASS.getMsg()))||(!plans.get(plans.size() - 1).getStatus().equals(WAITING_EXECUTION.getMsg())))
-                    throw new DescribeException(PLAN_HAS_NOT_PASSED);
-            }
-
-            collateralPlanService.save(collateralPlan);
-        }
-        else{
-            CollateralPlan plan=collateralPlanService.findById(planId);
-            if (ObjectUtils.isEmpty( plan))
-                throw new DescribeException(COLLATERAL_PLAN_NOT_EXIST);
-            ApprovalRecord approvalRecord = approvalService.findByIdAndIsDelete(plan.getApprovalRecordId());//获取上一次计划的审批记录
-            //若为通过或未审批,则无法修改
-            if(ObjectUtils.isEmpty(approvalRecord)||approvalRecord.getDecision().equals(PASS.getMsg()))
-                throw new DescribeException(PLAN_HAS_BEEN_PASSED);
-
-            collateralPlanService.updatePlanById(collateralPlan,planId);
-        }
-
-        //更新状态
-        String stepName = collateralPlanDTO.getFlag().equals(ENTER_WAREHOUSE.getMsg()) ? StepPropertyEnum.PLAN_SUBMISSION.getLabel() : StepPropertyEnum.PLAN_SUBMISSION_2.getLabel();
-        //String stepName2 = collateralPlanDTO.getFlag().equals("入库") ? StepPropertyEnum.APPROVAL_ASSIGNMENT.getLabel() : StepPropertyEnum.APPROVAL_ASSIGNMENT_2.getLabel();
-
-        stepService.updateUserId1ByCaseIdAndStepName(stepName,userid,caseId);
-        if (isComplete){
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), stepName,caseId);
-
-        }
-        stepService.tryStartStep(stepName,caseId);
+        collateralPlanService.createCollateralPlan(collateralPlanDTO,planId,isComplete);
 
         return ResultUtil.success( "计划上报成功");
     }
@@ -179,7 +105,7 @@ public class CollateralController {
     @PostMapping("/plan/approval/{recordId}")
     @PreAuthorize("@pms.hasRole('APPROVER')")
     @ApiOperation("审批计划")
-    public Result refuseCollateralPlan(@RequestBody CollateralPlanApprovalDTO collateralPlanApprovalDTO,@PathVariable("recordId") Long recordId){
+    public Result approvalCollateralPlan(@RequestBody CollateralPlanApprovalDTO collateralPlanApprovalDTO,@PathVariable("recordId") Long recordId){
         if(ObjectUtils.isEmpty(collateralPlanApprovalDTO)||collateralPlanApprovalDTO.getPlanId()== null||collateralPlanApprovalDTO.getCaseId()== null)
             throw new DescribeException(INPUT_ERROR);
         CollateralPlan collateralPlan = collateralPlanService.findById(collateralPlanApprovalDTO.getPlanId());
@@ -192,73 +118,13 @@ public class CollateralController {
         }
 
         Long caseId = collateralPlanApprovalDTO.getCaseId();
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.PLAN_SUBMISSION.getLabel(), caseId);
-        if(ObjectUtils.isEmpty(stepVO)){
-            if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
-                throw new DescribeException(STEP_HAS_NOT_PROCESS);
-        }
+        Integer stepCode = collateralPlan.getFlag() .equals(ENTER_WAREHOUSE.getMsg()) ? StepPropertyEnum.APPROVAL_ASSIGNMENT.getCode() : StepPropertyEnum.APPROVAL_ASSIGNMENT_2.getCode();
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(stepCode, caseId);
+        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+
+        return collateralPlanService.approvalCollateralPlan(collateralPlan,collateralPlanApprovalDTO,recordId);
 
-        String stepName = collateralPlan.getFlag().equals(ENTER_WAREHOUSE.getMsg()) ? StepPropertyEnum.APPROVAL_ASSIGNMENT.getLabel() : StepPropertyEnum.APPROVAL_ASSIGNMENT_2.getLabel();
-        ApprovalRecord approvalRecord = ApprovalRecord.builder()
-                .caseId(caseId)
-                .stepName(stepName)
-                .approverId(BaseContext.getCurrentId())
-                .decision(collateralPlanApprovalDTO.getDecision())
-                .comments(collateralPlanApprovalDTO.getComments())
-                .createTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
-                .updateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
-                .isDelete( false)
-                .build();
-
-        //TODO:如果存在则修改,不存在则保持
-        if (recordId <= 0)
-            approvalRecord = approvalService.save(approvalRecord);
-        else
-            approvalService.updateByIdAndIsDelete(approvalRecord,recordId);
-
-        //TODO:绑定审批计划与押品计划、下环节负责人
-        collateralPlanService.updateApprovalRecordAndOperatorById(approvalRecord.getId(),collateralPlanApprovalDTO.getUserId(),collateralPlan.getId());
-
-        Long userId = BaseContext.getCurrentId();
-        String stepName2 = collateralPlan.getFlag().equals(ENTER_WAREHOUSE.getMsg()) ? StepPropertyEnum.PLAN_SUBMISSION.getLabel() : StepPropertyEnum.PLAN_SUBMISSION_2.getLabel();
-
-        stepService.updateUserId1ByCaseIdAndStepName(stepName,userId,caseId);
-        //拒绝计划
-        if(REJECTION.getMsg().equals(collateralPlanApprovalDTO.getDecision())){
-            collateralPlanService.updateCollateralStatusById(collateralPlan.getId(), REJECTION.getMsg());
-
-            stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(), stepName,caseId);
-            stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(), stepName2,caseId);
-
-            return ResultUtil.success("success","计划已拒绝");
-            //同意计划
-        }else if(PASS.getMsg().equals(collateralPlanApprovalDTO.getDecision())){
-            collateralPlanService.updateCollateralStatusById(collateralPlanApprovalDTO.getPlanId(),WAITING_EXECUTION.getMsg());
-
-            //若上报计划全部完成且审批通过,则此次审批阶段完成
-            boolean flag = true;
-            List<CollateralPlan> collateralPlans = collateralPlanService.findByCaseIdAndFlagAndIsDelete(caseId,collateralPlan.getFlag(),false);
-            List<ApprovalRecordVO> approvalRecords = approvalService.findByCollateralAndCaseIdAndIsDelete(stepName,caseId, false);
-            if (collateralPlans.size()==approvalRecords.size()){
-                for(ApprovalRecordVO approvalRecordVO:approvalRecords){
-                    if(!approvalRecordVO.getDecision().equals(PASS.getMsg())){
-                        flag = false;
-                        break;
-                    }
-                }
-                if(flag && stepService.findByStepNameAndCaseId(stepName2, caseId).getStatus().equals(StepEnum.COMPLETED.getMsg())){
-                    stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), stepName,caseId);
-
-                }
-            }
-
-            stepService.tryStartStep(stepName,caseId);
-
-//TODO 改状态+微信推送下一个节点业务员
-            return ResultUtil.success("success","计划已通过");
-        }else{
-            return ResultUtil.error(ExceptionEnum.UNKNOWN_ERROR,"请选择通过或者拒绝");
-        }
     }
 
     /**确认抵押物入库,完成任务
@@ -272,38 +138,19 @@ public class CollateralController {
         if(collateralPlan == null){
             throw new DescribeException(COLLATERAL_PLAN_NOT_EXIST);
         }
-        if(!collateralPlan.getStatus().equals(WAITING_EXECUTION.getMsg())){
-            throw new DescribeException(COLLATERAL_PLAN_NOT_ALREADY_EXECUTION);
-        }
         if(!collateralPlan.getFlag().equals(ENTER_WAREHOUSE.getMsg())){
             throw new DescribeException(COLLATERAL_PLAN_NOT_EQUAL_WAREHOUSE);
         }
-        if(collateralPlan.getOperatorId()!= BaseContext.getCurrentId())
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
 
-        Long caseId = collateralPlan.getCaseId();
-        Long userId= BaseContext.getCurrentId();
+        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.EVIDENCE_CONFIRMATION.getCode(), collateralPlan.getCaseId());
+        if(step == null || !step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+            throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        collateralService.updateCollateralStatusByIds(getCollateralIds(collateralPlan.getCollateralIds()),ENTER_WAREHOUSE.getMsg());
-        collateralPlanService.updateCollateralStatusById(id,EXECUTION_COMPLETE.getMsg());
-        collateralPlanService.updateOperatorIdAndCommentsById(userId,comments,id);
+        if(collateralPlan.getOperatorId()!= BaseContext.getCurrentId())
+            throw new DescribeException(STEP_USER_NOT_EXPECTED);
 
-        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.EVIDENCE_CONFIRMATION.getLabel(),userId,caseId);
 
-        //若所有押品入库,则完成
-        boolean flag = true;
-        List<CollateralVO> collateralVOS = collateralService.findByCaseId(caseId);
-        for(CollateralVO collateralVO:collateralVOS){
-            if(!collateralVO.getStaus().equals(ENTER_WAREHOUSE.getMsg())){
-                flag = false;
-                break;
-            }
-        }
-        if(flag){
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.COLLATERAL_RECEIVE.getLabel(), caseId);
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.EVIDENCE_CONFIRMATION.getLabel(),caseId);
-            stepService.tryStartStep(StepPropertyEnum.EVIDENCE_CONFIRMATION.getLabel(),caseId);
-        }
+        collateralPlanService.warehouseCollateral(collateralPlan, comments);
 
         return ResultUtil.success("success","抵押物入库成功");
     }
@@ -318,46 +165,98 @@ public class CollateralController {
         if(collateralPlan == null){
             throw new DescribeException(COLLATERAL_PLAN_NOT_EXIST);
         }
-        if(!collateralPlan.getStatus().equals(WAITING_EXECUTION.getMsg())){
-            throw new DescribeException(COLLATERAL_PLAN_NOT_ALREADY_EXECUTION);
-        }
         if(!collateralPlan.getFlag().equals(OUT_WAREHOUSE.getMsg())){
            throw new DescribeException(COLLATERAL_PLAN_NOT_EQUAL_OUTBOUND);
         }
 
+        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.DELIVERY_CONFIRMATION.getCode(), collateralPlan.getCaseId());
+        if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+
         if(collateralPlan.getOperatorId()!= BaseContext.getCurrentId())
             throw new DescribeException(STEP_USER_NOT_EXPECTED);
 
-//        Collateral collateral = collateralService.findCollateralById(collateralPlan.getCollateralId());
-//        if(collateral == null){
-//            throw new DescribeException(COLLATERAL_NOT_EXIST);
-//        }
-
-        Long caseId = collateralPlan.getCaseId();
-        Long userId= BaseContext.getCurrentId();
-        collateralService.updateCollateralStatusByIds(getCollateralIds(collateralPlan.getCollateralIds()),OUT_WAREHOUSE.getMsg());
-        collateralPlanService.updateCollateralStatusById(id,EXECUTION_COMPLETE.getMsg());
-        collateralPlanService.updateOperatorIdAndCommentsById(userId,comments,id);
-
-        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.DELIVERY_CONFIRMATION.getLabel(),userId,caseId);
-
-        //若所有押品出库,则完成
-        boolean flag = true;
-        List<CollateralVO> collateralVOS = collateralService.findByCaseId(caseId);
-        for(CollateralVO collateralVO:collateralVOS){
-            if(!collateralVO.getStaus().equals(OUT_WAREHOUSE.getMsg())){
-                flag = false;
-                break;
-            }
-        }
-        if(flag){
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.COLLATERAL_DELIVERY.getLabel(),caseId);
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.DELIVERY_CONFIRMATION.getLabel(),caseId);
-            stepService.tryStartStep(StepPropertyEnum.DELIVERY_CONFIRMATION.getLabel(),caseId);
-        }
+        collateralPlanService.outboundCollateral(collateralPlan, comments);
 
         return ResultUtil.success("success","抵押物出库成功");
     }
+
+    @GetMapping("/channel/{caseId}")//x
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE','EXTERNAL')")
+    @ApiOperation("获取渠道信息")
+    public Result listChannelPushInfo(@PathVariable Long caseId,@RequestParam String flag){
+        return ResultUtil.success("success",channelPushService.listChannelPushInfo(caseId,flag));
+    }
+
+    @PostMapping("/channelPush")//x
+    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES','ASSIST_SALES')")
+    @ApiOperation("渠道推送")
+    public Result channelPush1(@RequestBody ChannelPushDTO channelPushDTO,@RequestParam(defaultValue = "false") Boolean isComplete){
+//        if (channelPushDTO == null || channelPushDTO.getFlag() == null || channelPushDTO.getCollateralIds()== null || channelPushDTO.getRecommenderId() == null)
+//            throw new DescribeException(INPUT_ERROR);
+//        if (channelPushDTO.getCaseId() ==  null || loanService.existsByIdAndIsDelete(channelPushDTO.getCaseId())== null)
+//            throw new DescribeException(PROJECT_NOT_EXIST);
+//
+//        Integer stepCode = channelPushDTO.getFlag() .equals(ENTER_WAREHOUSE.getMsg()) ? StepPropertyEnum.CHANNEL_PUSH.getCode() : StepPropertyEnum.CHANNEL_PUSH_2.getCode();
+//        StepVO stepVO = stepService.findByStepCodeAndCaseId(stepCode, channelPushDTO.getCaseId());
+//        if (ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+//            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+//
+//        channelPushService.channelPush(channelPushDTO,isComplete,stepCode);
+
+        return ResultUtil.success("success","推送成功");
+    }
+
+    @PostMapping("/channelConfirm/{channelId}")//x
+    @PreAuthorize("@pms.hasAnyRoles('EXTERNAL')")
+    @ApiOperation("渠道确认")
+    public Result channelConfirm(@PathVariable Long channelId,@RequestParam String  comments){
+//        ChannelPush channel = channelPushService.findByChannelId(channelId);
+//        if (channel == null )
+//            throw new DescribeException(COLLATERAL_PLAN_NOT_EXIST);
+//
+//        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.CHANNEL_CONFIRMATION.getCode(), channel.getCaseId());
+//        if (ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+//            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+//
+//        Long recommenderId = userRecommenderService.findByUserId(BaseContext.getCurrentId());
+//        if (channel.getRecommenderId() == null || channel.getRecommenderId() != recommenderId)
+//            throw new DescribeException(STEP_USER_NOT_EXPECTED);
+//
+//        channelPushService.channelConfirm(channel,comments,step.getCode());
+
+        return ResultUtil.success("success","推送成功");
+    }
+
+
+//    @PostMapping("/channelPush2")
+//    @PreAuthorize("@pms.hasAnyRoles('EXTERNAL')")
+//    @ApiOperation("渠道推送2")
+//    public Result channelPush2(){
+//
+//        return ResultUtil.success("success","推送成功");
+//    }
+
+    @PostMapping("/channelReceive/{channelId}")//x
+    @PreAuthorize("@pms.hasAnyRoles('EXTERNAL')")
+    @ApiOperation("渠道接收")
+    public Result channelReceive(@PathVariable Long channelId,@RequestParam String  comments){
+//        ChannelPush channel = channelPushService.findByChannelId(channelId);
+//        if (channel == null )
+//            throw new DescribeException(COLLATERAL_PLAN_NOT_EXIST);
+//
+//        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.CHANNEL_RECEIVE.getCode(), channel.getCaseId());
+//        if (ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
+//            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+//
+//        Long recommenderId = userRecommenderService.findByUserId(BaseContext.getCurrentId());
+//        if (channel.getRecommenderId() == null || channel.getRecommenderId() != recommenderId)
+//            throw new DescribeException(STEP_USER_NOT_EXPECTED);
+//
+//        channelPushService.channelReceive(channel,comments,step.getCode());
+
+        return ResultUtil.success("success","推送成功");
+    }
     /**删除抵押物
      *
      */
@@ -380,6 +279,7 @@ public class CollateralController {
                 .map(Long::valueOf) // 将字符串转换为 Long
                 .toArray(Long[]::new); // 转换为 Long 数组
 
+        //.filter(s -> !s.isEmpty()) // 过滤掉空字符串
         return Arrays.asList(collateralIds);
     }
 

+ 25 - 64
src/main/java/com/loan/system/controller/wechat/ContractController.java

@@ -2,10 +2,14 @@ package com.loan.system.controller.wechat;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.http.server.HttpServerResponse;
+import com.loan.system.constant.ContractConstant;
 import com.loan.system.context.BaseContext;
+import com.loan.system.domain.dto.ContractDTO;
+import com.loan.system.domain.dto.ContractWrapperDTO;
 import com.loan.system.domain.dto.DocumentDTO;
 import com.loan.system.domain.entity.Contract;
 import com.loan.system.domain.entity.Document;
+import com.loan.system.domain.enums.ContractEnum;
 import com.loan.system.domain.enums.ExceptionEnum;
 import com.loan.system.domain.pojo.ContractInformation;
 import com.loan.system.domain.vo.CustomerVO;
@@ -33,27 +37,20 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
-import static com.loan.system.domain.enums.ExceptionEnum.STEP_HAS_NOT_PROCESS;
-import static com.loan.system.domain.enums.ExceptionEnum.STEP_USER_NOT_EXPECTED;
+import static com.loan.system.domain.enums.ExceptionEnum.*;
 
 @RestController
 @RequestMapping("/wechat/contract")
 @Api(tags = "合同签订接口")
 public class ContractController {
-    @Value("${upload.templatePath}")
-    private String contractPath;
-
     @Autowired
     private ContractService contractService;
     @Autowired
     private StepService stepService;
     @Autowired
-    private DocumentService documentService;
-    @Autowired
-    private CustomerService customerService;
-    @Autowired
     private LoanService loanService;
 
     @GetMapping
@@ -71,18 +68,18 @@ public class ContractController {
         if(ObjectUtils.isEmpty(contractById))
             throw new DescribeException(ExceptionEnum.CONTRACT_NOT_EXIST);
 
-        getContractInformation(response,caseId,BeanUtil.copyProperties(contractById,ContractVO.class));
+        contractService.getContractInformation(response,caseId,BeanUtil.copyProperties(contractById,ContractVO.class));
 
     }
 
     @PostMapping("/{id}")
     @ApiOperation("推送合同")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
+    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
     public Result pushContract(@PathVariable("id") Long contractId,@RequestParam Long caseId){
         if(caseId == null||!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.CONTRACT_SIGN.getLabel(),caseId);
+        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.CONTRACT_SIGN.getCode(),caseId);
         if(ObjectUtils.isEmpty(step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
@@ -118,14 +115,14 @@ public class ContractController {
 
     @GetMapping("/customer")
     @ApiOperation("显示所有合同详情(客户)")
-    @PreAuthorize("@pms.hasAnyRoles('EXTERNAL')")
+    @PreAuthorize("@pms.hasAnyRoles('CUSTOMER')")
     public Result findContractsByCustomer(@RequestParam Long caseId) {
 
         return ResultUtil.success("success",contractService.findContractByCaseIdAndIsPush(caseId,true));
     }
     @GetMapping("/customer/{id}")
     @ApiOperation("显示合同文件详情(客户)")
-    @PreAuthorize("@pms.hasAnyRoles('EXTERNAL')")
+    @PreAuthorize("@pms.hasAnyRoles('CUSTOMER')")
     public void findContractsDetailByCustomer(@PathVariable("id") Long contractId ,@RequestParam Long caseId,HttpServletResponse response){
         if(caseId == null||!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
@@ -137,14 +134,14 @@ public class ContractController {
         if (!contractById.getIsPush())
             throw new DescribeException(ExceptionEnum.CONTRACT_NOT_PUSH);
 
-        getContractInformation(response,caseId,BeanUtil.copyProperties(contractById,ContractVO.class));
+        contractService.getContractInformation(response,caseId,BeanUtil.copyProperties(contractById,ContractVO.class));
     }
 
     //签署的电子信息先以附件上传
     //再修改合同状态
     @PostMapping("/{id}/sign")
-    @ApiOperation("签署合同")
-    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES','EXTERNAL')")
+    @ApiOperation("签署合同")//x
+    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
     public Result updateContract(@PathVariable("id")Long contractId,@RequestParam Long signId,@RequestParam Long commitedId){
         Contract contract = contractService.findContractById(contractId);
         if(ObjectUtils.isEmpty(contract))
@@ -163,8 +160,8 @@ public class ContractController {
     }
 
     @PostMapping("/{id}/signReset")
-    @ApiOperation("重设签署权限")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
+    @ApiOperation("重设签署权限")//x
+    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
     public Result updateContractStatus(@PathVariable("id")Long contractId,@RequestParam Long caseId){
         Contract contract = contractService.findContractById(contractId);
         if(ObjectUtils.isEmpty(contract))
@@ -173,7 +170,7 @@ public class ContractController {
         if(caseId == null||!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.CONTRACT_SIGN.getLabel(),caseId);
+        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.CONTRACT_SIGN.getCode(),caseId);
         if(ObjectUtils.isEmpty( step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
@@ -182,40 +179,22 @@ public class ContractController {
             throw new DescribeException(STEP_USER_NOT_EXPECTED);
         }
 
-        contractService.updateSignedCustomerById(contractId);
-        if(contract.getSignedId()!=null){
-            Document document = documentService.findById(contract.getSignedId());
-            String filePath = document.getFilePath();
-
-            if (filePath!=null){
-                try {
-                    Path path = Paths.get(filePath);
-                    boolean deleted = Files.deleteIfExists(path);
-                    if (deleted) {
-                        contractService.deleteSigned(contract.getSignedId());
-                        documentService.deleteFileById(contract.getSignedId());
-                        System.out.println("文件删除成功: " + filePath);
-                    } else {
-                        System.out.println("文件不存在或已被删除: " + filePath);
-                    }
-                } catch (IOException e) {
-                    System.err.println("删除文件时发生错误: " + e.getMessage());
-                    e.printStackTrace();
-                }
-            }
-        }
+        contractService.updateContractStatus(contract,caseId);
 
         return ResultUtil.success("success");
     }
 
     @PutMapping("/case/{caseId}")
     @ApiOperation("合同签订完成")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result completeContract(@PathVariable("caseId")Long caseId){
+    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
+    public Result completeContract(@PathVariable("caseId")Long caseId, @RequestBody ContractWrapperDTO contractWrapper){
         if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.CONTRACT_SIGN.getLabel(),caseId);
+        if(ObjectUtils.isEmpty(contractWrapper) || ObjectUtils.isEmpty(contractWrapper.getContractDTOS()))
+            throw new DescribeException(INPUT_ERROR);
+
+        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.CONTRACT_SIGN.getCode(),caseId);
         if(ObjectUtils.isEmpty( step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
@@ -224,28 +203,10 @@ public class ContractController {
             throw new DescribeException(STEP_USER_NOT_EXPECTED);
         }
 
-        //合同签约完成
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.CONTRACT_SIGN_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.CONTRACT_SIGN.getLabel(),caseId);
-        stepService.tryStartStep(StepPropertyEnum.CONTRACT_SIGN.getLabel(),caseId);
-
+        contractService.completeContract(caseId,contractWrapper);
 
         return ResultUtil.success("success");
     }
 
-    private void getContractInformation(HttpServletResponse response, Long caseId,ContractVO contractVO){
-        LoanCaseSimpleVO loancase = loanService.findLoanCaseSimpleByIdAndIsDelete(caseId, false);
-        CustomerVO customer = customerService.findByCustomerIdAndIsDelete(loancase.getCustomerId(),false);
-        ContractInformation contractInformation = BeanUtil.copyProperties(customer, ContractInformation.class);
-        if (contractVO.getSignedId() != null){
-            Document byId = documentService.findById(contractVO.getSignedId());
-            contractInformation.setSignaturePath1(byId.getFilePath());
-        }
-        contractInformation.setContractNo(contractVO.getContractNo());
-        contractInformation.setInterestRate(contractVO.getInterestRate());
-
-        String downloadName = contractVO.getContractName()+"-"+contractVO.getId();
-        new PoiWordUtil().writeApprove(response,contractPath+"template1.docx",contractInformation,downloadName);
-    }
 
 }

+ 2 - 1
src/main/java/com/loan/system/controller/wechat/CustomerController.java

@@ -21,7 +21,8 @@ public class CustomerController {
     @GetMapping
     @ApiOperation("查询所有客户")
     public Result findAllCustomers(@RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize){
-        return ResultUtil.success("success", customerService.getAllCustomers(pageNum, pageSize,false));
+        //已注册的客户才能办理
+        return ResultUtil.success("success", customerService.getAllCustomersAndIsRegister(pageNum, pageSize,true,false));
     }
 
     @GetMapping("/{id}")

+ 87 - 390
src/main/java/com/loan/system/controller/wechat/DisbursementController.java

@@ -1,6 +1,5 @@
 package com.loan.system.controller.wechat;
 
-import cn.hutool.core.bean.BeanUtil;
 import com.loan.system.context.BaseContext;
 import com.loan.system.domain.dto.*;
 import com.loan.system.domain.entity.*;
@@ -14,15 +13,13 @@ import com.loan.system.utils.RedisData;
 import com.loan.system.utils.ResultUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.*;
 
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 import static com.loan.system.domain.enums.ExceptionEnum.*;
@@ -30,6 +27,7 @@ import static com.loan.system.domain.enums.ExceptionEnum.*;
 @RestController
 @RequestMapping("/wechat/disbursement")
 @Api(tags = "出款接口")
+@Slf4j
 public class DisbursementController {
     @Autowired
     private DisbursementService disbursementService;
@@ -38,59 +36,18 @@ public class DisbursementController {
     @Autowired
     private LoanService loanService;
     @Autowired
-    private DocumentService documentService;
-    @Autowired
-    private ContractService contractService;
-    @Autowired
-    private ContractSeqService contractSeqService;
-    @Autowired
-    private ApprovalService approvalService;
-    @Autowired
     private UserService userService;
-    @Autowired
-    private DisbursementRecordService disbursementRecordService;
-    @Autowired
-    private CollateralService collateralService;
-    @Autowired
-    private StringRedisTemplate redisTemplate;
-    @Autowired
-    private PawnTicketService pawnTicketService;
 
-    @GetMapping("/planDetails")
-    @ApiOperation("显示计划详情")//与loanContraller的方法一致,但为了规范
+    @GetMapping("/allDetails")
+    @ApiOperation("显示所有详情")
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails(@RequestParam Long caseId){
+    public Result findAllDetails(@RequestParam Long caseId){
         if(caseId == null||!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        DisbursementDetailVO disbursementDetailVO = disbursementService.getLoanCaseAndCustomerByCaseId(caseId);
-        //填充
-        if(!ObjectUtils.isEmpty(disbursementDetailVO)){
-            Disbursement disbursementByCaseId = disbursementService.getDisbursementByCaseId(caseId);
-            if(!ObjectUtils.isEmpty(disbursementByCaseId)){
-                disbursementDetailVO.setDisbursementId(disbursementByCaseId.getId());
-                disbursementDetailVO.setDisbursementAmount(disbursementByCaseId.getPlannedAmount());
-                disbursementDetailVO.setCurrentLocation(disbursementByCaseId.getPlannedLocation());
-                disbursementDetailVO.setPlannedTime(disbursementByCaseId.getPlannedTime());
-                disbursementDetailVO.setPlannedComment(disbursementByCaseId.getPlannedComment());
-                disbursementDetailVO.setRejectComment(new RedisData(redisTemplate).getRejectApprovalRecord(caseId,StepPropertyEnum.PLAN_REPORT.getLabel()));
-            }
-            List<CollateralVO> collateralVOS = collateralService.findByCaseId(caseId);
-            List<String> collateralStatus =new ArrayList<>();
-            for(CollateralVO collateralVO:collateralVOS){
-                if(collateralVO.getStaus().isEmpty())
-                    collateralStatus.add("押品未取得");
-                else
-                    collateralStatus.add("押品已取得");
-            }
-            disbursementDetailVO.setCollateralStatus(collateralStatus);
-
-        }
-
-        return ResultUtil.success("success",disbursementDetailVO);
+        return ResultUtil.success("success",disbursementService.getAllDetails(caseId));
     }
 
-
     @PostMapping
     @ApiOperation(value = "上报出款计划")
     @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
@@ -101,62 +58,21 @@ public class DisbursementController {
         if(!loanService.existsByIdAndIsDelete(disbursementDTO.getCaseId()))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.PLAN_REPORT.getLabel(),disbursementDTO.getCaseId());
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PLAN_REPORT.getCode(),disbursementDTO.getCaseId());
         if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
+        disbursementService.addDisbursementPlan(disbursementDTO);
 
-        //上报计划
-        DisbursementVO detailsById = disbursementService.findDisbursementDetailsById(disbursementDTO.getCaseId());
-        if(!ObjectUtils.isEmpty(detailsById))
-            disbursementService.updateDisbursementByCaseId(disbursementDTO);
-        else
-            disbursementService.addDisbursement(disbursementDTO);
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.PLAN_REPORT.getLabel(), BaseContext.getCurrentId(), disbursementDTO.getCaseId());
-        stepService.updateStatusByCaseId( StepEnum.COMPLETED.getMsg(), StepPropertyEnum.PLAN_REPORT.getLabel(),disbursementDTO.getCaseId());
-        new RedisData(redisTemplate).deleteApprovalByKey(disbursementDTO.getCaseId(),StepPropertyEnum.PLAN_REPORT.getLabel());
-        stepService.tryStartStep(StepPropertyEnum.PLAN_REPORT.getLabel(),disbursementDTO.getCaseId());
         return ResultUtil.success("success");
     }
 
-    @GetMapping("/approvalDetails")
-    @ApiOperation("显示计划审批详情")//与loanContraller的方法一致,但为了规范
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails2(@RequestParam Long caseId){
-        if(caseId == null||!loanService.existsByIdAndIsDelete(caseId))
-            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
-
-        DisbursementDetailVO disbursementDetailVO = disbursementService.getLoanCaseAndCustomerByCaseId(caseId);
-        //填充
-        if(!ObjectUtils.isEmpty(disbursementDetailVO)){
-            Disbursement disbursementByCaseId = disbursementService.getDisbursementByCaseId(caseId);
-            if(!ObjectUtils.isEmpty(disbursementByCaseId)){
-                disbursementDetailVO.setDisbursementAmount(disbursementByCaseId.getPlannedAmount());
-                disbursementDetailVO.setCurrentLocation(disbursementByCaseId.getPlannedLocation());
-                disbursementDetailVO.setPlannedTime(disbursementByCaseId.getPlannedTime());
-            }
-
-            List<ApprovalRecordVO> approvalRecord = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId,StepPropertyEnum.PLAN_AUDIT.getLabel(),BaseContext.getCurrentId());
-            if(!ObjectUtils.isEmpty(approvalRecord))
-                disbursementDetailVO.setApprovalRecords1(approvalRecord);
-        }
-
-        return ResultUtil.success("success",disbursementDetailVO);
-
-    }
-
     //TODO:选择回款业务员-在userController中
 
     @PostMapping("/approval/pass1")
     @ApiOperation(value = "计划审批")
     @PreAuthorize("@pms.hasRole('APPROVER')")
-    public Result disbursementPlanApproval(@RequestBody ApprovalRecordDTO approvalRecordDTO, @RequestParam Long chargeId) {
+    public Result disbursementPlanApproval(@RequestBody ApprovalRecordDTO approvalRecordDTO, @RequestParam Long chargeId ,@RequestParam(required = false) Long assistantId) {
         if (ObjectUtils.isEmpty(approvalRecordDTO)||approvalRecordDTO.getCaseId()==null)
             throw new DescribeException(INPUT_ERROR);
 
@@ -164,41 +80,18 @@ public class DisbursementController {
         if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.PLAN_AUDIT.getLabel(),caseId);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PLAN_AUDIT.getCode(),caseId);
         if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
         //选择回款负责人,开启回款开始环节,创建回款单
         if(chargeId == null || !userService.existsByIdAndIsDelete(chargeId))
             throw new DescribeException(ExceptionEnum.USER_NOT_EXIST);
+        //如果辅办人员id存在但用户不存在,则报错
+        if (assistantId != null && !userService.existsByIdAndIsDelete(assistantId))
+            throw new DescribeException(ASSISTANT__NOT_EXIST);
 
-        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.DISBURSE_START.getLabel(),chargeId,caseId);
-
-        //填写审批意见
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.PLAN_AUDIT.getLabel(),DecisionEnum.PASS.getMsg(),approvalRecordDTO.getComments());
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.PLAN_AUDIT.getLabel(), BaseContext.getCurrentId(), caseId);
-        stepService.updateStatusByCaseId( StepEnum.COMPLETED.getMsg(),StepPropertyEnum.PLAN_AUDIT.getLabel(),caseId);
-        //stepService.updateStatusByCaseId( StepEnum.PROCESS.getMsg(), StepPropertyEnum.DISBURSE_START.getLabel(),caseId);
-        stepService.tryStartStep(StepPropertyEnum.PLAN_AUDIT.getLabel(),caseId);
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
-
+        disbursementService.disbursementPlanApproval(approvalRecordDTO,chargeId,assistantId);
 
         return ResultUtil.success("success");
     }
@@ -207,145 +100,57 @@ public class DisbursementController {
     @ApiOperation(value = "计划审批驳回")
     @PreAuthorize("@pms.hasRole('APPROVER')")
     public Result disbursementPlanApprovalReject(@RequestBody ApprovalRecordDTO approvalRecordDTO) {
-        if (ObjectUtils.isEmpty(approvalRecordDTO)||approvalRecordDTO.getCaseId()==null)
+        if (ObjectUtils.isEmpty(approvalRecordDTO)||approvalRecordDTO.getCaseId()==null || approvalRecordDTO.getRecordId() == null)
             throw new DescribeException(INPUT_ERROR);
 
         Long caseId = approvalRecordDTO.getCaseId();
         if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.PLAN_AUDIT.getLabel(),caseId);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.PLAN_AUDIT.getCode(),caseId);
         if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
-
-        //填写驳回
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.PLAN_AUDIT.getLabel(),DecisionEnum.REJECT.getMsg(),approvalRecordDTO.getComments());
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.PLAN_AUDIT.getLabel(), BaseContext.getCurrentId(), caseId);
-        stepService.updateStatusByCaseId( StepEnum.PROCESS.getMsg(), StepPropertyEnum.PLAN_REPORT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(), StepPropertyEnum.PLAN_AUDIT.getLabel(),caseId);
-        new RedisData(redisTemplate).setRejectApprovalRecord(caseId,StepPropertyEnum.PLAN_REPORT.getLabel(),approvalRecordDTO.getComments());
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        StepVO stepVO1 = stepService.findByStepNameAndCaseId(StepPropertyEnum.PLAN_REPORT.getLabel(), caseId);
-//        message.setMobile(userService.findByIdAndIsDelete(stepVO1.getUserId1()).getMobile());
-//        message.setUserRole(null);
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName(StepPropertyEnum.PLAN_AUDIT.getLabel());
-//        message.setRelatedId(caseId);
-//        message.setRelatedType("");
- //       messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
+        disbursementService.disbursementPlanApprovalReject(approvalRecordDTO);
 
         return ResultUtil.success("success");
     }
 
-    //上传文件UploadController
-    //显示文件列表
-
-    @GetMapping(("/commitDetail"))
-    @ApiOperation("显示出款启动详情")
-    public Result findLoanCaseDetails3(@RequestParam Long caseId) {
-        //TODO:待修改
-        DisbursementDetailVO disbursementDetailVO = new DisbursementDetailVO();
-
-        DisbursementVO detailsById = disbursementService.findDisbursementDetailsById(caseId);
-        if (detailsById != null)
-            disbursementDetailVO.setDisbursementComment(detailsById.getDisbursementComment());
-
-        List<PawnTicketInfo> pawnTicketInfoList = pawnTicketService.findByCaseIdAndIsDelete(caseId);
-        Map<Long,String> contractIdAndPawn = new HashMap<>();
-        for (PawnTicketInfo pawnTicketInfo : pawnTicketInfoList) {
-            Long contractId = pawnTicketInfo.getContractId();
-            String pawnTicketNo = pawnTicketInfo.getPawnTicketNo();
-            contractIdAndPawn.put(contractId,pawnTicketNo);
-        }
-
-        disbursementDetailVO.setContractAndPawn(contractIdAndPawn);
-        disbursementDetailVO.setRejectComment(new RedisData(redisTemplate).getRejectApprovalRecord(caseId,StepPropertyEnum.DISBURSE_START.getLabel()));
-        return ResultUtil.success("success",disbursementDetailVO);
-    }
-
-    @PutMapping("/commit")
+    @PostMapping("/commit")
     @ApiOperation(value = "出款启动")
-    @PreAuthorize("@pms.hasAnyRoles('APPROVER','LEAD_SALES')")
-    public Result commitDisbursement(@RequestBody DisbursementStartDTO disbursementStartDTO) {
-        if (ObjectUtils.isEmpty(disbursementStartDTO)||disbursementStartDTO.getCaseId()==null)
+    @PreAuthorize("@pms.hasAnyRoles('ASSIST_SALES','LEAD_SALES')")
+    public Result commitDisbursement(@RequestBody DisbursementStartDTO disbursementStartDTO,@RequestParam(defaultValue = "false") Boolean isComplete) {
+        if (ObjectUtils.isEmpty(disbursementStartDTO)||ObjectUtils.isEmpty(disbursementStartDTO.getDisbursementRecordDTO())||
+        disbursementStartDTO.getDisbursementRecordDTO().getCaseId()== null)
             throw new DescribeException(INPUT_ERROR);
-        Long caseId = disbursementStartDTO.getCaseId();
+        Long caseId = disbursementStartDTO.getDisbursementRecordDTO().getCaseId();
         if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.DISBURSE_START.getLabel(),caseId);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.DISBURSE_START.getCode(),caseId);
         if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
-
-        //TODO:添加当票信息
-        pawnTicketService.addPawnTicket(disbursementStartDTO.getContractAndPawn(),caseId);
-        disbursementService.updateDisbursementCommentsByCaseId(disbursementStartDTO.getComment(),caseId);
-        new RedisData(redisTemplate).deleteApprovalByKey(caseId,StepPropertyEnum.DISBURSE_START.getLabel());
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.DISBURSE_START.getLabel(), BaseContext.getCurrentId(), caseId);
-        stepService.updateStatusByCaseId( StepEnum.COMPLETED.getMsg(),StepPropertyEnum.DISBURSE_START.getLabel(),caseId);
-        stepService.tryStartStep(StepPropertyEnum.DISBURSE_START.getLabel(),caseId);
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
-
-
-        return ResultUtil.success("success");
-    }
-
-    @GetMapping("/approvalDetails2")
-    @ApiOperation("显示出款审批详情")//与loanContraller的方法一致,但为了规范
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetail3(@RequestParam Long caseId){
-        if(caseId == null||!loanService.existsByIdAndIsDelete(caseId))
-            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
-
-        DisbursementDetailVO disbursementDetailVO = disbursementService.getLoanCaseAndCustomerByCaseId(caseId);
-        //填充
-        if(!ObjectUtils.isEmpty(disbursementDetailVO)){
-            Disbursement disbursementByCaseId = disbursementService.getDisbursementByCaseId(caseId);
-            if(!ObjectUtils.isEmpty(disbursementByCaseId)){
-                disbursementDetailVO.setDisbursementAmount(disbursementByCaseId.getPlannedAmount());
-                disbursementDetailVO.setCurrentLocation(disbursementByCaseId.getPlannedLocation());
-                disbursementDetailVO.setPlannedTime(disbursementByCaseId.getPlannedTime());
+        if (stepVO.getUserIds() != null){
+            boolean flag = false;//是否存在
+            String[] userIds = stepVO.getUserIds().split(",");//分主办与辅办,但仅需任意一人操作即可
+            for (String userId : userIds){
+                Long currentId = Long.valueOf(userId);
+                if(BaseContext.getCurrentId().equals(currentId)){
+                    flag = true;
+                    break;
+                }
             }
-
-            List<ApprovalRecordVO> approvalRecord = approvalService.findByCaseIdAndStepNameAndApproverIdAndIsDelete(caseId,StepPropertyEnum.DISBURSE_AUDIT.getLabel(),BaseContext.getCurrentId());
-            if(!ObjectUtils.isEmpty(approvalRecord))
-                disbursementDetailVO.setApprovalRecords2(approvalRecord);
+            if(!flag)
+                //全部遍历结束未break,表示不是预期用户
+                throw new DescribeException(STEP_USER_NOT_EXPECTED);
         }
 
-        return ResultUtil.success("success",disbursementDetailVO);
+        disbursementService.commitDisbursement(disbursementStartDTO,isComplete);
 
+        return ResultUtil.success("success");
     }
 
-
     @PostMapping("/approval/pass2")
     @ApiOperation(value = "出款审批")
     @PreAuthorize("@pms.hasRole('APPROVER')")
@@ -357,25 +162,11 @@ public class DisbursementController {
         if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.DISBURSE_AUDIT.getLabel(),caseId);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.DISBURSE_AUDIT.getCode(),caseId);
         if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
-
-        //填写审批意见
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.DISBURSE_AUDIT.getLabel(),DecisionEnum.PASS.getMsg(),approvalRecordDTO.getComments());
-
-        //修改出款单
-        disbursementService.updateApprovalUserById(BaseContext.getCurrentId(),disbursementService.getDisbursementByCaseId(caseId).getId());
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.DISBURSE_AUDIT.getLabel(), BaseContext.getCurrentId(), caseId);
-        stepService.updateStatusByCaseId( StepEnum.COMPLETED.getMsg(),StepPropertyEnum.DISBURSE_AUDIT.getLabel(),caseId);
-        stepService.tryStartStep(StepPropertyEnum.DISBURSE_AUDIT.getLabel(),caseId);
+        disbursementService.disbursementApproval(approvalRecordDTO,DecisionEnum.PASS.getMsg());
 
         return ResultUtil.success("success");
     }
@@ -391,182 +182,88 @@ public class DisbursementController {
         if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.DISBURSE_AUDIT.getLabel(),caseId);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.DISBURSE_AUDIT.getCode(),caseId);
         if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
-
-//        //TODO:专门用于审批驳回,驳回后若上个步骤未重新提交,则无法审批
-//        StepVO stepVO1 = stepService.findByStepNameAndCaseId(StepPropertyEnum.DISBURSE_START.getLabel(),caseId);
-//        if (stepVO1.getStatus().equals(StepEnum.PROCESS.getMsg()))
-//            throw new DescribeException(PRE_STEP_NOT_COMPLETE);
-
-        //填写驳回意见
-        approvalService.addApprovalRecord(caseId,StepPropertyEnum.DISBURSE_AUDIT.getLabel(),DecisionEnum.REJECT.getMsg(),approvalRecordDTO.getComments());
-
-        //修改出款单
-        disbursementService.updateApprovalUserById(BaseContext.getCurrentId(),disbursementService.getDisbursementByCaseId(caseId).getId());
-        new RedisData( redisTemplate).setRejectApprovalRecord(caseId,StepPropertyEnum.DISBURSE_START.getLabel(),approvalRecordDTO.getComments());
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.DISBURSE_AUDIT.getLabel(), BaseContext.getCurrentId(), caseId);
-        stepService.updateStatusByCaseId( StepEnum.PROCESS.getMsg(),StepPropertyEnum.DISBURSE_START.getLabel(),caseId);//重新出款
-        stepService.updateStatusByCaseId( StepEnum.UNSTART.getMsg(),StepPropertyEnum.DISBURSE_AUDIT.getLabel(),caseId);
+        disbursementService.disbursementApproval(approvalRecordDTO,DecisionEnum.REJECT.getMsg());
 
         return ResultUtil.success("success");
     }
 
-    @GetMapping("/financeDetails")
-    @ApiOperation("显示财务出款详情")//与loanContraller的方法一致,但为了规范
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails4(@RequestParam Long caseId){
-        if(caseId == null || !loanService.existsByIdAndIsDelete(caseId))
+//    @PostMapping("/amount")
+//    @ApiOperation(value = "财务出款")
+//    @PreAuthorize("@pms.hasRole('FINANCE')")//TODO:多次出款,负责人可能不一样
+//    public Result financeDisbursement(@RequestBody DisbursementRecordListWrapper disbursementRecordListWrapper, @RequestParam Long caseId, @RequestParam Double currentAmount) {
+//        List<DisbursementRecordDTO> disbursementRecordDTOs = disbursementRecordListWrapper.getDisbursementRecordDTOs();
+//        if (ObjectUtils.isEmpty(disbursementRecordDTOs)||disbursementRecordDTOs.get(0).getDisbursementId() == null)
+//            throw new DescribeException(INPUT_ERROR);
+//
+//        if(!loanService.existsByIdAndIsDelete(caseId))
+//            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
+//
+//        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.FINANCE_DISBURSE.getCode(),caseId);
+//        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+//            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+//
+//        disbursementService.financeDisbursement(disbursementRecordDTOs,caseId,currentAmount);
+//
+//        return ResultUtil.success("success");
+//    }
+
+    @PostMapping("/amount/pass")
+    @ApiOperation(value = "财务复核通过")
+    @PreAuthorize("@pms.hasRole('FINANCE')")//TODO:多次出款,负责人可能不一样
+    public Result financeDisbursementPass( @RequestBody ApprovalRecordDTO approvalRecordDTO) {
+        if (ObjectUtils.isEmpty(approvalRecordDTO)||approvalRecordDTO.getCaseId() == null)
+            throw new DescribeException(INPUT_ERROR);
+
+        Long caseId = approvalRecordDTO.getCaseId();
+        if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        DisbursementDetailVO disbursementDetailVO = disbursementService.getLoanCaseAndCustomerByCaseId(caseId);
-        //填充
-        if(!ObjectUtils.isEmpty(disbursementDetailVO)){
-            Disbursement disbursementByCaseId = disbursementService.getDisbursementByCaseId(caseId);
-            disbursementDetailVO.setDisbursementId(disbursementByCaseId.getId());
-            if(!ObjectUtils.isEmpty(disbursementByCaseId)){
-                List<DisbursementRecordVO> records = disbursementRecordService.findRecordById(disbursementByCaseId.getId());
-
-                double currentAmount = 0;
-                for (int i = 0; i < records.size(); i++){
-                    DisbursementRecordVO record = records.get(i);
-                    record.setFinanceName(userService.findByIdAndIsDelete(record.getFinanceId()).getRealName());
-                    currentAmount += record.getAmount();
-                }
-                disbursementDetailVO.setDisbursementRecords(records);
-                disbursementDetailVO.setCurrentAmount(currentAmount);
-                disbursementDetailVO.setTotalAmount(disbursementByCaseId.getPlannedAmount());
-            }
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.FINANCE_DISBURSE.getCode(),caseId);
+        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+            throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        }
 
-        return ResultUtil.success("success",disbursementDetailVO);
+        disbursementService.financeApproval(approvalRecordDTO,DecisionEnum.PASS.getMsg());
+
+        return ResultUtil.success("success");
     }
 
-    @PostMapping("/amount")
-    @ApiOperation(value = "财务出款")
+    @PostMapping("/amount/reject")
+    @ApiOperation(value = "财务复核拒绝")
     @PreAuthorize("@pms.hasRole('FINANCE')")//TODO:多次出款,负责人可能不一样
-    public Result financeDisbursement(@RequestBody DisbursementRecordListWrapper disbursementRecordListWrapper, @RequestParam Long caseId, @RequestParam Double currentAmount) {
-        List<DisbursementRecordDTO> disbursementRecordDTOs = disbursementRecordListWrapper.getDisbursementRecordDTOs();
-        System.out.println(disbursementRecordDTOs);
-        if (ObjectUtils.isEmpty(disbursementRecordDTOs)||disbursementRecordDTOs.get(0).getDisbursementId() == null)
+    public Result financeDisbursement(@RequestBody ApprovalRecordDTO approvalRecordDTO) {
+        if (ObjectUtils.isEmpty(approvalRecordDTO)||approvalRecordDTO.getCaseId() == null)
             throw new DescribeException(INPUT_ERROR);
 
+        Long caseId = approvalRecordDTO.getCaseId();
         if(!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.FINANCE_DISBURSE.getLabel(),caseId);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.FINANCE_DISBURSE.getCode(),caseId);
         if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Long disbursementId = disbursementRecordDTOs.get(0).getDisbursementId();
-        double totalAmount=0.0;
-        for(DisbursementRecordDTO disbursementRecordDTO:disbursementRecordDTOs) {
-            totalAmount += disbursementRecordDTO.getAmount();
-            disbursementRecordDTO.setDisbursementId(disbursementId);//防止列表中的某一个id为空,导致记录关联失败
-        }
-
-        if ((currentAmount + totalAmount) > (disbursementService.getDisbursementByCaseId(caseId).getPlannedAmount() + 0.1))
-                throw new DescribeException(ExceptionEnum.AMOUNT_HAS_EXCEED_PLANNED_AMOUNT);
-
-        //添加出款记录 TODO:不能将这两个循环合并,总金额过多会报错,导致添加的记录无法回滚。
-        for (DisbursementRecordDTO disbursementRecordDTO:disbursementRecordDTOs)
-            disbursementRecordService.addDisbursementRecord(disbursementRecordDTO);
-
-        //修改状态(出款金额达标才修改)
-        if(Math.abs(currentAmount+totalAmount-disbursementService.getDisbursementByCaseId(caseId).getPlannedAmount())<=Double.MIN_VALUE){
-            stepService.updateStatusByCaseId( StepEnum.COMPLETED.getMsg(),StepPropertyEnum.FINANCE_DISBURSE.getLabel(),caseId);
-            stepService.tryStartStep(StepPropertyEnum.FINANCE_DISBURSE.getLabel(),caseId);
-        }
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
+        disbursementService.financeApproval(approvalRecordDTO,DecisionEnum.REJECT.getMsg());
 
         return ResultUtil.success("success");
     }
 
-    @GetMapping("/confirmDetails")
-    @ApiOperation("显示业务确认详情")//与loanContraller的方法一致,但为了规范
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails5(@RequestParam Long caseId){
-        if(caseId == null ||!loanService.existsByIdAndIsDelete(caseId))
-            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
-
-        DisbursementDetailVO disbursementDetailVO = disbursementService.getLoanCaseAndCustomerByCaseId(caseId);
-        //填充
-       if(!ObjectUtils.isEmpty(disbursementDetailVO)){
-           Disbursement disbursement = disbursementService.getDisbursementByCaseId(caseId);
-           if(!ObjectUtils.isEmpty(disbursement)){
-               List<DisbursementRecordVO> records = disbursementRecordService.findRecordById(disbursement.getId());
-               disbursementDetailVO.setDisbursementRecords(records);
-
-               disbursementDetailVO.setTotalAmount(disbursement.getPlannedAmount());
-               disbursementDetailVO.setConfirmComment(disbursement.getConfirmComment());
-               
-           }
-       }
-
-        return ResultUtil.success("success",disbursementDetailVO);
-    }
-
     @PostMapping("/confirm")
     @ApiOperation(value = "业务确认")
     @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
-    public Result confirmDisbursement(@RequestParam("caseId") Long caseId,@RequestParam("comment") String comment) {
+    public Result confirmDisbursement(@RequestParam List<Long> recordIds,@RequestParam Long caseId,@RequestParam("comment") String comment) {
         if(caseId == null ||!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.DISBURSE_CONFIRM.getLabel(),caseId);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.DISBURSE_CONFIRM.getCode(),caseId);
         if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
-
-        //设置说明
-        disbursementService.updateCommentsById(comment,disbursementService.getDisbursementByCaseId(caseId).getId());
-        disbursementService.updateStatusByCaseId(DecisionEnum.DISBURSEMENT_COMPLETE.getMsg(),caseId);
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.DISBURSE_CONFIRM.getLabel(), BaseContext.getCurrentId(), caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(), StepPropertyEnum.DISBURSE_PARENT.getLabel(), caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.DISBURSE_CONFIRM.getLabel(), caseId);
-        stepService.tryStartStep(StepPropertyEnum.DISBURSE_CONFIRM.getLabel(),caseId);
-
-        //填写合同编号
-        List<ContractVO> contractByCaseId = contractService.findContractByCaseId(caseId);
-        String today = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
-
-        contractByCaseId.forEach(contractVO -> {
-            String prefix= contractVO.getBusinessAttr().equals(BusinessAttrEnum.ESTATE_MORTGAGE.getMsg()) ? "宝押字": "宝质字";
-
-            ContractSeq contractSeq = contractSeqService.findSeqBySeqDataAndBusinessAttr(today,prefix);
-            if (ObjectUtils.isEmpty(contractSeq))
-                contractSeq =contractSeqService.addSeq(today,prefix);
-
-            //合同编号
-            String contractNo = String.format("%s%s-%s-%03d",
-                    prefix,
-                    today.substring(0, 4),        // 年份
-                    today.substring(4),           // 月日
-                    contractSeq.getNextSeq());
-
-            //更新号码
-            contractSeqService.updateSeqById(contractSeq.getId(),contractSeq.getNextSeq()+1);
-
-            //更新合同编号
-            contractService.updateContractNoById(contractVO.getId(),contractNo);
-        });
+        disbursementService.confirmDisbursement(recordIds,caseId, comment);
 
         return ResultUtil.success("success");
     }

+ 413 - 0
src/main/java/com/loan/system/controller/wechat/EsignController.java

@@ -0,0 +1,413 @@
+package com.loan.system.controller.wechat;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.loan.system.config.FileUploadConfig;
+import com.loan.system.domain.dto.EsignCreateFlowDTO;
+import com.loan.system.domain.dto.EsignFillTemplateDTO;
+import com.loan.system.domain.dto.EsignTemplateFillDTO;
+import com.loan.system.domain.entity.Contract;
+import com.loan.system.domain.entity.Document;
+import com.loan.system.domain.enums.ContractEnum;
+import com.loan.system.domain.pojo.ContractInformation;
+import com.loan.system.domain.pojo.Result;
+import com.loan.system.domain.vo.CustomerVO;
+import com.loan.system.domain.vo.EsignFlowVO;
+import com.loan.system.domain.vo.LoanCaseSimpleVO;
+import com.loan.system.service.*;
+import com.loan.system.utils.EsignContractUtil;
+import com.loan.system.utils.ResultUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * e签宝电子签署Controller
+ */
+@Slf4j
+@RestController
+@RequestMapping("/wechat/esign")
+@Api(tags = "e签宝电子签署接口")
+@RequiredArgsConstructor
+public class EsignController {
+    
+    @Autowired
+    private EsignService esignService;
+
+    @Value("${upload.templatePath}")
+    private String contractPath;
+    @Autowired
+    private ContractService contractService;
+
+    /**
+     * 查询流程状态
+     */
+    @GetMapping("/flow/{flowId}/status")
+    @ApiOperation("查询流程状态")
+    public Result queryFlowStatus(@PathVariable String flowId) {
+        Map<String, Object> status = esignService.queryFlowStatus(flowId);
+        if (status != null) {
+            return ResultUtil.success("查询成功", status);
+        }
+        return ResultUtil.error(500, "查询失败");
+    }
+    
+    /**
+     * TODO:1.1沙盒测试环境填充模板(方案一:标准认证授权流程)
+     * 填充合同模板并创建签署流程(完整流程)
+     * 注意:使用此接口前,必须确保签署人已完成实名认证
+     */
+    @PostMapping("/flow/createFromTemplate")
+    @ApiOperation("填充合同模板并创建签署流程")
+    public Result createFlowFromTemplateSimplified(@RequestBody EsignFillTemplateDTO dto) {
+        try {
+            // 1. 填充模板并上传到e签宝
+            String fullTemplatePath = "template1.docx";
+            fullTemplatePath = contractPath + fullTemplatePath;
+
+            String fileId = EsignContractUtil.fillTemplateAndUpload(
+                    fullTemplatePath,
+                    contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()),
+                    dto.getContractName() != null ? dto.getContractName() : "合同",
+                    esignService
+            );
+            
+            if (fileId == null || fileId.isEmpty()) {
+                return ResultUtil.error(500, "模板填充并上传失败");
+            }
+            
+            // 2. 创建签署流程(使用简化流程)
+            EsignCreateFlowDTO flowDTO = new EsignCreateFlowDTO();
+            flowDTO.setContractId(dto.getContractId());
+            flowDTO.setCaseId(dto.getCaseId());
+            flowDTO.setFileId(fileId);
+            flowDTO.setDocumentUrl(fileId);
+            flowDTO.setDocumentName((dto.getContractName() != null ? dto.getContractName() : "合同") + ".pdf");
+            flowDTO.setCustomerName(dto.getCustomerName());
+            flowDTO.setCustomerMobile(dto.getCustomerMobile());
+            flowDTO.setCustomerIdNumber(dto.getCustomerIdNumber());
+            flowDTO.setBusinessName(dto.getBusinessName());
+            flowDTO.setBusinessMobile(dto.getBusinessMobile());
+            flowDTO.setBusinessIdNumber(dto.getBusinessIdNumber());
+            flowDTO.setSignDeadline(dto.getSignDeadline());
+            flowDTO.setRemark(dto.getRemark());
+            
+            String flowId = esignService.createSignFlowSimplified(flowDTO);
+            if (flowId != null) {
+                return ResultUtil.success("模板填充并创建签署流程成功(简化流程)", flowId);
+            }
+            return ResultUtil.error(500, "创建签署流程失败");
+        } catch (Exception e) {
+            log.error("填充模板并创建签署流程异常(简化流程)", e);
+            return ResultUtil.error(500, "处理失败: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 仅填充模板并上传到e签宝(不上传签署流程)
+     */
+    @PostMapping("/template/upload")
+    @ApiOperation("填充合同模板并上传到e签宝")
+    public Result fillTemplateAndUpload(@RequestBody EsignFillTemplateDTO dto) {
+        try {
+            String fullTemplatePath = "template1.docx";
+
+            // 如果模板路径是相对路径,拼接配置的模板路径
+            fullTemplatePath =contractPath + fullTemplatePath;
+            
+            String fileId = EsignContractUtil.fillTemplateAndUpload(
+                    fullTemplatePath,
+                    contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()),
+                    dto.getContractName() != null ? dto.getContractName() : "合同",
+                    esignService
+            );
+            
+            if (fileId != null) {
+                return ResultUtil.success("模板填充并上传成功", fileId);
+            }
+            return ResultUtil.error(500, "模板填充并上传失败");
+        } catch (Exception e) {
+            log.error("填充模板并上传异常", e);
+            return ResultUtil.error(500, "处理失败: " + e.getMessage());
+        }
+    }
+    /**
+    
+    /**
+     * TODO:1.2 生产环境填充模板
+     * 使用e签宝模板填充字段并创建签署流程
+     */
+    @PostMapping("/flow/createFromEsignTemplate")
+    @ApiOperation("使用e签宝模板填充字段并创建签署流程")
+    public Result createFlowFromEsignTemplate(@RequestBody @Validated EsignTemplateFillDTO dto) {
+        try {
+            // 1. 构建字段映射
+            Map<String, String> formFields = EsignContractUtil.convertContractToTemplateFields(contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()));
+            
+            // 2. 使用e签宝模板填充字段并生成文件
+            String fileId = esignService.fillTemplateAndGenerateFile(
+                dto.getTemplateId(),
+                dto.getContractName() + ".pdf",
+                formFields
+            );
+            
+            if (fileId == null || fileId.isEmpty()) {
+                return ResultUtil.error(500, "模板填充并生成文件失败");
+            }
+            
+            // 3. 创建签署流程
+            EsignCreateFlowDTO flowDTO = new EsignCreateFlowDTO();
+            flowDTO.setContractId(dto.getContractId());
+            flowDTO.setCaseId(dto.getCaseId());
+            flowDTO.setFileId(fileId);
+            flowDTO.setDocumentName(dto.getContractName() + ".pdf");
+            flowDTO.setCustomerName(dto.getCustomerName());
+            flowDTO.setCustomerMobile(dto.getCustomerMobile());
+            flowDTO.setCustomerIdNumber(dto.getCustomerIdNumber());
+            flowDTO.setBusinessName(dto.getBusinessName());
+            flowDTO.setBusinessMobile(dto.getBusinessMobile());
+            flowDTO.setBusinessIdNumber(dto.getBusinessIdNumber());
+            flowDTO.setSignDeadline(dto.getSignDeadline());
+            flowDTO.setRemark(dto.getRemark());
+            
+            String flowId = esignService.createSignFlow(flowDTO);
+            
+            if (flowId == null || flowId.isEmpty()) {
+                return ResultUtil.error(500, "创建签署流程失败");
+            }
+            
+            log.info("使用e签宝模板创建签署流程成功,templateId: {}, flowId: {}", dto.getTemplateId(), flowId);
+            return ResultUtil.success("使用e签宝模板创建签署流程成功", flowId);
+            
+        } catch (Exception e) {
+            log.error("使用e签宝模板创建签署流程异常", e);
+            return ResultUtil.error(500, "创建签署流程异常: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 仅使用e签宝模板填充字段生成文件(不上传,不创建流程)
+     */
+    @PostMapping("/template/fill")
+    @ApiOperation("使用e签宝模板填充字段生成文件")
+    public Result fillEsignTemplate(@RequestBody @Validated EsignTemplateFillDTO dto) {
+        try {
+            // 构建字段映射
+            Map<String, String> formFields = EsignContractUtil.convertContractToTemplateFields(contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()));
+            
+            // 使用e签宝模板填充字段并生成文件
+            String fileId = esignService.fillTemplateAndGenerateFile(
+                dto.getTemplateId(),
+                dto.getContractName() + ".pdf",
+                formFields
+            );
+            
+            if (fileId == null || fileId.isEmpty()) {
+                return ResultUtil.error(500, "模板填充并生成文件失败");
+            }
+            
+            log.info("使用e签宝模板填充字段成功,templateId: {}, fileId: {}", dto.getTemplateId(), fileId);
+            return ResultUtil.success("模板填充并生成文件成功", fileId);
+            
+        } catch (Exception e) {
+            log.error("使用e签宝模板填充字段异常", e);
+            return ResultUtil.error(500, "模板填充异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * TODO 2.签署流程(方案一:标准认证授权流程)
+     * 创建签署流程(使用已上传的文件ID)
+     * 注意:使用此接口前,必须确保签署人已完成实名认证
+     */
+    @PostMapping("/flow/create")
+    @ApiOperation("创建签署流程(方案一)")
+    public Result createSignFlow(@RequestBody EsignCreateFlowDTO dto) {
+        // 优先使用fileId,如果没有则使用documentUrl
+        if (dto.getFileId() == null && dto.getDocumentUrl() != null) {
+            dto.setFileId(dto.getDocumentUrl());
+        }
+
+        String flowId = esignService.createSignFlow(dto);
+        if (flowId != null) {
+            return ResultUtil.success("创建签署流程成功", flowId);
+        }
+        return ResultUtil.error(500, "创建签署流程失败");
+    }
+        
+    /**
+     * 【方案二】创建签署流程(简化流程)
+     * 此接口不需要提前引导用户实名认证,签署时由e签宝自动处理
+     */
+    @PostMapping("/flow/createSimplified")
+    @ApiOperation("创建签署流程(方案二-简化流程)")
+    public Result createSignFlowSimplified(@RequestBody EsignCreateFlowDTO dto) {
+        // 优先使用fileId,如果没有则使用documentUrl
+        if (dto.getFileId() == null && dto.getDocumentUrl() != null) {
+            dto.setFileId(dto.getDocumentUrl());
+        }
+
+        String flowId = esignService.createSignFlowSimplified(dto);
+        if (flowId != null) {
+            return ResultUtil.success("创建签署流程成功(简化流程)", flowId);
+        }
+        return ResultUtil.error(500, "创建签署流程失败");
+    }
+    /**
+     * 启动签署流程
+     */
+    @PostMapping("/flow/{flowId}/start")
+    @ApiOperation("启动签署流程")
+    public Result startSignFlow(@PathVariable String flowId) {
+        boolean success = esignService.startSignFlow(flowId);
+        if (success) {
+            return ResultUtil.success("启动签署流程成功");
+        }
+        return ResultUtil.error(500, "启动签署流程失败");
+    }
+
+    /**
+     * 获取客户签署链接
+     */
+    @GetMapping("/flow/{flowId}/customer/sign-url")
+    @ApiOperation("获取客户签署链接")
+    public Result getCustomerSignUrl(@PathVariable String flowId,
+                                     @RequestParam(required = false) String accountId,
+                                     @RequestParam String mobile,
+                                     @RequestParam String name) {
+        String signUrl = esignService.getCustomerSignUrl(flowId, accountId, mobile, name);
+        if (signUrl != null) {
+            return ResultUtil.success("获取成功", signUrl);
+        }
+        return ResultUtil.error(500, "获取签署链接失败");
+    }
+
+    /**
+     * 获取业务方签署链接
+     */
+    @GetMapping("/flow/{flowId}/business/sign-url")
+    @ApiOperation("获取业务方签署链接")
+    public Result getBusinessSignUrl(@PathVariable String flowId,
+                                     @RequestParam(required = false) String accountId,
+                                     @RequestParam String mobile,
+                                     @RequestParam String name) {
+        String signUrl = esignService.getBusinessSignUrl(flowId, accountId, mobile, name);
+        if (signUrl != null) {
+            return ResultUtil.success("获取成功", signUrl);
+        }
+        return ResultUtil.error(500, "获取签署链接失败");
+    }
+
+    /**
+     * 下载已签署的合同文档
+     */
+    @GetMapping("/flow/{flowId}/download")
+    @ApiOperation("下载已签署的合同文档")
+    public Result downloadSignedDocument(@PathVariable String flowId) {
+        String downloadUrl = esignService.downloadSignedDocument(flowId);
+        if (downloadUrl != null) {
+            return ResultUtil.success("获取下载地址成功", downloadUrl);
+        }
+        return ResultUtil.error(500, "获取下载地址失败");
+    }
+
+    /**
+     * 撤销签署流程
+     */
+    @PostMapping("/flow/{flowId}/revoke")
+    @ApiOperation("撤销签署流程")
+    public Result revokeFlow(@PathVariable String flowId,
+                             @RequestParam String revokeReason) {
+        boolean success = esignService.revokeFlow(flowId, revokeReason);
+        if (success) {
+            return ResultUtil.success("撤销成功");
+        }
+        return ResultUtil.error(500, "撤销失败");
+    }
+
+    /**
+     * e签宝回调通知接口
+     */
+    @PostMapping("/callback")
+    @ApiOperation("e签宝回调通知")
+    public Result callback(@RequestBody Map<String, Object> params) {
+        System.out.println("回调通知");
+        boolean success = esignService.handleCallback(params);
+        if (success) {
+            return ResultUtil.success("回调处理成功");
+        }
+        return ResultUtil.error(500, "回调处理失败");
+    }
+
+    @GetMapping("/{id}")
+    @ApiOperation("获取签署信息")
+    public Result getSignInfo(@PathVariable Long id) {
+        return ResultUtil.success("success",esignService.findById(id));
+    }
+
+    @GetMapping("/{contractId}/byContract")
+    @ApiOperation("按contractId获取签署信息")
+    public Result getSignInfoByContract(@PathVariable Long contractId) {
+        return ResultUtil.success("success",esignService.findByContract(contractId));
+    }
+
+    @GetMapping("/{caseId}/byCase")
+    @ApiOperation("按caseId获取签署信息")
+    public Result getSignInfoByCase(@PathVariable Long caseId) {
+        return ResultUtil.success("success",esignService.findByCaseId(caseId));
+    }
+
+    /**
+     * 获取个人认证授权页面链接
+     */
+    @PostMapping("/auth/psn-url")
+    @ApiOperation("获取个人认证授权页面链接")
+    public Result getPsnAuthUrl(@RequestParam String mobile,
+                                @RequestParam String name,
+                                @RequestParam(required = false) String idNumber,
+                                @RequestParam(required = false) String notifyUrl) {
+        try {
+            Map<String, String> result = esignService.getPsnAuthUrl(mobile, name, idNumber, notifyUrl);
+            log.info(mobile + " " + name + " " + idNumber );
+            if (result != null) {
+                return ResultUtil.success("获取认证授权链接成功", result);
+            }
+            return ResultUtil.error(500, "获取认证授权链接失败");
+        } catch (Exception e) {
+            log.error("获取认证授权链接异常", e);
+            return ResultUtil.error(500, "获取认证授权链接异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询个人认证信息
+     */
+    @GetMapping("/auth/psn-info")
+    @ApiOperation("查询个人认证信息")
+    public Result queryPsnInfo(@RequestParam(required = false) String mobile,
+                               @RequestParam(required = false) String idNumber) {
+        try {
+            if (mobile == null && idNumber == null) {
+                return ResultUtil.error(400, "手机号和身份证号至少需要提供一个");
+            }
+            Map<String, Object> result = esignService.queryPsnInfo(mobile, idNumber);
+            if (result != null) {
+                Object psnId = result.get("psnId");
+                return ResultUtil.success("查询个人认证信息成功", result);
+            }
+            return ResultUtil.error(500, "查询个人认证信息失败,用户可能未实名认证");
+        } catch (Exception e) {
+            log.error("查询个人认证信息异常", e);
+            return ResultUtil.error(500, "查询个人认证信息异常: " + e.getMessage());
+        }
+    }
+
+
+}
+

+ 495 - 0
src/main/java/com/loan/system/controller/wechat/EsignTestController.java

@@ -0,0 +1,495 @@
+package com.loan.system.controller.wechat;
+
+import com.loan.system.domain.dto.EsignCreateFlowDTO;
+import com.loan.system.domain.dto.EsignFillTemplateDTO;
+import com.loan.system.domain.dto.EsignTemplateFillDTO;
+import com.loan.system.domain.pojo.Result;
+import com.loan.system.domain.vo.EsignFlowVO;
+import com.loan.system.service.ContractService;
+import com.loan.system.service.EsignService;
+import com.loan.system.utils.EsignContractUtil;
+import com.loan.system.utils.ResultUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * e签宝电子签署Controller
+ */
+@Slf4j
+@RestController
+@RequestMapping("/wechat/esignTest")
+@Api(tags = "e签宝电子签署接口")
+@RequiredArgsConstructor
+public class EsignTestController {
+
+    @Autowired
+    private EsignService esignService;
+
+    @Value("${upload.templatePath}")
+    private String contractPath;
+    @Autowired
+    private ContractService contractService;
+
+    /**
+     * TODO:1.1沙盒测试环境填充模板(方案一:标准认证授权流程)
+     * 填充合同模板并创建签署流程(完整流程)
+     * 注意:使用此接口前,必须确保签署人已完成实名认证
+     */
+    @PostMapping("/flow/createFromTemplateDetail")
+    @ApiOperation("填充合同模板并创建签署流程(方案一)")
+    public Result createFlowFromTemplate(@RequestBody EsignFillTemplateDTO dto) {
+        try {
+            // 1. 填充模板并上传到e签宝
+            String fullTemplatePath = "template1.docx";
+
+            // 如果模板路径是相对路径,拼接配置的模板路径
+            fullTemplatePath = contractPath + fullTemplatePath;
+
+            String fileId = EsignContractUtil.fillTemplateAndUpload(
+                    fullTemplatePath,
+                    contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()),
+                    dto.getContractName() != null ? dto.getContractName() : "合同",
+                    esignService
+            );
+
+            if (fileId == null || fileId.isEmpty()) {
+                return ResultUtil.error(500, "模板填充并上传失败");
+            }
+
+            // 2. 创建签署流程
+            EsignCreateFlowDTO flowDTO = new EsignCreateFlowDTO();
+            flowDTO.setContractId(dto.getContractId());
+            flowDTO.setCaseId(dto.getCaseId());
+            flowDTO.setFileId(fileId);
+            flowDTO.setDocumentUrl(fileId); // 兼容旧字段
+            flowDTO.setDocumentName((dto.getContractName() != null ? dto.getContractName() : "合同") + ".pdf");
+            flowDTO.setCustomerName(dto.getCustomerName());
+            flowDTO.setCustomerMobile(dto.getCustomerMobile());
+            flowDTO.setCustomerIdNumber(dto.getCustomerIdNumber());
+            flowDTO.setBusinessName(dto.getBusinessName());
+            flowDTO.setBusinessMobile(dto.getBusinessMobile());
+            flowDTO.setBusinessIdNumber(dto.getBusinessIdNumber());
+            flowDTO.setSignDeadline(dto.getSignDeadline());
+            flowDTO.setRemark(dto.getRemark());
+
+            String flowId = esignService.createSignFlow(flowDTO);
+            if (flowId != null) {
+                return ResultUtil.success("模板填充并创建签署流程成功", flowId);
+            }
+            return ResultUtil.error(500, "创建签署流程失败");
+        } catch (Exception e) {
+            log.error("填充模板并创建签署流程异常", e);
+            return ResultUtil.error(500, "处理失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 【方案二】填充模板并创建签署流程(简化流程)
+     * 此接口不需要提前引导用户实名认证,签署时由e签宝自动处理
+     */
+    @PostMapping("/flow/createFromTemplate")
+    @ApiOperation("填充合同模板并创建签署流程(方案二-简化流程)")
+    public Result createFlowFromTemplateSimplified(@RequestBody EsignFillTemplateDTO dto) {
+        try {
+            // 1. 填充模板并上传到e签宝
+            String fullTemplatePath = "template1.docx";
+            fullTemplatePath = contractPath + fullTemplatePath;
+
+            String fileId = EsignContractUtil.fillTemplateAndUpload(
+                    fullTemplatePath,
+                    contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()),
+                    dto.getContractName() != null ? dto.getContractName() : "合同",
+                    esignService
+            );
+
+            if (fileId == null || fileId.isEmpty()) {
+                return ResultUtil.error(500, "模板填充并上传失败");
+            }
+
+            // 2. 创建签署流程(使用简化流程)
+            EsignCreateFlowDTO flowDTO = new EsignCreateFlowDTO();
+            flowDTO.setContractId(dto.getContractId());
+            flowDTO.setCaseId(dto.getCaseId());
+            flowDTO.setFileId(fileId);
+            flowDTO.setDocumentUrl(fileId);
+            flowDTO.setDocumentName((dto.getContractName() != null ? dto.getContractName() : "合同") + ".pdf");
+            flowDTO.setCustomerName(dto.getCustomerName());
+            flowDTO.setCustomerMobile(dto.getCustomerMobile());
+            flowDTO.setCustomerIdNumber(dto.getCustomerIdNumber());
+            flowDTO.setBusinessName(dto.getBusinessName());
+            flowDTO.setBusinessMobile(dto.getBusinessMobile());
+            flowDTO.setBusinessIdNumber(dto.getBusinessIdNumber());
+            flowDTO.setSignDeadline(dto.getSignDeadline());
+            flowDTO.setRemark(dto.getRemark());
+
+            String flowId = esignService.createSignFlowSimplified(flowDTO);
+            if (flowId != null) {
+                return ResultUtil.success("模板填充并创建签署流程成功(简化流程)", flowId);
+            }
+            return ResultUtil.error(500, "创建签署流程失败");
+        } catch (Exception e) {
+            log.error("填充模板并创建签署流程异常(简化流程)", e);
+            return ResultUtil.error(500, "处理失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 仅填充模板并上传到e签宝(不上传签署流程)
+     */
+    @PostMapping("/template/upload")
+    @ApiOperation("填充合同模板并上传到e签宝")
+    public Result fillTemplateAndUpload(@RequestBody EsignFillTemplateDTO dto) {
+        try {
+            String fullTemplatePath = "template1.docx";
+
+            // 如果模板路径是相对路径,拼接配置的模板路径
+            fullTemplatePath =contractPath + fullTemplatePath;
+
+            String fileId = EsignContractUtil.fillTemplateAndUpload(
+                    fullTemplatePath,
+                    contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()),
+                    dto.getContractName() != null ? dto.getContractName() : "合同",
+                    esignService
+            );
+
+            if (fileId != null) {
+                return ResultUtil.success("模板填充并上传成功", fileId);
+            }
+            return ResultUtil.error(500, "模板填充并上传失败");
+        } catch (Exception e) {
+            log.error("填充模板并上传异常", e);
+            return ResultUtil.error(500, "处理失败: " + e.getMessage());
+        }
+    }
+    /**
+     * 获取e签宝模板列表
+     */
+    @GetMapping("/template/list")
+    @ApiOperation("获取e签宝模板列表")
+    public Result getTemplateList(@RequestParam(defaultValue = "1") Integer pageNum,
+                                  @RequestParam(defaultValue = "20") Integer pageSize) {
+        try {
+            Map<String, Object> result = esignService.getTemplateList(pageNum, pageSize);
+            Integer code = (Integer) result.get("code");
+            if (code != null && code == 0) {
+                return ResultUtil.success("获取模板列表成功", result.get("data"));
+            } else {
+                return ResultUtil.error(500, (String) result.get("message"));
+            }
+        } catch (Exception e) {
+            log.error("获取e签宝模板列表异常", e);
+            return ResultUtil.error(500, "获取模板列表异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取e签宝模板详情
+     */
+    @GetMapping("/template/{templateId}")
+    @ApiOperation("获取e签宝模板详情")
+    public Result getTemplateDetail(@PathVariable String templateId) {
+        try {
+            Map<String, Object> result = esignService.getTemplateDetail(templateId);
+            Integer code = (Integer) result.get("code");
+            if (code != null && code == 0) {
+                return ResultUtil.success("获取模板详情成功", result.get("data"));
+            } else {
+                return ResultUtil.error(500, (String) result.get("message"));
+            }
+        } catch (Exception e) {
+            log.error("获取e签宝模板详情异常", e);
+            return ResultUtil.error(500, "获取模板详情异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * TODO:1.2 生产环境填充模板
+     * 使用e签宝模板填充字段并创建签署流程
+     */
+    @PostMapping("/flow/createFromEsignTemplate")
+    @ApiOperation("使用e签宝模板填充字段并创建签署流程")
+    public Result createFlowFromEsignTemplate(@RequestBody @Validated EsignTemplateFillDTO dto) {
+        try {
+            // 1. 构建字段映射
+            Map<String, String> formFields = EsignContractUtil.convertContractToTemplateFields(contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()));
+
+
+            // 2. 使用e签宝模板填充字段并生成文件
+            String fileId = esignService.fillTemplateAndGenerateFile(
+                dto.getTemplateId(),
+                dto.getContractName() + ".pdf",
+                formFields
+            );
+
+            if (fileId == null || fileId.isEmpty()) {
+                return ResultUtil.error(500, "模板填充并生成文件失败");
+            }
+
+            // 3. 创建签署流程
+            EsignCreateFlowDTO flowDTO = new EsignCreateFlowDTO();
+            flowDTO.setContractId(dto.getContractId());
+            flowDTO.setCaseId(dto.getCaseId());
+            flowDTO.setFileId(fileId);
+            flowDTO.setDocumentName(dto.getContractName() + ".pdf");
+            flowDTO.setCustomerName(dto.getCustomerName());
+            flowDTO.setCustomerMobile(dto.getCustomerMobile());
+            flowDTO.setCustomerIdNumber(dto.getCustomerIdNumber());
+            flowDTO.setBusinessName(dto.getBusinessName());
+            flowDTO.setBusinessMobile(dto.getBusinessMobile());
+            flowDTO.setBusinessIdNumber(dto.getBusinessIdNumber());
+            flowDTO.setSignDeadline(dto.getSignDeadline());
+            flowDTO.setRemark(dto.getRemark());
+
+            String flowId = esignService.createSignFlow(flowDTO);
+
+            if (flowId == null || flowId.isEmpty()) {
+                return ResultUtil.error(500, "创建签署流程失败");
+            }
+
+            log.info("使用e签宝模板创建签署流程成功,templateId: {}, flowId: {}", dto.getTemplateId(), flowId);
+            return ResultUtil.success("使用e签宝模板创建签署流程成功", flowId);
+
+        } catch (Exception e) {
+            log.error("使用e签宝模板创建签署流程异常", e);
+            return ResultUtil.error(500, "创建签署流程异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 仅使用e签宝模板填充字段生成文件(不上传,不创建流程)
+     */
+    @PostMapping("/template/fill")
+    @ApiOperation("使用e签宝模板填充字段生成文件")
+    public Result fillEsignTemplate(@RequestBody @Validated EsignTemplateFillDTO dto) {
+        try {
+            // 构建字段映射
+            Map<String, String> formFields = EsignContractUtil.convertContractToTemplateFields(contractService.getContractInformationById(dto.getCaseId(), dto.getContractId()));
+
+
+            // 使用e签宝模板填充字段并生成文件
+            String fileId = esignService.fillTemplateAndGenerateFile(
+                dto.getTemplateId(),
+                dto.getContractName() + ".pdf",
+                formFields
+            );
+
+            if (fileId == null || fileId.isEmpty()) {
+                return ResultUtil.error(500, "模板填充并生成文件失败");
+            }
+
+            log.info("使用e签宝模板填充字段成功,templateId: {}, fileId: {}", dto.getTemplateId(), fileId);
+            return ResultUtil.success("模板填充并生成文件成功", fileId);
+
+        } catch (Exception e) {
+            log.error("使用e签宝模板填充字段异常", e);
+            return ResultUtil.error(500, "模板填充异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * TODO 2.签署流程(方案一:标准认证授权流程)
+     * 创建签署流程(使用已上传的文件ID)
+     * 注意:使用此接口前,必须确保签署人已完成实名认证
+     */
+    @PostMapping("/flow/create")
+    @ApiOperation("创建签署流程(方案一)")
+    public Result createSignFlow(@RequestBody EsignCreateFlowDTO dto) {
+        // 优先使用fileId,如果没有则使用documentUrl
+        if (dto.getFileId() == null && dto.getDocumentUrl() != null) {
+            dto.setFileId(dto.getDocumentUrl());
+        }
+
+        String flowId = esignService.createSignFlow(dto);
+        if (flowId != null) {
+            return ResultUtil.success("创建签署流程成功", flowId);
+        }
+        return ResultUtil.error(500, "创建签署流程失败");
+    }
+
+    /**
+     * 【方案二】创建签署流程(简化流程)
+     * 此接口不需要提前引导用户实名认证,签署时由e签宝自动处理
+     */
+    @PostMapping("/flow/createSimplified")
+    @ApiOperation("创建签署流程(方案二-简化流程)")
+    public Result createSignFlowSimplified(@RequestBody EsignCreateFlowDTO dto) {
+        // 优先使用fileId,如果没有则使用documentUrl
+        if (dto.getFileId() == null && dto.getDocumentUrl() != null) {
+            dto.setFileId(dto.getDocumentUrl());
+        }
+
+        String flowId = esignService.createSignFlowSimplified(dto);
+        if (flowId != null) {
+            return ResultUtil.success("创建签署流程成功(简化流程)", flowId);
+        }
+        return ResultUtil.error(500, "创建签署流程失败");
+    }
+    /**
+     * 启动签署流程
+     */
+    @PostMapping("/flow/{flowId}/start")
+    @ApiOperation("启动签署流程")
+    public Result startSignFlow(@PathVariable String flowId) {
+        boolean success = esignService.startSignFlow(flowId);
+        if (success) {
+            return ResultUtil.success("启动签署流程成功");
+        }
+        return ResultUtil.error(500, "启动签署流程失败");
+    }
+
+    /**
+     * 获取签署流程详情
+     */
+    @GetMapping("/flow/{flowId}")
+    @ApiOperation("获取签署流程详情")//x
+    public Result getFlowDetail(@PathVariable String flowId) {
+        EsignFlowVO vo = esignService.getFlowDetail(flowId);
+        if (vo != null) {
+            return ResultUtil.success("获取成功", vo);
+        }
+        return ResultUtil.error(404, "流程不存在");
+    }
+
+
+
+    /**
+     * 获取客户签署链接
+     */
+    @GetMapping("/flow/{flowId}/customer/sign-url")
+    @ApiOperation("获取客户签署链接")
+    public Result getCustomerSignUrl(@PathVariable String flowId,
+                                     @RequestParam(required = false) String accountId,
+                                     @RequestParam String mobile,
+                                     @RequestParam String name) {
+        String signUrl = esignService.getCustomerSignUrl(flowId, accountId, mobile, name);
+        if (signUrl != null) {
+            return ResultUtil.success("获取成功", signUrl);
+        }
+        return ResultUtil.error(500, "获取签署链接失败");
+    }
+
+    /**
+     * 获取业务方签署链接
+     */
+    @GetMapping("/flow/{flowId}/business/sign-url")
+    @ApiOperation("获取业务方签署链接")
+    public Result getBusinessSignUrl(@PathVariable String flowId,
+                                     @RequestParam(required = false) String accountId,
+                                     @RequestParam String mobile,
+                                     @RequestParam String name) {
+        String signUrl = esignService.getBusinessSignUrl(flowId, accountId, mobile, name);
+        if (signUrl != null) {
+            return ResultUtil.success("获取成功", signUrl);
+        }
+        return ResultUtil.error(500, "获取签署链接失败");
+    }
+
+    /**
+     * 查询流程状态
+     */
+    @GetMapping("/flow/{flowId}/status")
+    @ApiOperation("查询流程状态")
+    public Result queryFlowStatus(@PathVariable String flowId) {
+        Map<String, Object> status = esignService.queryFlowStatus(flowId);
+        if (status != null) {
+            return ResultUtil.success("查询成功", status);
+        }
+        return ResultUtil.error(500, "查询失败");
+    }
+
+    /**
+     * 下载已签署的合同文档
+     */
+    @GetMapping("/flow/{flowId}/download")
+    @ApiOperation("下载已签署的合同文档")
+    public Result downloadSignedDocument(@PathVariable String flowId) {
+        String downloadUrl = esignService.downloadSignedDocument(flowId);
+        if (downloadUrl != null) {
+            return ResultUtil.success("获取下载地址成功", downloadUrl);
+        }
+        return ResultUtil.error(500, "获取下载地址失败");
+    }
+
+    /**
+     * 撤销签署流程
+     */
+    @PostMapping("/flow/{flowId}/revoke")
+    @ApiOperation("撤销签署流程")
+    public Result revokeFlow(@PathVariable String flowId,
+                             @RequestParam String revokeReason) {
+        boolean success = esignService.revokeFlow(flowId, revokeReason);
+        if (success) {
+            return ResultUtil.success("撤销成功");
+        }
+        return ResultUtil.error(500, "撤销失败");
+    }
+
+    /**
+     * e签宝回调通知接口
+     */
+    @PostMapping("/callback")
+    @ApiOperation("e签宝回调通知")
+    public Result callback(@RequestBody Map<String, Object> params) {
+        boolean success = esignService.handleCallback(params);
+        if (success) {
+            return ResultUtil.success("回调处理成功");
+        }
+        return ResultUtil.error(500, "回调处理失败");
+    }
+    
+    /**
+     * 获取个人认证授权页面链接
+     */
+    @PostMapping("/auth/psn-url")
+    @ApiOperation("获取个人认证授权页面链接")
+    public Result getPsnAuthUrl(@RequestParam String mobile,
+                                 @RequestParam String name,
+                                 @RequestParam(required = false) String idNumber,
+                                 @RequestParam(required = false) String notifyUrl) {
+        try {
+            Map<String, String> result = esignService.getPsnAuthUrl(mobile, name, idNumber, notifyUrl);
+            if (result != null) {
+                return ResultUtil.success("获取认证授权链接成功", result);
+            }
+            return ResultUtil.error(500, "获取认证授权链接失败");
+        } catch (Exception e) {
+            log.error("获取认证授权链接异常", e);
+            return ResultUtil.error(500, "获取认证授权链接异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询个人认证信息
+     */
+    @GetMapping("/auth/psn-info")
+    @ApiOperation("查询个人认证信息")
+    public Result queryPsnInfo(@RequestParam(required = false) String mobile,
+                                @RequestParam(required = false) String idNumber) {
+        try {
+            if (mobile == null && idNumber == null) {
+                return ResultUtil.error(400, "手机号和身份证号至少需要提供一个");
+            }
+            Map<String, Object> result = esignService.queryPsnInfo(mobile, idNumber);
+            if (result != null) {
+                Object psnId = result.get("psnId");
+                return ResultUtil.success("查询个人认证信息成功", result);
+            }
+            return ResultUtil.error(500, "查询个人认证信息失败,用户可能未实名认证");
+        } catch (Exception e) {
+            log.error("查询个人认证信息异常", e);
+            return ResultUtil.error(500, "查询个人认证信息异常: " + e.getMessage());
+        }
+    }
+
+
+
+}
+

+ 107 - 234
src/main/java/com/loan/system/controller/wechat/LoanController.java

@@ -37,80 +37,63 @@ public class LoanController {
     @Autowired
     private StepService stepService;
     @Autowired
-    private ContractService contractService;
-    @Autowired
-    private ApprovalService approveService;
-    @Autowired
     private CustomerService customerService;
-    @Autowired
-    private CustomerOtherService customerOtherService;
-    @Autowired
-    private CollateralService collateralService;
-    @Autowired
-    private ContractAndCollateralService contractAndCollateralService;
-    @Autowired
-    private WxService wxService;
-    @Autowired
-    private UserService userService;
-    @Autowired
-    private StringRedisTemplate stringRedisTemplate;
-
 
     @GetMapping
-    @ApiOperation("显示所有业务")
+    @ApiOperation("显示所有业务")//pc
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseAll(@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        return ResultUtil.success("success",loanService.findLoanCaseAll(pageNum, pageSize,false));
+    public Result findLoanCaseAll(@RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "20") Integer pageSize) {
+        return ResultUtil.success("success", loanService.findLoanCaseAll(pageNum, pageSize, false));
     }
 
     @GetMapping("/customer")
-    @ApiOperation("显示客户的所有业务")
+    @ApiOperation("显示客户的所有业务")//x
     @PreAuthorize("@pms.hasAnyRoles('EXTERNAL')")
-    public Result findLoanCaseByCustomerId(@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerId(BaseContext.getCurrentId(),pageNum,pageSize,false);
-        return ResultUtil.success("success",loanCases);
+    public Result findLoanCaseByCustomerId(@RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "20") Integer pageSize) {
+        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerId(BaseContext.getCurrentId(), pageNum, pageSize, false);
+        return ResultUtil.success("success", loanCases);
     }
 
     @GetMapping("/dealing")
-    @ApiOperation("显示处理中的业务")
+    @ApiOperation("显示处理中的业务")//x
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByIsComplete(@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByIsComplete(pageNum,pageSize,DecisionEnum.PROCESS.getMsg(),false);
+    public Result findLoanCaseByIsComplete(@RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "20") Integer pageSize) {
+        List<LoanCaseVO> loanCases = loanService.findLoanCaseByIsComplete(pageNum, pageSize, DecisionEnum.PROCESS.getMsg(), false);
 
-        return ResultUtil.success("success",loanCases);
+        return ResultUtil.success("success", loanCases);
     }
 
     @GetMapping("/completed")
-    @ApiOperation("显示处理中的业务")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByCompleted(@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByIsComplete(pageNum,pageSize,DecisionEnum.COMPLETE.getMsg(),false);
+    @ApiOperation("显示历史业务")
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER', 'BACK_OFFICE')")
+    public Result findLoanCaseByCompleted(@RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "20") Integer pageSize) {
+        List<LoanCaseVO> loanCases = loanService.findLoanCaseByIsComplete(pageNum, pageSize, DecisionEnum.COMPLETE.getMsg(), false);
 
-        return ResultUtil.success("success",loanCases);
+        return ResultUtil.success("success", loanCases);
     }
 
-    @GetMapping("/stepName/dealings")
+    @GetMapping("/stepCode/dealings")
     @ApiOperation("按环节名称查询业务")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByStepNameAndIsComplete(@RequestParam String stepName){
-        if (stepName== null)
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','LEAD_SALES','ASSIST_SALES','APPROVER','FINANCE','BACK_OFFICE','EXTERNAL')")
+    public Result findLoanCaseByStepCodeAndIsComplete(@RequestParam Integer stepCode) {
+        if (stepCode == null)
             throw new DescribeException(INPUT_ERROR);
-        return ResultUtil.success("success",loanService.listLoanCaseByStepName(stepName));
+        return ResultUtil.success("success", loanService.listLoanCaseByStepCode(stepCode));
     }
 
     @GetMapping("/{role}/dealings")
-    @ApiOperation("按客户角色查询业务")
+    @ApiOperation("按客户角色查询业务")//x
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByRoleAndIsComplete(@PathVariable  String role, @RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        if(role.equals(RoleEnum.ALL.getMsg()))
-            return ResultUtil.success("success",loanService.findLoanCaseByIsComplete(pageNum,pageSize,DecisionEnum.PROCESS.getMsg(),false));
+    public Result findLoanCaseByRoleAndIsComplete(@PathVariable String role, @RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "20") Integer pageSize) {
+        if (role.equals(RoleEnum.ALL.getMsg()))
+            return ResultUtil.success("success", loanService.findLoanCaseByIsComplete(pageNum, pageSize, DecisionEnum.PROCESS.getMsg(), false));
         //根据角色返回业务列表,业务员返回全部未完成、审批员与财务员仅展示未审批或未财务审批的人物
-        Map<String,List<String>> roleAndStep = getRoleAndStep();
+        Map<String, List<String>> roleAndStep = getRoleAndStep();
         List<Long> loanCaseIds = loanService.findIdsByIsComplete(DecisionEnum.PROCESS.getMsg(), false);
         List<Long> ids = new ArrayList<>();
-        List<StepVO> currentSteps=new ArrayList<>();
+        List<StepVO> currentSteps = new ArrayList<>();
 
-        for(Long caseId : loanCaseIds){
+        for (Long caseId : loanCaseIds) {
             List<StepVO> steps = stepService.getStepByCaseId(caseId);
 
             for (StepVO step : steps) {
@@ -124,24 +107,25 @@ public class LoanController {
 
         }
         List<LoanCaseVO> loanCaseVOS = loanService.findLoanCaseByIds(ids, pageNum, pageSize, false);
-        for(int i=0;i<loanCaseVOS.size();i++){
+        for (int i = 0; i < loanCaseVOS.size(); i++) {
             LoanCaseVO loanCaseVO = loanCaseVOS.get(i);
             loanCaseVO.setStep(currentSteps.get(i));
             loanCaseVO.setCategory(getCategory(currentSteps.get(i).getCode()));
 
         }
-        return ResultUtil.success("success",loanCaseVOS);
+        return ResultUtil.success("success", loanCaseVOS);
     }
+
     private Integer getCategory(Integer stepCode) {
-        if(stepCode>=500 && stepCode<520)
+        if (stepCode >= 500 && stepCode < 520)
             return 1;
-        else if(stepCode>=520 && stepCode<530)
+        else if (stepCode >= 520 && stepCode < 530)
             return 2;
-        else if(stepCode>=530 && stepCode<540)
+        else if (stepCode >= 530 && stepCode < 540)
             return 3;
-        else if(stepCode>=540 && stepCode<560)
+        else if (stepCode >= 540 && stepCode < 560)
             return 4;
-        else if(stepCode>=560 && stepCode<580)
+        else if (stepCode >= 560 && stepCode < 580)
             return 5;
 
         return 0;
@@ -149,80 +133,73 @@ public class LoanController {
 
 
     @GetMapping("/all/{key}")
-    @ApiOperation("按客户关键字查询业务(所有)")
+    @ApiOperation("按客户关键字查询业务(所有)")//x
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findAllLoanCaseByKey(@PathVariable String key,@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
+    public Result findAllLoanCaseByKey(@PathVariable String key, @RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "20") Integer pageSize) {
         List<Customer> customers = customerService.getAllCustomerByKey(key, false);
         List<Long> customerIds = new ArrayList<>();
-        for(Customer customer : customers)
+        for (Customer customer : customers)
             customerIds.add(customer.getId());
 
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerIds(customerIds,pageNum,pageSize,false);
+        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerIds(customerIds, pageNum, pageSize, false);
 
-        return ResultUtil.success("success",loanCases);
+        return ResultUtil.success("success", loanCases);
     }
 
     @GetMapping("/dealing/{key}")
-    @ApiOperation("按客户关键字查询业务")
+    @ApiOperation("按客户关键字查询业务")//x
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByKey(@PathVariable String key,@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
+    public Result findLoanCaseByKey(@PathVariable String key, @RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "20") Integer pageSize) {
         List<Customer> customers = customerService.getAllCustomerByKey(key, false);
         List<Long> customerIds = new ArrayList<>();
-        for(Customer customer : customers)
+        for (Customer customer : customers)
             customerIds.add(customer.getId());
 
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerIds(customerIds,pageNum,pageSize,DecisionEnum.PROCESS.getMsg(),false);
+        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerIds(customerIds, pageNum, pageSize, DecisionEnum.PROCESS.getMsg(), false);
 
-        return ResultUtil.success("success",loanCases);
+        return ResultUtil.success("success", loanCases);
     }
 
     @GetMapping("/dealing/currentUser/{userId}")
-    @ApiOperation("按当前处理人查询待处理业务")
+    @ApiOperation("按当前处理人查询待处理业务")//x
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByCurrentUser(@PathVariable Long userId,@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize) {
+    public Result findLoanCaseByCurrentUser(@PathVariable Long userId, @RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "20") Integer pageSize) {
         //获取所有业务id
         List<Long> loanCaseIds = loanService.findIdsByIsComplete(DecisionEnum.PROCESS.getMsg(), false);
         List<Long> ids = new ArrayList<>();
-        for (Long loanCaseId : loanCaseIds) {
-            //寻找每个id的阶段
-            List<StepVO> stepByCaseId = stepService.getStepByCaseId(loanCaseId);
-            //判断是否有未完成的阶段
-            for (StepVO stepVO : stepByCaseId)
-                if (!stepVO.getStatus().equals(StepEnum.COMPLETED.getMsg()) &&
-                        ((stepVO.getUserId1() != null && stepVO.getUserId1().equals(userId))
-                                || (stepVO.getUserId2() != null && stepVO.getUserId2().equals(userId)))) {
-                    //返回该id
-                    ids.add(loanCaseId);
-                    break;
-                }
-
-        }
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByIds(ids,pageNum,pageSize,false);
-        return ResultUtil.success("success",loanCases);
+//        for (Long loanCaseId : loanCaseIds) {
+//            //寻找每个id的阶段
+//            List<StepVO> stepByCaseId = stepService.getStepByCaseId(loanCaseId);
+//            //判断是否有未完成的阶段
+//            for (StepVO stepVO : stepByCaseId)
+//                if (!stepVO.getStatus().equals(StepEnum.COMPLETED.getMsg()) &&
+//                        ((stepVO.getUserId1() != null && stepVO.getUserId1().equals(userId))
+//                                || (stepVO.getUserId2() != null && stepVO.getUserId2().equals(userId)))) {
+//                    //返回该id
+//                    ids.add(loanCaseId);
+//                    break;
+//                }
+//
+//        }
+        List<LoanCaseVO> loanCases = loanService.findLoanCaseByIds(ids, pageNum, pageSize, false);
+        return ResultUtil.success("success", loanCases);
     }
 
     @GetMapping("/{id}/details")
     @ApiOperation("显示业务详情")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails(@PathVariable("id")Long caseId){
-        return ResultUtil.success("success",loanService.findLoanCaseDetailsById(caseId));
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES','FINANCE','BACK_OFFICE','EXTERNAL')")
+    public Result findLoanCaseDetails(@PathVariable("id") Long caseId) {
+        return ResultUtil.success("success", loanService.findLoanCaseDetailsById(caseId));
     }
 
     @PostMapping("/create")
     @ApiOperation("创建业务")
     @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES','ASSIST_SALES')")
-    public Result createLoanCase(@RequestParam("customerId") Long customerId){
-        if(customerId == null ||!customerService.existsByIdAndIsDelete(customerId))
+    public Result createLoanCase(@RequestParam("customerId") Long customerId) {
+        if (customerId == null || !customerService.existsByIdAndIsDelete(customerId))
             throw new DescribeException(ExceptionEnum.USER_NOT_EXIST);
-        //创建订单
-        LoanCaseVO loanCase = loanService.addLoanCaseByCustomerId(customerId);
-        //创建流程
-//        stepService.addStepByCaseId(loanCase.getId());
-        List<List<Integer>> seq = StepPropertyEnum.seqList();
-        stepService.addStepByCaseIdAndSequences(loanCase.getId(),seq);
-        //设置当前处理人
-        stepService.updateUserByCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),BaseContext.getCurrentId(),loanCase.getId());
-        return ResultUtil.success("success",loanCase);
+
+        return ResultUtil.success("success", loanService.createLoanCase(customerId));
     }
 
     /*
@@ -232,174 +209,71 @@ public class LoanController {
     @PostMapping("/{id}")//请求中操作复杂,putmapping仅适用于更新操作
     @ApiOperation("提交/保存业务")
     @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
-    public Result saveLoanCase(@PathVariable("id")Long caseId, @RequestBody LoanCaseDTO loanCaseDTO, @RequestParam(value = "isComplete",defaultValue = "false")Boolean isComplete){//isComplete 若保存,则设置为未完成,若提交,则设置为已完成
-        if(!loanService.existsByIdAndIsDelete(caseId))
+    public Result saveLoanCase(@PathVariable("id") Long caseId, @RequestBody LoanCaseDTO loanCaseDTO, @RequestParam(value = "isComplete", defaultValue = "false") Boolean isComplete,@RequestParam(defaultValue = "false") Boolean isSkip) {//isComplete 若保存,则设置为未完成,若提交,则设置为已完成
+        if (!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
         if (ObjectUtils.isEmpty(loanCaseDTO))
             throw new DescribeException(ExceptionEnum.INPUT_ERROR);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getCode(), caseId);
+        if (ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
         Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
+        if (currentId != null && !BaseContext.getCurrentId().equals(currentId)) {
             throw new DescribeException(STEP_USER_NOT_EXPECTED);
         }
 
-        //1.补充业务信息
-        loanService.updateLoanCaseById(loanCaseDTO,caseId);
-
-        RedisData redisData = new RedisData(stringRedisTemplate);
-        redisData.delete(caseId);
-        if(!isComplete){//TODO:利用redis保存草稿信息
-            redisData.setLoanCase(caseId,loanCaseDTO);
-
-        }else {
-            /*
-            //TODO:提交后才真正保存到数据库,并删除原有的草稿
-            因为后续驳回会修改,所以是如果有数据则修改,无数据则新增
-             */
-            //2.设置合同(若存在则修改)
-            contractService.deleteAllByCaseId(caseId);
-            List<Contract> contracts = new ArrayList<>();
-            for (ContractDTO contractDTO : loanCaseDTO.getContracts()){
-                Long contractId = contractDTO.getId();
-                if(contractId != null && contractService.existsById(contractId)){
-                    //修改合同
-                    contractService.updateContractById(contractId,contractDTO,false);
-                    contracts.add(BeanUtil.copyProperties(contractDTO,Contract.class));
-                }else {
-                    //新增合同(若为负数,表示不存在)
-                    Contract contract = contractService.saveContract(contractDTO);
-                    contracts.add(contract);
-                }
-            }
-
-            //3.设置押品
-            collateralService.deleteAllByCaseId(caseId);
-            List<Long> collateralIds = new ArrayList<>();
-            for(CollateralDTO collateralDTO : loanCaseDTO.getCollateral()){
-                Long collateralId = collateralDTO.getId();
-                if(collateralId != null && collateralService.existsById(collateralId)){
-                    //修改押品
-                    collateralService.updateCollateralById(collateralId,collateralDTO,false);
-                }else {
-                    //新增押品
-                    Collateral collateral = collateralService.saveCollateral(collateralDTO);
-                    collateralId = collateral.getId();
-                }
-
-                collateralIds.add(collateralId);
-            }
-
-            //4.设置合同、押品关联
-            contractAndCollateralService.deleteAllByCaseId(caseId);
-            for(int i=0 ; i < loanCaseDTO.getCollateralAndContract().size(); i++){
-                Contract contract = contracts.get( i);
-                List<Integer> collaterals = loanCaseDTO.getCollateralAndContract().get(i).get(contract.getBusinessAttr());
-
-                for (Integer collateralId : collaterals)
-                    contractAndCollateralService.addContractAndCollateral(contract.getId(),collateralIds.get(collateralId-1),caseId);
-            }
-
-            //5.添加其它客户
-            customerOtherService.deleteAllByCaseId(caseId);
-            if(!ObjectUtils.isEmpty(loanCaseDTO.getCustomers1())){
-                System.out.println(loanCaseDTO.getCustomers1());
-                if(loanCaseDTO.getCustomers1().getId() != null && customerOtherService.existsById(loanCaseDTO.getCustomers1().getId()))
-                    customerOtherService.updateCustomerById(loanCaseDTO.getCustomers1(),loanCaseDTO.getCustomers1().getId());
-                else
-                    customerOtherService.addCustomers(loanCaseDTO.getCustomers1());
-            }
-
-            if (!ObjectUtils.isEmpty(loanCaseDTO.getCustomers2())){
-                if(loanCaseDTO.getCustomers2().getId() != null && customerOtherService.existsById(loanCaseDTO.getCustomers2().getId()))
-                    customerOtherService.updateCustomerById(loanCaseDTO.getCustomers2(),loanCaseDTO.getCustomers2().getId());
-                else
-                    customerOtherService.addCustomers(loanCaseDTO.getCustomers2());
-            }
-        }
-        //6.设置客户婚姻
-        customerService.updateMarriedStatusAndIsIllegalById(loanCaseDTO.getCustomerId(),loanCaseDTO.getMarriedStatus(),loanCaseDTO.getIsIllegal());
-
-        //7.设置阶段负责人与状态(如果第一个人是辅办人员,那第二个人必须是主办)
-        if(isComplete){
-            redisData.deleteApprovalByKey(caseId,StepPropertyEnum.BUSINESS_ACCEPT.getLabel());
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT_PARENT.getLabel(),caseId);
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),caseId);
-            stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),BaseContext.getCurrentId(),caseId);
-            stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.PRE_TRIAL.getLabel(),BaseContext.getCurrentId(),caseId);
-            //设置当阶段为完成,下阶段为开始
-            stepService.tryStartStep(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),caseId);
-
-            //TODO:8.微信推送预审通过消息和通知下一环节
-//            SysMessage message = new SysMessage();
-//            message.setMobile(null);
- //           message.setUserRole(RoleEnum.ASSIST_SALES.getMsg());
-//            message.setMessageTitle("");
-//            message.setMessageContent("");
- //           message.setStepName(StepPropertyEnum.PRE_TRIAL.getLabel());
-  //          message.setRelatedId(caseId);
-//            message.setRelatedType("");
- //           messageService.addMessage(message);
-            System.out.println(wxService.sendTemplateMessage(customerService.findByIdAndIsDelete(loanCaseDTO.getCustomerId()).getOpenid(),new TemplateMessage()));;
-
-        }
+        loanService.saveLoanCase(loanCaseDTO, caseId, isComplete,isSkip);
 
         return ResultUtil.success("success");
     }
 
-
-
-    @PostMapping("/{id}/preApprove/cancel")
-    @ApiOperation("取消预审")
+    @PostMapping("/cancel/{caseId}")
+    @ApiOperation("撤销业务提交")
     @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
-    public Result cancelPreApprove(@PathVariable("id")Long caseId){
-        if(!loanService.existsByIdAndIsDelete(caseId))
+    public Result cancelLoanCase(@PathVariable("caseId") Long caseId) {
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(),caseId);
-        if(ObjectUtils.isEmpty( step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
-            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+        StepVO step = stepService.findByStepCodeAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getCode(), caseId);
+        if (ObjectUtils.isEmpty(step) || !step.getStatus().equals(StepEnum.COMPLETED.getMsg()))
+            throw new DescribeException(STEP_HAS__NOT_COMPLETE);
 
-        Long currentId = step.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
+        if (step.getUserId1() != null && !BaseContext.getCurrentId().equals(step.getUserId1()))
             throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
-
-        //设置预审核阶段已完成,审批阶段开始
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.PRE_TRIAL_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.PRE_TRIAL.getLabel(),caseId);
 
-        stepService.skipStep(StepPropertyEnum.PRE_TRIAL_PARENT.getLabel(),caseId);
+        //若业务已预审完成,则无法撤回
+        StepVO step1 = stepService.findByStepCodeAndCaseId(StepPropertyEnum.APPROVAL.getCode(), caseId);
+        if ((!ObjectUtils.isEmpty(step1)) && step1.getStatus().equals(StepEnum.COMPLETED.getMsg()))
+            throw new DescribeException(PRE_TRAIL_HAS_COMPLETE);
 
-        stepService.updateUserByCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(), BaseContext.getCurrentId(), caseId);
 
-        //添加审批记录
-        approveService.addPreApprovalRecord(StepPropertyEnum.PRE_TRIAL.getLabel(),BaseContext.getCurrentId(),caseId);
+        loanService.cancelLoanCase(caseId);
 
         return ResultUtil.success("success");
     }
 
+
     @PutMapping("/{id}/complete")
-    @ApiOperation("完成业务")
+    @ApiOperation("完成业务")//x
     @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
-    public Result completeLoanCase(@PathVariable("id")Long caseId){
-        if(!loanService.existsByIdAndIsDelete(caseId))
+    public Result completeLoanCase(@PathVariable("id") Long caseId) {
+        if (!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
         List<StepVO> steps = stepService.getStepByCaseId(caseId);
-        for(StepVO step : steps){
-            if(!step.getStatus().equals(StepEnum.COMPLETED.getMsg()))
+        for (StepVO step : steps) {
+            if (!step.getStatus().equals(StepEnum.COMPLETED.getMsg()))
                 throw new DescribeException(PROJECT_HAS_NOT_COMPLETED);
         }
-        loanService.updateIsCompleteByCaseId(StepEnum.COMPLETED.getMsg(),caseId);
+        loanService.updateIsCompleteByCaseId(StepEnum.COMPLETED.getMsg(), caseId);
         return ResultUtil.success("success");
     }
-    private Map<String,List<String>> getRoleAndStep(){
-        List<String> saleList= Arrays.asList(
+
+    private Map<String, List<String>> getRoleAndStep() {
+        List<String> saleList = Arrays.asList(
                 StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),
                 StepPropertyEnum.PRE_TRIAL.getLabel(),
                 StepPropertyEnum.CONTRACT_SIGN.getLabel(),
@@ -413,7 +287,7 @@ public class LoanController {
                 StepPropertyEnum.REPAY_START.getLabel(),
                 StepPropertyEnum.BALANCE_REPAY.getLabel()
         );
-        List<String> approvalList= Arrays.asList(
+        List<String> approvalList = Arrays.asList(
                 StepPropertyEnum.APPROVAL.getLabel(),            //审批、取证审批分派、计划审核、出账审核、送证审批分派、回款审批
                 StepPropertyEnum.APPROVAL_ASSIGNMENT.getLabel(),
                 StepPropertyEnum.PLAN_AUDIT.getLabel(),
@@ -421,19 +295,18 @@ public class LoanController {
                 StepPropertyEnum.APPROVAL_ASSIGNMENT_2.getLabel(),
                 StepPropertyEnum.REPAY_APPROVAL.getLabel()
         );
-        List<String> financeList= Arrays.asList(
+        List<String> financeList = Arrays.asList(
                 StepPropertyEnum.FINANCE_DISBURSE.getLabel(),//财务出款、财务核算、财务确认
-                StepPropertyEnum.FINANCE_CHECK.getLabel(),
+//                StepPropertyEnum.FINANCE_CHECK.getLabel(),
                 StepPropertyEnum.FINANCE_CONFIRM.getLabel()
         );
 
-        Map<String,List<String>> map = new HashMap<>();
-        map.put(RoleEnum.LEAD_SALES.getMsg(),saleList);
-        map.put(RoleEnum.APPROVER.getMsg(),approvalList);
-        map.put(RoleEnum.FINANCE.getMsg(),financeList);
+        Map<String, List<String>> map = new HashMap<>();
+        map.put(RoleEnum.LEAD_SALES.getMsg(), saleList);
+        map.put(RoleEnum.APPROVER.getMsg(), approvalList);
+        map.put(RoleEnum.FINANCE.getMsg(), financeList);
         return map;
     }
 
 
-
 }

+ 0 - 345
src/main/java/com/loan/system/controller/wechat/LoanController_copy.java

@@ -1,345 +0,0 @@
-package com.loan.system.controller.wechat;
-
-import com.loan.system.context.BaseContext;
-import com.loan.system.domain.dto.CollateralDTO;
-import com.loan.system.domain.dto.ContractDTO;
-import com.loan.system.domain.dto.LoanCaseDTO;
-import com.loan.system.domain.entity.Collateral;
-import com.loan.system.domain.entity.Contract;
-import com.loan.system.domain.entity.Customer;
-import com.loan.system.domain.entity.User;
-import com.loan.system.domain.enums.*;
-import com.loan.system.domain.pojo.Result;
-import com.loan.system.domain.vo.LoanCaseVO;
-import com.loan.system.domain.vo.StepVO;
-import com.loan.system.exception.DescribeException;
-import com.loan.system.service.*;
-import com.loan.system.utils.ResultUtil;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.util.ObjectUtils;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.loan.system.domain.enums.ExceptionEnum.*;
-
-@RestController
-@RequestMapping("/wechat/case/cpoy")
-@Api(tags = "业务受理接口(副本)")
-public class LoanController_copy {
-
-    @Autowired
-    private LoanService loanService;
-    @Autowired
-    private StepService stepService;
-    @Autowired
-    private ContractService contractService;
-    @Autowired
-    private ApprovalService approveService;
-    @Autowired
-    private CustomerService customerService;
-    @Autowired
-    private CustomerOtherService customerOtherService;
-    @Autowired
-    private CollateralService collateralService;
-    @Autowired
-    private ContractAndCollateralService contractAndCollateralService;
-    @Autowired
-    private MessageService messageService;
-    @Autowired
-    private UserService userService;
-
-    @GetMapping
-    @ApiOperation("显示所有业务")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseAll(@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseAll(pageNum, pageSize,false);
-        return ResultUtil.success("success",loanCases);
-    }
-
-    @GetMapping("/customer")
-    @ApiOperation("显示客户的所有业务")
-    @PreAuthorize("@pms.hasAnyRoles('EXTERNAL')")
-    public Result findLoanCaseByCustomerId(@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerId(BaseContext.getCurrentId(),pageNum,pageSize,false);
-        return ResultUtil.success("success",loanCases);
-    }
-
-    @GetMapping("/dealing")
-    @ApiOperation("显示处理中的业务")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByIsComplete(@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        //根据角色返回业务列表,业务员返回全部未完成、审批员与财务员仅展示未审批或未财务审批的人物
-        List<Long> loanCaseIds = loanService.findIdsByIsComplete(DecisionEnum.PROCESS.getMsg(), false);
-        User user = userService.findByIdAndIsDelete(BaseContext.getCurrentId());
-        List<Long> ids = new ArrayList<>();
-        List<LoanCaseVO> loanCases=new ArrayList<>();
-        if(isContainRole(user.getRole(),RoleEnum.APPROVER.getMsg())){
-            for(Long caseId : loanCaseIds){
-                List<StepVO> steps = stepService.getStepByCaseId(caseId);
-                for(StepVO step : steps)
-                    if(step.getStatus().equals(DecisionEnum.PROCESS.getMsg()) &&
-                            (step.getStepName().equals(StepPropertyEnum.APPROVAL.getLabel())||step.getStepName().equals(StepPropertyEnum.APPROVAL_ASSIGNMENT.getLabel())
-                                    ||step.getStepName().equals(StepPropertyEnum.APPROVAL_ASSIGNMENT_2.getLabel())||step.getStepName().equals(StepPropertyEnum.REPAY_APPROVAL.getLabel())
-                                    ||step.getStepName().equals(StepPropertyEnum.DISBURSE_AUDIT.getLabel())||step.getStepName().equals(StepPropertyEnum.PLAN_AUDIT.getLabel()))){
-                        ids.add(caseId);
-                        break;
-                    }
-            }
-            loanCases = loanService.findLoanCaseByIds(ids,pageNum,pageSize,false);
-        }else if(isContainRole(user.getRole(),RoleEnum.FINANCE.getMsg())) {
-            for(Long caseId : loanCaseIds){
-                List<StepVO> steps = stepService.getStepByCaseId(caseId);
-                for (StepVO step : steps)
-                    if (step.getStatus().equals(DecisionEnum.PROCESS.getMsg()) &&
-                            (step.getStepName().equals(StepPropertyEnum.FINANCE_DISBURSE.getLabel()) || step.getStepName().equals(StepPropertyEnum.FINANCE_CHECK.getLabel()) ||
-                                    step.getStepName().equals(StepPropertyEnum.FINANCE_CONFIRM.getLabel()))) {
-                        ids.add(step.getCaseId());
-                        break;
-                    }
-            }
-            loanCases = loanService.findLoanCaseByIds(ids,pageNum,pageSize,false);
-        }else {
-            loanCases = loanService.findLoanCaseByIsComplete(pageNum,pageSize,DecisionEnum.PROCESS.getMsg(),false);
-        }
-
-        return ResultUtil.success("success",loanCases);
-    }
-
-    @GetMapping("/all/{key}")
-    @ApiOperation("按客户关键字查询业务(所有)")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findAllLoanCaseByKey(@PathVariable String key,@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        List<Customer> customers = customerService.getAllCustomerByKey(key, false);
-        List<Long> customerIds = new ArrayList<>();
-        for(Customer customer : customers)
-            customerIds.add(customer.getId());
-
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerIds(customerIds,pageNum,pageSize,false);
-
-        return ResultUtil.success("success",loanCases);
-    }
-
-    @GetMapping("/dealing/{key}")
-    @ApiOperation("按客户关键字查询业务")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByKey(@PathVariable String key,@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        List<Customer> customers = customerService.getAllCustomerByKey(key, false);
-        List<Long> customerIds = new ArrayList<>();
-        for(Customer customer : customers)
-            customerIds.add(customer.getId());
-
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByCustomerIds(customerIds,pageNum,pageSize,DecisionEnum.PROCESS.getMsg(),false);
-
-        return ResultUtil.success("success",loanCases);
-    }
-
-    @GetMapping("/dealing/currentUser/{userId}")
-    @ApiOperation("按当前处理人查询待处理业务")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseByCurrentUser(@PathVariable Long userId,@RequestParam(defaultValue = "0") Integer pageNum,@RequestParam(defaultValue = "20") Integer pageSize){
-        //获取所有业务id
-        List<Long> loanCaseIds = loanService.findIdsByIsComplete(DecisionEnum.PROCESS.getMsg(), false);
-        List<Long> ids = new ArrayList<>();
-        for(Long loanCaseId : loanCaseIds){
-            //寻找每个id的阶段
-            List<StepVO> stepByCaseId = stepService.getStepByCaseId(loanCaseId);
-            //判断是否有未完成的阶段
-            for(StepVO stepVO : stepByCaseId)
-                if(!stepVO.getStatus().equals(StepEnum.COMPLETED.getMsg())&&
-                        ((stepVO.getUserId1()!=null  && stepVO.getUserId1().equals(userId))
-                            ||(stepVO.getUserId2()!=null  && stepVO.getUserId2().equals(userId)))){
-                    //返回该id
-                    ids.add(loanCaseId);
-                    break;
-                }
-
-        }
-
-        List<LoanCaseVO> loanCases = loanService.findLoanCaseByIds(ids,pageNum,pageSize,false);
-        return ResultUtil.success("success",loanCases);
-    }
-
-    @GetMapping("/{id}/details")
-    @ApiOperation("显示业务详情")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails(@PathVariable("id")Long caseId){
-        return ResultUtil.success("success",loanService.findLoanCaseDetailsById(caseId));
-    }
-
-    @PostMapping("/create")
-    @ApiOperation("创建业务")
-    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES','ASSIST_SALES')")
-    public Result createLoanCase(@RequestParam("customerId") Long customerId){
-        if(customerId == null ||!customerService.existsByIdAndIsDelete(customerId))
-            throw new DescribeException(ExceptionEnum.USER_NOT_EXIST);
-        //创建订单
-        LoanCaseVO loanCase = loanService.addLoanCaseByCustomerId(customerId);
-        //创建流程
-//        stepService.addStepByCaseId(loanCase.getId());
-        List<List<Integer>> seq = StepPropertyEnum.seqList();
-        stepService.addStepByCaseIdAndSequences(loanCase.getId(),seq);
-        //设置当前处理人
-        stepService.updateUserByCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),BaseContext.getCurrentId(),loanCase.getId());
-        return ResultUtil.success("success",loanCase);
-    }
-
-    /*
-    1.添加业务,开启业务流程
-    2.绑定阶段和业务
-     */
-    @PostMapping("/{id}")//请求中操作复杂,putmapping仅适用于更新操作
-    @ApiOperation("提交/保存业务")
-    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
-    public Result saveLoanCase(@PathVariable("id")Long caseId, @RequestBody LoanCaseDTO loanCaseDTO, @RequestParam(value = "isComplete",defaultValue = "false")Boolean isComplete){//isComplete 若保存,则设置为未完成,若提交,则设置为已完成
-        if(!loanService.existsByIdAndIsDelete(caseId))
-            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
-
-        if (ObjectUtils.isEmpty(loanCaseDTO))
-            throw new DescribeException(ExceptionEnum.INPUT_ERROR);
-
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
-            throw new DescribeException(STEP_HAS_NOT_PROCESS);
-
-        Long currentId = stepVO.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
-
-        //1.补充业务信息
-        loanService.updateLoanCaseById(loanCaseDTO,caseId);
-
-        //2.设置合同(若存在则修改)
-        contractService.deleteAllByCaseId(caseId);
-        List<Contract> contracts = new ArrayList<>();//新增的合同
-        for(ContractDTO contractDTO : loanCaseDTO.getContracts()){
-            Contract c = contractService.saveContract(contractDTO);
-
-            contracts.add(c);
-        }
-
-        //3.设置押品
-        collateralService.deleteAllByCaseId(caseId);
-        List<Long> collateralIds = new ArrayList<>();
-        for(CollateralDTO collateralDTO : loanCaseDTO.getCollateral()){
-            Collateral collateral = collateralService.saveCollateral(collateralDTO);
-
-            collateralIds.add(collateral.getId());
-        }
-
-        //4.设置合同、押品关联
-        contractAndCollateralService.deleteAllByCaseId(caseId);
-        for(int i=0 ; i < loanCaseDTO.getCollateralAndContract().size(); i++){
-            Contract contract = contracts.get( i);
-            List<Integer> collaterals = loanCaseDTO.getCollateralAndContract().get(i).get(contract.getBusinessAttr());
-
-            for (Integer collateralId : collaterals)
-                contractAndCollateralService.addContractAndCollateral(contract.getId(),collateralIds.get(collateralId-1),caseId);
-        }
-
-        //5.添加其它客户
-        customerOtherService.deleteAllByCaseId(caseId);
-        if(!ObjectUtils.isEmpty(loanCaseDTO.getCustomers1()))
-            customerOtherService.addCustomers(loanCaseDTO.getCustomers1());
-
-        if (!ObjectUtils.isEmpty(loanCaseDTO.getCustomers2()))
-            customerOtherService.addCustomers(loanCaseDTO.getCustomers2());
-
-
-        //6.设置客户婚姻
-        customerService.updateMarriedStatusAndIsIllegalById(loanCaseDTO.getCustomerId(),loanCaseDTO.getMarriedStatus(),loanCaseDTO.getIsIllegal());
-
-        //7.设置阶段负责人与状态(如果第一个人是辅办人员,那第二个人必须是主办)
-        if(isComplete){
-            //stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT_PARENT.getLabel(),caseId);
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),caseId);
-            //设置当阶段为完成,下阶段为开始
-            stepService.tryStartStep(StepPropertyEnum.BUSINESS_ACCEPT.getLabel(),caseId);
-
-
-            //TODO:8.微信推送预审通过消息和通知下一环节
-//            SysMessage message = new SysMessage();
-//            message.setMobile(null);
- //           message.setUserRole(RoleEnum.ASSIST_SALES.getMsg());
-//            message.setMessageTitle("");
-//            message.setMessageContent("");
- //           message.setStepName(StepPropertyEnum.PRE_TRIAL.getLabel());
-  //          message.setRelatedId(caseId);
-//            message.setRelatedType("");
- //           messageService.addMessage(message);
-//            wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
-
-        }
-
-        return ResultUtil.success("success");
-    }
-
-
-
-    @PostMapping("/{id}/preApprove/cancel")
-    @ApiOperation("取消预审")
-    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
-    public Result cancelPreApprove(@PathVariable("id")Long caseId){
-        if(!loanService.existsByIdAndIsDelete(caseId))
-            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
-
-        StepVO step = stepService.findByStepNameAndCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(),caseId);
-        if(ObjectUtils.isEmpty( step)||!step.getStatus().equals(StepEnum.PROCESS.getMsg()))
-            throw new DescribeException(STEP_HAS_NOT_PROCESS);
-
-        Long currentId = step.getUserId1();
-        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-        }
-
-        //设置预审核阶段已完成,审批阶段开始
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.PRE_TRIAL_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.PRE_TRIAL.getLabel(),caseId);
-
-//        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(),StepPropertyEnum.APPROVAL_PARENT.getLabel(),caseId);
-//        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(),StepPropertyEnum.APPROVAL.getLabel(),caseId);
-        stepService.skipStep(StepPropertyEnum.PRE_TRIAL_PARENT.getLabel(),caseId);
-
-        stepService.updateUserByCaseId(StepPropertyEnum.PRE_TRIAL.getLabel(), BaseContext.getCurrentId(), caseId);
-
-        //添加审批记录
-        approveService.addPreApprovalRecord(StepPropertyEnum.PRE_TRIAL.getLabel(),BaseContext.getCurrentId(),caseId);
-
-        //List<StepVO> list = stepService.getStepByCaseId(caseId);
-
-        return ResultUtil.success("success");
-    }
-
-    @PutMapping("/{id}/complete")
-    @ApiOperation("完成业务")
-    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
-    public Result completeLoanCase(@PathVariable("id")Long caseId){
-        if(!loanService.existsByIdAndIsDelete(caseId))
-            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
-
-        List<StepVO> steps = stepService.getStepByCaseId(caseId);
-        for(StepVO step : steps){
-            if(!step.getStatus().equals(StepEnum.COMPLETED.getMsg()))
-                throw new DescribeException(PROJECT_HAS_NOT_COMPLETED);
-        }
-        loanService.updateIsCompleteByCaseId(StepEnum.COMPLETED.getMsg(),caseId);
-        return ResultUtil.success("success");
-    }
-
-    private Boolean isContainRole(String roles,String role){
-        String[] roleArray =roles.split( ",");
-
-        for(String r : roleArray)
-            if(r.equals(role))
-                return true;
-
-        return false;
-    }
-
-
-}

+ 166 - 0
src/main/java/com/loan/system/controller/wechat/MessageSendController.java

@@ -0,0 +1,166 @@
+package com.loan.system.controller.wechat;
+
+import com.loan.system.constant.MessageConstant;
+import com.loan.system.domain.dto.TemplateMessageSendDTO;
+import com.loan.system.domain.enums.ExceptionEnum;
+import com.loan.system.domain.pojo.Result;
+import com.loan.system.domain.pojo.TemplateMessage;
+import com.loan.system.exception.DescribeException;
+import com.loan.system.service.WxService;
+import com.loan.system.utils.ResultUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 微信模板消息发送控制器
+ *
+ * @author system
+ */
+@RestController
+@RequestMapping("/wechat/message")
+@Api(tags = "微信模板消息推送接口")
+@Slf4j
+@Validated
+public class MessageSendController {
+
+    @Autowired
+    private WxService wxService;
+
+    @PostMapping("/send/test")
+    @ApiOperation(value = "发送微信订阅模板消息", notes = "向指定用户发送微信小程序订阅模板消息")
+    public Result sendTemplateMessageTest() {
+
+        // 转换为 TemplateMessage 对象
+        TemplateMessage message = new TemplateMessage();
+
+
+        // 发送消息
+        boolean success = wxService.sendTemplateMessage("oln6A10gAGkHE0TciZCUd67Cxapg", message);
+
+        return success ? ResultUtil.success("消息发送成功") : ResultUtil.error(-500, "消息发送失败");
+    }
+
+    /**
+     * 发送微信订阅模板消息
+     *
+     * @param dto 模板消息发送请求DTO
+     * @return 发送结果
+     */
+    @PostMapping("/send")
+    @ApiOperation(value = "发送微信订阅模板消息", notes = "向指定用户发送微信小程序订阅模板消息")
+    public Result sendTemplateMessage(@Valid @RequestBody TemplateMessageSendDTO dto) {
+        try {
+            log.info("收到发送模板消息请求: openid={}, templateId={}", dto.getOpenid(), dto.getTemplateId());
+
+            // 参数验证
+            if (dto.getData() == null || dto.getData().isEmpty()) {
+                throw new DescribeException(ExceptionEnum.INPUT_ERROR.getCode(), "模板数据不能为空");
+            }
+
+            // 转换为 TemplateMessage 对象
+            TemplateMessage message = new TemplateMessage();
+            message.setTouser(dto.getOpenid());
+            message.setTemplate_id(dto.getTemplateId());
+            message.setPage(dto.getPage());
+            message.setUrl(dto.getUrl());
+            message.setMiniprogram(dto.getMiniprogram());
+            message.setData(dto.getData());
+            message.setClient_msg_id(dto.getClientMsgId());
+
+            // 发送消息
+            boolean success = wxService.sendTemplateMessage(dto.getOpenid(), message);
+
+            if (success) {
+                log.info("模板消息发送成功: openid={}, templateId={}", dto.getOpenid(), dto.getTemplateId());
+                return ResultUtil.success("消息发送成功");
+            } else {
+                log.warn("模板消息发送失败: openid={}, templateId={}", dto.getOpenid(), dto.getTemplateId());
+                // 返回更详细的错误提示,建议用户检查授权状态
+                return ResultUtil.error(-500, "消息发送失败。可能的原因:1) 用户未授权订阅消息;2) 模板ID错误;3) 模板数据格式不正确。请检查日志获取详细错误信息");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("参数错误: {}", e.getMessage());
+            throw new DescribeException(ExceptionEnum.INPUT_ERROR.getCode(), e.getMessage());
+        } catch (Exception e) {
+            log.error("发送模板消息异常: {}", e.getMessage(), e);
+            return ResultUtil.error(-500, "发送消息异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量发送微信订阅模板消息
+     *
+     * @param dto     模板消息发送请求DTO(openid 可以为空,通过其他方式获取)
+     * @param openids 接收者 openid 列表
+     * @return 发送结果统计
+     */
+    @PostMapping("/send/batch")
+    @ApiOperation(value = "批量发送微信订阅模板消息", notes = "向多个用户批量发送微信小程序订阅模板消息")
+    public Result sendBatchTemplateMessage(
+            @Valid @RequestBody TemplateMessageSendDTO dto,
+            @ApiParam(value = "接收者 openid 列表", required = true)
+            @RequestParam @NotBlank(message = "openids 不能为空") String[] openids) {
+        try {
+            log.info("收到批量发送模板消息请求: templateId={}, count={}", dto.getTemplateId(), openids.length);
+
+            if (dto.getData() == null || dto.getData().isEmpty()) {
+                throw new DescribeException(ExceptionEnum.INPUT_ERROR.getCode(), "模板数据不能为空");
+            }
+
+            int successCount = 0;
+            int failCount = 0;
+            StringBuilder failDetails = new StringBuilder();
+
+            // 转换为 TemplateMessage 对象
+            TemplateMessage message = new TemplateMessage();
+            message.setTemplate_id(dto.getTemplateId());
+            message.setPage(dto.getPage());
+            message.setUrl(dto.getUrl());
+            message.setMiniprogram(dto.getMiniprogram());
+            message.setData(dto.getData());
+            message.setClient_msg_id(dto.getClientMsgId());
+
+            // 批量发送
+            for (String openid : openids) {
+                try {
+                    message.setTouser(openid);
+                    boolean success = wxService.sendTemplateMessage(openid, message);
+                    if (success) {
+                        successCount++;
+                    } else {
+                        failCount++;
+                        failDetails.append(openid).append("; ");
+                    }
+                } catch (Exception e) {
+                    failCount++;
+                    failDetails.append(openid).append("(").append(e.getMessage()).append("); ");
+                    log.error("发送消息失败: openid={}, error={}", openid, e.getMessage());
+                }
+            }
+
+            log.info("批量发送完成: 成功={}, 失败={}", successCount, failCount);
+
+            java.util.Map<String, Object> result = new java.util.HashMap<>();
+            result.put("total", openids.length);
+            result.put("successCount", successCount);
+            result.put("failCount", failCount);
+            if (failCount > 0) {
+                result.put("failDetails", failDetails.toString());
+            }
+
+            return ResultUtil.success("批量发送完成", result);
+        } catch (Exception e) {
+            log.error("批量发送模板消息异常: {}", e.getMessage(), e);
+            return ResultUtil.error(-500, "批量发送消息异常: " + e.getMessage());
+        }
+    }
+}
+

+ 353 - 0
src/main/java/com/loan/system/controller/wechat/OssFileController.java

@@ -0,0 +1,353 @@
+package com.loan.system.controller.wechat;
+
+import cn.hutool.core.util.StrUtil;
+import com.loan.system.domain.dto.CompleteMultipartUploadDTO;
+import com.loan.system.domain.entity.Document;
+import com.loan.system.domain.enums.ExceptionEnum;
+import com.loan.system.domain.pojo.Result;
+import com.loan.system.domain.vo.DocumentVO;
+import com.loan.system.service.DocumentService;
+import com.loan.system.service.Impl.OssService;
+import com.loan.system.utils.ResultUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import com.aliyun.oss.model.PartETag;
+
+import java.io.*;
+import java.net.URLEncoder;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+@RestController
+@RequestMapping("/wechat/file")
+@Api(tags = "OSS文件处理接口")
+public class OssFileController {
+    @Autowired
+    private DocumentService fileService;
+    @Autowired
+    private OssService ossService;
+
+    /** 上传文件到OSS */
+    @PostMapping("/upload/{caseId}/{type}")
+    @ApiOperation("OSS文件上传")
+    public Result uploadFileToOss(@PathVariable("caseId") Long caseId, 
+                                  @PathVariable("type") String fileType, 
+                                  @RequestParam(required = false) MultipartFile file, 
+                                  @RequestParam Map<String, String> isDelete) throws IOException {
+        // 处理待删除的文件
+        Iterator<Map.Entry<String, String>> iterator = isDelete.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<String, String> entry = iterator.next();
+            Long id = Long.parseLong(entry.getKey());
+            Integer value = Integer.parseInt(entry.getValue());
+            Document document = fileService.findById(id);
+            if (ObjectUtils.isEmpty(document))
+                break;
+
+            if (value != 0) {
+                System.out.println("删除文件" + id);
+                fileService.deleteFileById(id);
+                // 从OSS删除文件
+                ossService.deleteFile(document.getFileName());
+            }
+        }
+
+        if (file == null)
+            return ResultUtil.success("success");
+
+        String originalName = file.getOriginalFilename();
+        String ext = FilenameUtils.getExtension(originalName).toLowerCase(Locale.ROOT);
+
+        // 生成新的文件名
+        String randomUUID = UUID.randomUUID().toString();
+        String newFileName = String.format("%d/%s/%s.%s", caseId, fileType, randomUUID, ext);
+
+        // 上传到OSS
+        String fileUrl = ossService.uploadFile(file.getInputStream(), newFileName);
+        if (fileUrl == null) {
+            return ResultUtil.error(ExceptionEnum.DIRECTORY_CREATE_ERROR);
+        }
+
+        Document document = new Document();
+        document.setCaseId(caseId);
+        document.setFileName(randomUUID+"."+ext);
+        document.setOriginName(originalName);
+        document.setFilePath(fileUrl); // 存储OSS文件URL
+        document.setFileSize(file.getSize());
+        document.setDocType(ext);
+        document.setDictType(fileType);
+        document.setIsDelete(false);
+        document.setCreateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+        document.setUpdateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+        DocumentVO documentVO = fileService.uploadFile(document);
+
+        return ResultUtil.success("上传成功", documentVO);
+    }
+
+    @GetMapping("/download/{caseId}/{fileType}/{filename:.+}")
+    @ApiOperation("获取OSS文件")
+    public void getFile(@PathVariable("caseId")Long caseId, @PathVariable("fileType") String fileType,@PathVariable("filename") String filename, HttpServletResponse response) {
+        try {
+            String fullFileName = String.format("%d/%s/%s", caseId, fileType, filename);
+            // 从OSS获取文件
+            com.aliyun.oss.model.OSSObject ossObject = ossService.downloadFile(fullFileName);
+
+            if (ossObject == null) {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
+                return;
+            }
+
+            // 设置响应头
+            response.setContentType(ossObject.getObjectMetadata().getContentType());
+            response.setContentLength((int) ossObject.getObjectMetadata().getContentLength());
+
+            // 如果需要浏览器直接显示文件而不是下载,可以设置inline
+            // 如果需要强制下载,可以设置attachment
+            String disposition = "inline; filename=\"" + URLEncoder.encode(filename, "UTF-8") + "\"";
+            response.setHeader("Content-Disposition", disposition);
+
+            // 将文件内容写入响应流
+            try (InputStream inputStream = ossObject.getObjectContent();
+                 OutputStream outputStream = response.getOutputStream()) {
+
+                byte[] buffer = new byte[8192];
+                int bytesRead;
+                while ((bytesRead = inputStream.read(buffer)) != -1) {
+                    outputStream.write(buffer, 0, bytesRead);
+                }
+                outputStream.flush();
+            }
+
+        } catch (Exception e) {
+            try {
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "文件读取失败: " + e.getMessage());
+            } catch (IOException ioException) {
+                // 处理发送错误响应时的异常
+            }
+        }
+    }
+
+    /** 从OSS下载文件 */
+    @GetMapping("/download/{fileName}")
+    @ApiOperation("OSS文件下载")
+    public void downloadFileFromOss(@PathVariable String fileName, 
+                                    @RequestParam String caseId, 
+                                    @RequestParam String fileType, 
+                                    HttpServletResponse response) throws IOException {
+        // 从OSS下载文件
+        com.aliyun.oss.model.OSSObject ossObject = ossService.downloadFile(fileName);
+        if (ossObject == null) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
+            return;
+        }
+
+        System.out.println("文件获取成功: " + fileName + ", 大小: " + ossObject.getObjectMetadata().getContentLength());
+
+        response.setContentType(ossObject.getObjectMetadata().getContentType());
+        response.setHeader("Content-Disposition",
+                "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
+
+        try (InputStream in = ossObject.getObjectContent();
+             OutputStream out = response.getOutputStream()) {
+            byte[] buffer = new byte[8192];
+            int len;
+            while ((len = in.read(buffer)) != -1) {
+                out.write(buffer, 0, len);
+            }
+        }
+    }
+
+    /** 从OSS删除文件 */
+    @DeleteMapping("/delete/{caseId}/{type}")
+    @ApiOperation("OSS文件删除")
+    public Result deleteFileFromOss(@RequestParam String fileName,
+                                    @PathVariable Long caseId, 
+                                    @PathVariable String fileType) {
+        // 从OSS删除文件
+        ossService.deleteFile(fileName);
+        return ResultUtil.success("文件删除成功");
+    }
+
+    /** 获取文件列表 */
+    @GetMapping("/{caseId}")
+    @ApiOperation("获取文件列表")
+    public Result getFileList(@PathVariable Long caseId, @RequestParam(required = false) String type) {
+        List<DocumentVO> files = fileService.findByCaseId(caseId);
+
+        if (StrUtil.isBlank(type))
+            return ResultUtil.success("success", files);
+
+        List<DocumentVO> fileList = new ArrayList<>();
+        for (DocumentVO file : files) {
+            if (file.getDictType().equals(type))
+                fileList.add(file);
+        }
+
+        return ResultUtil.success("success", fileList);
+    }
+    
+    /** 启用版本控制 */
+    @PostMapping("/versioning/enable")
+    @ApiOperation("启用OSS版本控制")
+    public Result enableVersioning() {
+        boolean success = ossService.enableBucketVersioning();
+        if (success) {
+            return ResultUtil.success("版本控制已启用");
+        } else {
+            return ResultUtil.error(ExceptionEnum.VERSION_CONTROLLER_FAILED);
+        }
+    }
+    
+    /** 禁用版本控制 */
+    @PostMapping("/versioning/disable")
+    @ApiOperation("禁用OSS版本控制")
+    public Result disableVersioning() {
+        boolean success = ossService.disableBucketVersioning();
+        if (success) {
+            return ResultUtil.success("版本控制已禁用");
+        } else {
+            return ResultUtil.error(ExceptionEnum.VERSION_CONTROLLER_FAILED);
+        }
+    }
+    
+    /** 获取版本控制状态 */
+    @GetMapping("/versioning/status")
+    @ApiOperation("获取OSS版本控制状态")
+    public Result getVersioningStatus() {
+        String status = ossService.getBucketVersioningStatus();
+        if (status != null) {
+            return ResultUtil.success("当前版本控制状态: " + status, status);
+        } else {
+            return ResultUtil.error(ExceptionEnum.VERSION_CONTROLLER_FAILED);
+        }
+    }
+    
+    /** 初始化分块上传 */
+    @PostMapping("/multipart/init/{caseId}/{type}")
+    @ApiOperation("初始化分块上传")
+    public Result initMultipartUpload(@PathVariable("caseId") Long caseId,
+                                      @PathVariable("type") String fileType,
+                                      @RequestParam String originalName) {
+        String ext = FilenameUtils.getExtension(originalName).toLowerCase(Locale.ROOT);
+        
+        // 生成新的文件名
+        String randomUUID = UUID.randomUUID().toString();
+        String newFileName = String.format("%d/%s/%s.%s", caseId, fileType, randomUUID, ext);
+        
+        // 初始化分块上传
+        String uploadId = ossService.initMultipartUpload(newFileName);
+        if (uploadId == null) {
+            return ResultUtil.error(ExceptionEnum.DIRECTORY_CREATE_ERROR);
+        }
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("uploadId", uploadId);
+        result.put("fileName", newFileName);
+        result.put("randomUUID", randomUUID);
+        result.put("ext", ext);
+        
+        return ResultUtil.success("初始化成功", result);
+    }
+    
+    /** 上传分块 */
+    @PostMapping("/multipart/upload")
+    @ApiOperation("上传分块")
+    public Result uploadPart(@RequestParam String fileName,
+                            @RequestParam String uploadId,
+                            @RequestParam Integer partNumber,
+                            @RequestParam MultipartFile chunk) throws IOException {
+        if (chunk == null || chunk.isEmpty()) {
+            return ResultUtil.error(ExceptionEnum.FILE_IS_EMPTY);
+        }
+        
+        // 上传分块
+        PartETag partETag = ossService.uploadPart(fileName, uploadId, partNumber, 
+                                                  chunk.getInputStream(), chunk.getSize());
+        if (partETag == null) {
+            return ResultUtil.error(ExceptionEnum.FILE_BLOCK_UPLOAD_FAILED);
+        }
+        
+        Map<String, Object> result = new HashMap<>();
+        result.put("partNumber", partETag.getPartNumber());
+        result.put("eTag", partETag.getETag());
+        
+        return ResultUtil.success("分块上传成功", result);
+    }
+    
+    /** 完成分块上传 */
+    @PostMapping("/multipart/complete/{caseId}/{type}")
+    @ApiOperation("完成分块上传")
+    public Result completeMultipartUpload(@PathVariable("caseId") Long caseId,
+                                         @PathVariable("type") String fileType,
+                                         @RequestBody CompleteMultipartUploadDTO request) {
+        // 处理待删除的文件
+        if (request.getIsDelete() != null) {
+            Iterator<Map.Entry<String, String>> iterator = request.getIsDelete().entrySet().iterator();
+            while (iterator.hasNext()) {
+                Map.Entry<String, String> entry = iterator.next();
+                Long id = Long.parseLong(entry.getKey());
+                Integer value = Integer.parseInt(entry.getValue());
+                Document document = fileService.findById(id);
+                if (ObjectUtils.isEmpty(document))
+                    break;
+
+                if (value != 0) {
+                    System.out.println("删除文件" + id);
+                    fileService.deleteFileById(id);
+                    // 从OSS删除文件
+                    ossService.deleteFile(document.getFileName());
+                }
+            }
+        }
+        
+        // 将DTO列表转换为PartETag列表
+        List<PartETag> partETags = new ArrayList<>();
+        if (request.getPartETagsList() != null) {
+            for (CompleteMultipartUploadDTO.PartETagDTO partETagDTO : request.getPartETagsList()) {
+                partETags.add(new PartETag(partETagDTO.getPartNumber(), partETagDTO.getETag()));
+            }
+        }
+        
+        // 按分块编号排序
+        partETags.sort(Comparator.comparing(PartETag::getPartNumber));
+        
+        // 完成分块上传
+        String fileUrl = ossService.completeMultipartUpload(request.getFileName(), request.getUploadId(), partETags);
+        if (fileUrl == null) {
+            return ResultUtil.error(ExceptionEnum.DIRECTORY_CREATE_ERROR);
+        }
+        
+        // 保存文件信息到数据库(与uploadFileToOss逻辑一致)
+        Document document = new Document();
+        document.setCaseId(caseId);
+        document.setFileName(request.getRandomUUID() + "." + request.getExt());
+        document.setOriginName(request.getOriginalName());
+        document.setFilePath(fileUrl); // 存储OSS文件URL
+        document.setFileSize(request.getFileSize());
+        document.setDocType(request.getExt());
+        document.setDictType(fileType);
+        document.setIsDelete(false);
+        document.setCreateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+        document.setUpdateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+        DocumentVO documentVO = fileService.uploadFile(document);
+        
+        return ResultUtil.success("上传成功", documentVO);
+    }
+    
+    /** 取消分块上传 */
+    @PostMapping("/multipart/abort")
+    @ApiOperation("取消分块上传")
+    public Result abortMultipartUpload(@RequestParam String fileName,
+                                      @RequestParam String uploadId) {
+        ossService.abortMultipartUpload(fileName, uploadId);
+        return ResultUtil.success("已取消分块上传");
+    }
+}

+ 117 - 394
src/main/java/com/loan/system/controller/wechat/RepaymentController.java

@@ -14,14 +14,13 @@ import com.loan.system.service.DisbursementRecordService;
 import com.loan.system.utils.ResultUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import net.bytebuddy.dynamic.scaffold.MethodRegistry;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.*;
 
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -31,35 +30,28 @@ import static com.loan.system.utils.ValidUtil.isAllFieldsNull;
 @RestController
 @RequestMapping("/wechat/repayment")
 @Api(tags = "还款接口")
+@Slf4j
 public class RepaymentController {
 
-    @Autowired
-    private DisbursementService disbursementService;
     @Autowired
     private StepService stepService;
     @Autowired
-    private ContractService contractService;
-    @Autowired
-    private ContractRepaymentService contractRepaymentService;
-    @Autowired
     private RepaymentService repaymentService;
     @Autowired
-    private RepaymentRecordService repaymentRecordService;
-    @Autowired
     private LoanService loanService;
 
     @GetMapping("/{caseId}/repayDetails")
     @ApiOperation("显示回款详情")//与loanContraller的方法一致,但为了规范
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails(@PathVariable("caseId")Long caseId){
-        if(!loanService.existsByIdAndIsDelete(caseId))
+    public Result findLoanCaseDetails(@PathVariable("caseId") Long caseId) {
+        if (!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.REPAY_START.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO) || stepVO.getStatus().equals(StepEnum.UNSTART.getMsg()))
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.REPAY_START.getCode(), caseId);
+        if (ObjectUtils.isEmpty(stepVO) || stepVO.getStatus().equals(StepEnum.UNSTART.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        return ResultUtil.success("success",repaymentService.getDetails(caseId, StepPropertyEnum.REPAY_START.getLabel()));
+        return ResultUtil.success("success", repaymentService.getDetails(caseId, StepPropertyEnum.REPAY_START.getCode()));
     }
 
 
@@ -67,134 +59,49 @@ public class RepaymentController {
     @ApiOperation(value = "启动回款")
     @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
     public Result addDisbursement(@RequestBody RepaymentRecordDTO repaymentRecordDTO, @RequestParam Long caseId, @PathVariable("id") Long recordId) {
-        if(ObjectUtils.isEmpty(repaymentRecordDTO)||isAllFieldsNull(repaymentRecordDTO))
+        if (ObjectUtils.isEmpty(repaymentRecordDTO) || isAllFieldsNull(repaymentRecordDTO))
             throw new DescribeException(INPUT_ERROR);
 
-        System.out.println(repaymentRecordDTO);
-
-        if(caseId == null ||!loanService.existsByIdAndIsDelete(caseId))
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.REPAY_START.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.REPAY_START.getCode(), caseId);
+        if (ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-
-        Repayment repayment = repaymentService.findByCaseIdAndIsDelete(caseId, false);
-        if(ObjectUtils.isEmpty(repayment)){
-            Repayment rep = new Repayment();
-            rep.setCaseId(caseId);
-            rep.setStartUserId(BaseContext.getCurrentId());
-            rep.setTotalAmount(disbursementService.getDisbursementByCaseId(caseId).getPlannedAmount());
-            repayment=repaymentService.addRepayment(rep);
-        }
-
-        RepaymentRecord record =new RepaymentRecord();
-        //上报计划(-1表示当前为新增)
-        if(recordId <= 0){
-            List<RepaymentRecord> repaymentRecords = repaymentRecordService.findByRepaymentIdAndIsDelete(repayment.getId(), false);
-            if(!repaymentRecords.isEmpty()){
-                record = repaymentRecords.get(repaymentRecords.size()-1);
-                if(record.getApprovalDecision()==null || !record.getApprovalDecision().equals(DecisionEnum.PASS.getMsg()))
-                    throw new DescribeException(REPAYMENT_HAS_BEEN_REJECTED);
-            }
-            RepaymentRecord repaymentRecord = repaymentRecordService.addRepaymentRecord(repaymentRecordDTO, repayment.getId());
-            recordId = repaymentRecord.getId();
-        } else{
-            //审批意见为通过,无法修改
-            record = repaymentRecordService.findByIdAndIsDelete(recordId);
-            if(record.getApprovalDecision()==null || record.getApprovalDecision().equals(DecisionEnum.PASS.getMsg()))
-                throw new DescribeException(REPAYMENT_HAS_COMPLETED);
-
-            repaymentRecordService.updateRepaymentRecordById(repaymentRecordDTO,recordId);
-            //获取合同回款
-            List<Long> Ids = contractRepaymentService.findByRepaymentRecordIdAndIsDelete(recordId);
-            contractRepaymentService.deleteByIds(Ids);
-
-        }
-
-        //创建新关联关系
-        Iterator<Map.Entry<Long, Double>> iterator = repaymentRecordDTO.getContractIdAndAmount().entrySet().iterator();
-        while (iterator.hasNext()) {
-            Map.Entry<Long, Double> entry = iterator.next();
-            Long contractId = entry.getKey();
-            double amount = entry.getValue();
-            contractRepaymentService.addContractRepayment(recordId,contractId,caseId,amount,record.getRepayTime());
-
-        }
-
-        //修改状态
-        if(isCleared(caseId,StepPropertyEnum.REPAY_START.getLabel())){
-            //若结清,则回款完成
-            stepService.updateStatusByCaseId( StepEnum.COMPLETED.getMsg(),StepPropertyEnum.REPAY_START.getLabel(), caseId);
-        }
-
-        stepService.tryStartStep(StepPropertyEnum.REPAY_START.getLabel(),caseId);
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
+        repaymentService.addDisbursement(repaymentRecordDTO, caseId, recordId);
 
         return ResultUtil.success("success");
     }
 
     @GetMapping("/{caseId}/approvalDetails")
     @ApiOperation("显示审批详情")//与loanContraller的方法一致,但为了规范
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails2(@PathVariable("caseId")Long caseId){
-        if (caseId == null ||!loanService.existsByIdAndIsDelete(caseId))
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER', 'BACK_OFFICE')")
+    public Result findLoanCaseDetails2(@PathVariable("caseId") Long caseId) {
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        return ResultUtil.success("success",repaymentService.getDetails(caseId, StepPropertyEnum.REPAY_APPROVAL.getLabel()));
+        return ResultUtil.success("success", repaymentService.getDetails(caseId, StepPropertyEnum.REPAY_APPROVAL.getCode()));
     }
 
     //TODO:在回款记录添加审批记录,且每次审批完之后才能再回款
     @PostMapping("/approval/pass")
     @ApiOperation(value = "回款审批")
     @PreAuthorize("@pms.hasRole('APPROVER')")
-    public Result repaymentApproval(@RequestBody RepaymentApprovalDTO approvalDTO,@RequestParam Long chargeId) {
-        if(ObjectUtils.isEmpty(approvalDTO)||approvalDTO.getCaseId() == null||approvalDTO.getRecordId() == null)
+    public Result repaymentApproval(@RequestBody RepaymentApprovalDTO approvalDTO, @RequestParam Long chargeId, @RequestParam(required = false) Long assistantId) {
+        if (ObjectUtils.isEmpty(approvalDTO) || approvalDTO.getCaseId() == null || approvalDTO.getRecordId() == null)
             throw new DescribeException(INPUT_ERROR);
 
-        if(!loanService.existsByIdAndIsDelete(approvalDTO.getCaseId()))
+        if (!loanService.existsByIdAndIsDelete(approvalDTO.getCaseId()))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
         Long caseId = approvalDTO.getCaseId();
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.REPAY_APPROVAL.getLabel(),caseId);
-        if(!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.REPAY_APPROVAL.getCode(), caseId);
+        if (!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        //填写审批意见
-        repaymentRecordService.updateApprovalById(approvalDTO.getComments(),DecisionEnum.PASS.getMsg(),BaseContext.getCurrentId(),approvalDTO.getRecordId());
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.REPAY_APPROVAL.getLabel(), BaseContext.getCurrentId(), caseId);
-        //TODO:更新结清状态(因为是一审一批,只有通过的时候才结清状态,不通过则不结清,保证了结清时的总金额为审批通过的)
-        contractIsCleared(repaymentService.findByCaseIdAndIsDelete(caseId,false).getId(),chargeId);
-
-        if(isCleared(caseId,StepPropertyEnum.REPAY_APPROVAL.getLabel()))
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.REPAY_APPROVAL.getLabel(), caseId);
-        stepService.tryStartStep(StepPropertyEnum.REPAY_APPROVAL.getLabel(),caseId);
+        repaymentService.repaymentApproval(approvalDTO, chargeId, assistantId);
 
         return ResultUtil.success("success");
     }
@@ -203,366 +110,182 @@ public class RepaymentController {
     @ApiOperation(value = "回款审批驳回")
     @PreAuthorize("@pms.hasRole('APPROVER')")
     public Result repaymentCancel(@RequestBody RepaymentApprovalDTO approvalDTO) {
-        if(ObjectUtils.isEmpty(approvalDTO)||approvalDTO.getCaseId() == null||approvalDTO.getRecordId() == null)
+        if (ObjectUtils.isEmpty(approvalDTO) || approvalDTO.getCaseId() == null || approvalDTO.getRecordId() == null)
             throw new DescribeException(INPUT_ERROR);
 
-        if(!loanService.existsByIdAndIsDelete(approvalDTO.getCaseId()))
+        if (!loanService.existsByIdAndIsDelete(approvalDTO.getCaseId()))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
         Long caseId = approvalDTO.getCaseId();
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.REPAY_APPROVAL.getLabel(),caseId);
-        if(stepVO.getStatus().equals(StepEnum.COMPLETED.getMsg()))
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.REPAY_APPROVAL.getCode(), caseId);
+        if (stepVO.getStatus().equals(StepEnum.COMPLETED.getMsg()))
             throw new DescribeException(ExceptionEnum.STEP_HAS_COMPLETEED);
 
-        //填写驳回意见
-        repaymentRecordService.updateApprovalById(approvalDTO.getComments(),DecisionEnum.REJECT.getMsg(),BaseContext.getCurrentId(),approvalDTO.getRecordId());
-
-        //修改负责人
-        stepService.updateUserByCaseId(StepPropertyEnum.REPAY_APPROVAL.getLabel(), BaseContext.getCurrentId(), caseId);
-        stepService.updateStatusByCaseId(StepEnum.PROCESS.getMsg(), StepPropertyEnum.REPAY_START.getLabel(),  caseId);
-        stepService.updateStatusByCaseId(StepEnum.UNSTART.getMsg(), StepPropertyEnum.REPAY_APPROVAL.getLabel(), caseId);
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
+        repaymentService.repaymentCancel(approvalDTO);
 
         return ResultUtil.success("success");
     }
 
-    //TODO:用于判断总的金额是否结清
-    private Boolean isCleared(Long caseId,String stepName){
-        Repayment repayment = repaymentService.findByCaseIdAndIsDelete(caseId, false);
-        //获取回款记录
-        List<RepaymentRecord> repaymentRecords = repaymentRecordService.findByRepaymentIdAndIsDelete(repayment.getId(), false);
-        Double totalAmount = repaymentService.findByCaseIdAndIsDelete(caseId, false).getTotalAmount();
-
-        Double currentAmount= 0.0;
-        for (RepaymentRecord repaymentRecord : repaymentRecords)
-            if(stepName.equals(StepPropertyEnum.REPAY_APPROVAL.getLabel())){
-                if(repaymentRecord.getApprovalDecision()!=null && repaymentRecord.getApprovalDecision().equals(DecisionEnum.PASS.getMsg())
-                        && !repaymentRecord.getIsInterest())//审批、财务环节显示审批通过后的总额
-                    currentAmount += repaymentRecord.getAmount();
-            }else if(stepName.equals(StepPropertyEnum.REPAY_START.getLabel())){
-                if( !repaymentRecord.getIsInterest())//审批、财务环节显示审批通过后的总额
-                    currentAmount += repaymentRecord.getAmount();
-            }
-
-        return Math.abs(totalAmount-currentAmount)< Double.MIN_VALUE;
-    }
-
-    /*TODO:更新合同结清状态
-    * 1.根据recordId查合同关联表,
-    * 2.查出每个合同id对应的回款金额
-    * 3.若回款金额与借款金额相同,则结清
-    * */
-    private void contractIsCleared(Long repaymentId,Long chargeId) {
-        //1.
-        List<RepaymentRecord> repaymentRecords = repaymentRecordService.findByRepaymentIdAndIsDelete(repaymentId, false);
-        List<Long> recordIds= repaymentRecords.stream().map(RepaymentRecord::getId).collect(Collectors.toList());
-        List<ContractRepayment> contractRepayments = contractRepaymentService.findByRepaymentRecordIdsAndIsDelete(recordIds);
-
-        //2.
-        List<Long> contractIds = contractRepayments.stream().map(ContractRepayment::getContractId).collect(Collectors.toList());
-        for(Long contractId:contractIds){
-            double amount = 0.0;
-            for (ContractRepayment contractRepayment:contractRepayments){
-                RepaymentRecord repaymentRecord = repaymentRecordService.findByIdAndIsDelete(contractRepayment.getRepaymentRecordId());
-                if(!repaymentRecord.getIsInterest() && contractRepayment.getContractId().equals(contractId))
-                    amount += contractRepayment.getAmount();
-            }
-
-            System.out.println("amount:"+amount);
-            //3.
-            if(Math.abs(amount-contractService.findContractById(contractId).getContractAmount())< Double.MIN_VALUE)
-                contractService.updateRepayById(true,0.0,chargeId,contractId,false);
-        }
-    }
-    @GetMapping("/financeDetails")
-    @ApiOperation("显示财务核算详情")//与loanContraller的方法一致,但为了规范
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails3(@RequestParam Long caseId){
-        if (caseId == null ||!loanService.existsByIdAndIsDelete(caseId))
-            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
-        return ResultUtil.success("success",repaymentService.getDetails(caseId,StepPropertyEnum.FINANCE_CHECK.getLabel()));
-    }
+//    @GetMapping("/financeDetails")
+//    @ApiOperation("显示财务核算详情")//与loanContraller的方法一致,但为了规范
+//    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER', 'FINANCE', 'BACK_OFFICE')")
+//    public Result findLoanCaseDetails3(@RequestParam Long caseId){
+//        if (caseId == null ||!loanService.existsByIdAndIsDelete(caseId))
+//            throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
+//        return ResultUtil.success("success",repaymentService.getDetails(caseId,StepPropertyEnum.FINANCE_CHECK.getCode()));
+//    }
 
     @PostMapping("/financeCheck/{caseId}")
-    @ApiOperation(value = "财务核算")
+    @ApiOperation(value = "财务核算")//x
     @PreAuthorize("@pms.hasRole('FINANCE')")
-    public Result financeDisbursement( @PathVariable Long caseId,@RequestParam Double interest,@RequestParam Long contractId) {
-        if(caseId==null||!loanService.existsByIdAndIsDelete(caseId))
+    public Result financeDisbursement(@PathVariable Long caseId, @RequestParam Double interest, @RequestParam Long contractId) {
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.FINANCE_CHECK.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
-            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+//        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.FINANCE_CHECK.getCode(),caseId);
+//        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+//            throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        Contract contract = contractService.findContractById(contractId);
-        if(!contract.getIsCleared())
-            throw new DescribeException(FUND_NOT_CLEAR);
-        if(contract.getFinanceUserId()!=null && !contract.getFinanceUserId().equals(BaseContext.getCurrentId()))
-            throw new DescribeException(STEP_USER_NOT_EXPECTED);
+        //TODO:1.无需结清,一次回款计划结算一次,按合同结算,并统计本次利息金额
+//        Contract contract = contractService.findContractById(contractId);
+//        if(!contract.getIsCleared())
+//            throw new DescribeException(FUND_NOT_CLEAR);
 
-        //修改合同信息
-        contractService.updateRepayById(true,interest,BaseContext.getCurrentId(),contractId,false);
-
-        //修改状态
-        stepService.updateUserByCaseId(StepPropertyEnum.FINANCE_CHECK.getLabel(), BaseContext.getCurrentId(), caseId);
-        boolean flag = true;
-        List<Contract> contractByCaseId = contractService.findContractByCaseId2(caseId);
-        for (Contract contract2 : contractByCaseId)
-            if (contract2.getFinanceUserId()== null){
-                flag = false;
-                break;
-            }
-        if (flag)//如果全部合同都有财务人员,则表示全部核算完成
-            stepService.updateStatusByCaseId( StepEnum.COMPLETED.getMsg(),StepPropertyEnum.FINANCE_CHECK.getLabel(),caseId);
-
-        stepService.tryStartStep(StepPropertyEnum.FINANCE_CHECK.getLabel(),caseId);
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
+        repaymentService.financeDisbursement(caseId, interest, contractId);
 
         return ResultUtil.success("success");
     }
 
     @GetMapping("/balanceDetails")
     @ApiOperation("显示费息回款详情")//与loanContraller的方法一致,但为了规范
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result findLoanCaseDetails4(@RequestParam Long caseId){
-        if (caseId == null ||!loanService.existsByIdAndIsDelete(caseId))
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES','FINANCE', 'ASSIST_SALES','BACK_OFFICE')")
+    public Result findLoanCaseDetails4(@RequestParam Long caseId) {
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
-
-        return ResultUtil.success("success",repaymentService.getDetails(caseId,StepPropertyEnum.BALANCE_REPAY.getLabel()));
+        return ResultUtil.success("success", repaymentService.getDetails(caseId, StepPropertyEnum.BALANCE_REPAY.getCode()));
     }
 
+    /*
+    TODO:
+    1.显示本次回款本金
+    2.若本金结清,可输入合同利息总额
+    3.显示本次回款利息
+    4.一次汇款可能从多个银行汇款,所以再加一个表绑定汇款计划
+     */
+
     @PostMapping("/balanceRepay")
-    @ApiOperation(value = "费息回款")
+    @ApiOperation(value = "本金加费息回款")
     @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
-    public Result balanceRepay(@RequestParam Long caseId , @RequestBody RepaymentRecordDTO repaymentRecordDTO ) {
-        if(ObjectUtils.isEmpty(repaymentRecordDTO)||caseId==null)
+    public Result balanceRepay(@RequestParam Long caseId, @RequestBody RepaymentRecordDTO repaymentRecordDTO) {
+        if (ObjectUtils.isEmpty(repaymentRecordDTO) || caseId == null)
             throw new DescribeException(INPUT_ERROR);
-        if(!loanService.existsByIdAndIsDelete(caseId))
+        if (!loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.BALANCE_REPAY.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.BALANCE_REPAY.getCode(), caseId);
+        if (ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-//        Long currentId = stepVO.getUserId1();
-//        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-//            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-//        }
-
-        //添加回款记录
-        repaymentRecordDTO.setIsInterest( true);
-        RepaymentRecord record = repaymentRecordService.addRepaymentRecord(repaymentRecordDTO, repaymentService.findByCaseIdAndIsDelete(caseId, false).getId());
-        //创建新关联关系
-        Iterator<Map.Entry<Long, Double>> iterator = repaymentRecordDTO.getContractIdAndAmount().entrySet().iterator();
-        while (iterator.hasNext()) {
-            Map.Entry<Long, Double> entry = iterator.next();
-            Long contractId = entry.getKey();
-            double amount = entry.getValue();
-            contractRepaymentService.addContractRepayment(record.getId(),contractId,caseId,amount,record.getRepayTime());
-
+        if (stepVO.getUserIds() != null) {
+            boolean flag = false;//是否存在
+            String[] userIds = stepVO.getUserIds().split(",");//分主办与辅办,但仅需任意一人操作即可
+            for (String userId : userIds) {
+                Long currentId = Long.valueOf(userId);
+                if (BaseContext.getCurrentId().equals(currentId)) {
+                    flag = true;
+                    break;
+                }
+            }
+            if (!flag)
+                //全部遍历结束未break,表示不是预期用户
+                throw new DescribeException(STEP_USER_NOT_EXPECTED);
         }
 
-        //TODO:添加赎当号码
-
-        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.BALANCE_REPAY.getLabel(), BaseContext.getCurrentId(), caseId);
-        if(interestIsCleared(repaymentService.findByCaseIdAndIsDelete(caseId, false).getId()))//当全部的合同的费息都完成时才结束环节
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.BALANCE_REPAY.getLabel(),caseId);
-
-        stepService.tryStartStep(StepPropertyEnum.BALANCE_REPAY.getLabel(),caseId);
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
+        repaymentService.balanceRepay(repaymentRecordDTO, caseId);
 
         return ResultUtil.success("success");
     }
-    /*TODO:更新合同费息结清状态
-     * 1.根据recordId查合同关联表,
-     * 2.查出每个合同id对应的回款金额
-     * 3.若全部合同的回款金额与费息相同,则结清
-     * */
-    private Boolean interestIsCleared(Long repaymentId) {
-        //1.
-        List<RepaymentRecord> repaymentRecords = repaymentRecordService.findByRepaymentIdAndIsInterestAndIsDelete(repaymentId, false);
-        List<Long> recordIds= repaymentRecords.stream().map(RepaymentRecord::getId).collect(Collectors.toList());
-        List<ContractRepayment> contractRepayments = contractRepaymentService.findByRepaymentRecordIdsAndIsDelete(recordIds);
-
-        //2.
-        List<Long> contractIds = contractRepayments.stream().map(ContractRepayment::getContractId).collect(Collectors.toList());
-        for(Long contractId:contractIds){
-            double amount = 0.0;
-            for (ContractRepayment contractRepayment:contractRepayments){
-                RepaymentRecord record = repaymentRecordService.findByIdAndIsDelete(contractRepayment.getRepaymentRecordId());
-                if(contractRepayment.getContractId().equals(contractId)&&record.getIsInterest())
-                    amount += contractRepayment.getAmount();
-            }
-
-            //3.
-            if(Math.abs(amount-contractService.findContractById(contractId).getInterestAmount())> Double.MIN_VALUE)
-                return false;
-        }
-        return true;
-    }
 
+    /*
+    客户名称、合同金额、剩余回款金额、本次回款金额、本次回款费息、本次回款本金及费息合计;
+     */
     @PostMapping("/financeConfirm")
     @ApiOperation(value = "财务确认")
     @PreAuthorize("@pms.hasRole('FINANCE')")
-    public Result financeConfirm(@RequestParam List<Long> recordIds,@RequestParam Long caseId) {//TODO:一下子可以确认多笔
-        if(caseId==null || !loanService.existsByIdAndIsDelete(caseId))
+    public Result financeConfirm(@RequestParam List<Long> recordIds, @RequestParam Long caseId, @RequestParam String comment) {//TODO:一下子可以确认多笔
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.FINANCE_CONFIRM.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.FINANCE_CONFIRM.getCode(), caseId);
+        if (ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-//        Long currentId = stepVO.getUserId1();
-//        if(currentId!=null && !BaseContext.getCurrentId().equals(currentId)){
-//            throw new DescribeException(STEP_USER_NOT_EXPECTED);
-//        }
-        for(Long recordId:recordIds){
-            repaymentRecordService.updateApprovalById("",DecisionEnum.PASS.getMsg(),BaseContext.getCurrentId(),recordId);
-        }
-
-
-        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.FINANCE_CONFIRM.getLabel(), BaseContext.getCurrentId(), caseId);
-        if(interestIsCleared(repaymentService.findByCaseIdAndIsDelete(caseId, false).getId())){
-            stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.FINANCE_CONFIRM.getLabel(),caseId);
-
-//            loanService.updateIsCompleteByCaseId(DecisionEnum.COMPLETE.getMsg(),caseId);
-        }
-        stepService.tryStartStep(StepPropertyEnum.FINANCE_CONFIRM.getLabel(),caseId);
-
-        //TODO:微信推送预审通过消息和通知下一环节
-//        SysMessage message = new SysMessage();
-//        message.setMobile(null);
-//        message.setUserRole("");
-//        message.setMessageTitle("");
-//        message.setMessageContent("");
-//        message.setStepName("");
-//        message.setRelatedId(loanCase.getId());
-//        message.setRelatedType("");
-//        messageService.addMessage(message);
-//        wxService.sendTemplateMessage(loanCase.getCustomer().getMobile(),new TemplateMessage());
+        repaymentService.financeConfirm(recordIds, caseId, comment);
 
         return ResultUtil.success("success");
     }
 
     @PostMapping("/repayComplete")
     @ApiOperation(value = "回款完成")
-    @PreAuthorize("@pms.hasAnyRoles('APPROVAL')")
+    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES', 'ASSIST_SALES')")
     public Result repayComplete(@RequestParam Long caseId) {
-        if(caseId==null || !loanService.existsByIdAndIsDelete(caseId))
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.REPAYMENT_COMPLETE.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.REPAYMENT_COMPLETE.getCode(), caseId);
+        if (ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
             throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        StepVO stepVO1 = stepService.findByStepNameAndCaseId(StepPropertyEnum.FINANCE_CONFIRM.getLabel(), caseId);
-        if(ObjectUtils.isEmpty(stepVO1)||!stepVO1.getStatus().equals(StepEnum.COMPLETED.getMsg()))
+        StepVO stepVO1 = stepService.findByStepCodeAndCaseId(StepPropertyEnum.FINANCE_CONFIRM.getCode(), caseId);
+        if (ObjectUtils.isEmpty(stepVO1) || !stepVO1.getStatus().equals(StepEnum.COMPLETED.getMsg()))
             throw new DescribeException(PRE_STEP_NOT_COMPLETE);
 
         List<StepVO> steps = stepService.getStepByCaseId(caseId);
-        for(StepVO step:steps){
-            if(!step.getStatus().equals(StepEnum.COMPLETED.getMsg()))
+        for (StepVO step : steps) {
+            if (!step.getParentCode().equals(stepVO.getParentCode()) || step.getCode().equals(StepPropertyEnum.CASE_COMPLETE.getCode()) || step.getCode().equals(stepVO.getCode()))
+                continue;
+            if (!step.getStatus().equals(StepEnum.COMPLETED.getMsg()))
                 throw new DescribeException(STEP_EXIST_NOT_COMPLETE);
         }
 
+        repaymentService.repayComplete(caseId);
 
-        //更新回款单
-        repaymentService.updateStatusByCaseId(DecisionEnum.REPAYMENT_COMPLETE.getMsg(),caseId);
-
+        return ResultUtil.success("success");
+    }
 
-        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.REPAYMENT_COMPLETE.getLabel(),BaseContext.getCurrentId(), caseId);
-        //stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.REPAY_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.REPAYMENT_COMPLETE.getLabel(),caseId);
+    @GetMapping("/caseDetails")
+    @ApiOperation("显示业务终结详情")//
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES','FINANCE', 'ASSIST_SALES','BACK_OFFICE')")
+    public Result getCompleteDetails(@RequestParam Long caseId) {
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
+            throw new DescribeException(PROJECT_NOT_EXIST);
 
-        stepService.tryStartStep(StepPropertyEnum.REPAYMENT_COMPLETE.getLabel(),caseId);
-        return ResultUtil.success("success");
+        return ResultUtil.success("success", repaymentService.getCompleteDetails(caseId));
     }
 
     @PostMapping("/caseComplete")
     @ApiOperation(value = "业务完成")
     @PreAuthorize("@pms.hasAnyRoles('BACK_OFFICE')")
-    public Result loanCaseComplete(@RequestParam Long caseId) {
-        if(caseId==null || !loanService.existsByIdAndIsDelete(caseId))
+    public Result loanCaseComplete(@RequestParam Long caseId, @RequestParam Boolean isCancel, @RequestBody Map<String, String> pawnList) {
+        if (caseId == null || !loanService.existsByIdAndIsDelete(caseId))
             throw new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        StepVO stepVO = stepService.findByStepNameAndCaseId(StepPropertyEnum.CASE_COMPLETE.getLabel(),caseId);
-        if(ObjectUtils.isEmpty(stepVO)||!stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
-            throw new DescribeException(STEP_HAS_NOT_PROCESS);
+        if (isCancel)
+            loanService.loanCaseComplete(caseId, true, pawnList);
 
-        StepVO stepVO1 = stepService.findByStepNameAndCaseId(StepPropertyEnum.FINANCE_CONFIRM.getLabel(), caseId);
-        if(ObjectUtils.isEmpty(stepVO1)||!stepVO1.getStatus().equals(StepEnum.COMPLETED.getMsg()))
-            throw new DescribeException(PRE_STEP_NOT_COMPLETE);
+        StepVO stepVO = stepService.findByStepCodeAndCaseId(StepPropertyEnum.CASE_COMPLETE.getCode(), caseId);
+        if (ObjectUtils.isEmpty(stepVO) || !stepVO.getStatus().equals(StepEnum.PROCESS.getMsg()))
+            throw new DescribeException(STEP_HAS_NOT_PROCESS);
 
-        //设置业务状态
-        loanService.updateIsCompleteByCaseId(DecisionEnum.COMPLETE.getMsg(),caseId);
+        if (stepVO.getUserId1() != null && !BaseContext.getCurrentId().equals(stepVO.getUserId1()))
+            throw new DescribeException(STEP_USER_NOT_EXPECTED);
 
-        stepService.updateUserId1ByCaseIdAndStepName(StepPropertyEnum.CASE_COMPLETE.getLabel(),BaseContext.getCurrentId(), caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.REPAY_PARENT.getLabel(),caseId);
-        stepService.updateStatusByCaseId(StepEnum.COMPLETED.getMsg(),StepPropertyEnum.CASE_COMPLETE.getLabel(),caseId);
+        //设置业务状态 TODO:撤销,防止误判
+        loanService.loanCaseComplete(caseId, isCancel, pawnList);
 
-        stepService.tryStartStep(StepPropertyEnum.CASE_COMPLETE.getLabel(),caseId);
         return ResultUtil.success("success");
     }
 
 
-    //TODO:因为现在是一审一批,所以仅最后一笔金额可能是未通过
-//    private Boolean isCleared(Long caseId,String stepName){
-//        Repayment repayment = repaymentService.findByCaseIdAndIsDelete(caseId, false);
-//        //获取回款记录
-//        List<RepaymentRecord> repaymentRecords = repaymentRecordService.findByRepaymentIdAndIsDelete(repayment.getId(), false);
-//        Double totalAmount = repaymentService.findByCaseIdAndIsDelete(caseId, false).getTotalAmount();
-//
-//        Double currentAmount= 0.0;
-//        if(stepName.equals(StepPropertyEnum.REPAY_START.getLabel())){
-//            //不计算最后一次,因为最后一次一定未审批
-//            totalAmount-= repaymentRecords.get(repaymentRecords.size()-1).getAmount();
-//            for (int i=0;i<repaymentRecords.size()-1;i++){
-//                RepaymentRecord repaymentRecord = repaymentRecords.get(i);
-//                if(repaymentRecord.getApprovalDecision().equals(DecisionEnum.PASS.getMsg())&&!repaymentRecord.getIsInterest())//审批、财务环节显示审批通过后的总额
-//                    currentAmount += repaymentRecord.getAmount();
-//            }
-//        }else{
-//            for (RepaymentRecord repaymentRecord : repaymentRecords)
-//                if(repaymentRecord.getApprovalDecision().equals(DecisionEnum.PASS.getMsg())&& !repaymentRecord.getIsInterest())//审批、财务环节显示审批通过后的总额
-//                    currentAmount += repaymentRecord.getAmount();
-//
-//        }
-//
-//        return Math.abs(totalAmount-currentAmount)< Double.MIN_VALUE;
-//    }
-
 }

+ 261 - 14
src/main/java/com/loan/system/controller/wechat/StatisticsController.java

@@ -2,6 +2,8 @@ package com.loan.system.controller.wechat;
 
 import com.loan.system.config.FileUploadConfig;
 import com.loan.system.domain.entity.*;
+import com.loan.system.domain.enums.DecisionEnum;
+import com.loan.system.domain.enums.StepEnum;
 import com.loan.system.domain.enums.StepPropertyEnum;
 import com.loan.system.domain.pojo.Result;
 import com.loan.system.domain.vo.*;
@@ -9,6 +11,8 @@ import com.loan.system.service.*;
 import com.loan.system.utils.ResultUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.xssf.usermodel.XSSFRow;
 import org.apache.poi.xssf.usermodel.XSSFSheet;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
@@ -26,6 +30,7 @@ import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @RestController
 @RequestMapping("/wechat/statistics")
@@ -46,17 +51,15 @@ public class StatisticsController {
     @Autowired
     private ContractService contractService;
     @Autowired
-    private ContractRepaymentService contractRepaymentService;
-    @Autowired
     private CustomerService customerService;
     @Autowired
-    private CustomerOtherService customerOtherService;
-    @Autowired
-    private RecommenderService recommenderService;
-    @Autowired
     private StepService stepService;
     @Autowired
     private UserService userService;
+    @Autowired
+    private CollateralPlanService collateralPlanService;
+    @Autowired
+    private LocationDatumService locationDatumService;
 
     @GetMapping("/dailyReport")
     @ApiOperation("获取日报表")
@@ -96,7 +99,7 @@ public class StatisticsController {
                 List<ContractVO> contracts = contractService.findContractByCaseId(caseId);
                 double totalInterest = 0.0;
                 for (ContractVO contract : contracts)
-                    if (contract.getIsCleared())
+                    if (contract.getClearedStatus().equals(DecisionEnum.CLEAR_AMOUNT_YES.getMsg()))
                         totalInterest += contract.getInterestAmount();
 
                 dailyReport.setLoanInterest(totalInterest);//8.
@@ -130,7 +133,7 @@ public class StatisticsController {
                 })
                 .collect(Collectors.toList());
         //查询概览运营数据,提供给Excel模板文件
-        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileUploadConfig.getUploadDir() + "出账明细.xlsx");
+        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileUploadConfig.getUploadDir() + "日报汇总.xlsx");
         try {
             //基于提供好的模板文件创建一个新的Excel表格对象
             XSSFWorkbook excel = new XSSFWorkbook(inputStream);
@@ -170,6 +173,79 @@ public class StatisticsController {
         }
     }
 
+    @GetMapping("/arrangeReport")
+    @ApiOperation("获取排期统计表")
+    public Result arrangeReport(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin) {
+
+        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        String endTime = begin.atTime(23, 59, 59).format(fmt);
+
+        List<LoanCaseStatistic> loanCaseStatistics = new ArrayList<>();
+
+        //指派人员只需要显示进行中的业务
+        List<LoanCaseSimpleVO> loanCases = loanService.findLoanCaseSimpleByIsComplete(DecisionEnum.PROCESS.getMsg());
+        for (LoanCaseSimpleVO loanCase : loanCases){
+            LoanCaseStatistic loanCaseStatistic = new LoanCaseStatistic();
+            List<StepVO> steps = stepService.getChildStepByCaseIdBeforeTime(loanCase.getId(), endTime);
+            //仅显示派单环节的信息
+            for (StepVO step : steps){
+                if (step.getStatus().equals(StepEnum.UNSTART.getMsg()) || step.getUserId1() == null)
+                    continue;
+
+                if (!step.getCode().equals(StepPropertyEnum.EVIDENCE_CONFIRMATION.getCode()) && !step.getCode().equals(StepPropertyEnum.DELIVERY_CONFIRMATION.getCode())
+                &&!step.getCode().equals(StepPropertyEnum.DISBURSE_START.getCode()) && !step.getCode().equals(StepPropertyEnum.BALANCE_REPAY.getCode()))
+                    continue;
+
+                loanCaseStatistic.setWeekSeq(calculateWeekSequence(LocalDate.parse(step.getBeginTime(), fmt)));
+                loanCaseStatistic.setDisburseDate(getDateAndPlace( step ,loanCase.getId()).get(0));//2.
+                loanCaseStatistic.setCustomerName(customerService.findByCustomerIdAndIsDelete(loanCase.getCustomerId(), false).getName());//3.
+                loanCaseStatistic.setBusinessType(loanCase.getBusinessType());//4.
+                loanCaseStatistic.setAmount(loanCase.getTotalLoanAmount());//5.
+
+                loanCaseStatistic.setStepName(step.getStepName());//6.
+
+                if (step.getCode().equals(StepPropertyEnum.BALANCE_REPAY.getCode())||step.getCode().equals(StepPropertyEnum.DISBURSE_START.getCode())){
+                    List<String> data = getDateAndPlace(step, loanCase.getId());
+                    if (data.size()>=3)
+                        loanCaseStatistic.setUserName1(data.get(2));
+                    if (data.size()==4)
+                        loanCaseStatistic.setUserName2(data.get(3));
+                }else {
+                    String[] ids = new String[]{};
+                    if (step.getUserIds() != null)
+                        ids = step.getUserIds().split(",");
+                    String userName1 = "";
+                    if (ids.length > 0)
+                        userName1 = userService.findByIdAndIsDelete(Long.parseLong(ids[0])).getRealName();
+                    loanCaseStatistic.setUserName1(userName1);//7.
+
+                    String userName2 = "";
+                    Stream<String> distinct = Arrays.stream(ids).distinct();
+                    List<Long> userIds = distinct.map(Long::parseLong).collect(Collectors.toList());
+                    if(userIds.size() > 1){
+                        List<User> users = userService.findByIdsAndIsDelete(userIds.subList(1, userIds.size()));//保证了第一个不会出现在后面
+                        userName2 = users.stream().map(User::getRealName).collect(Collectors.toList()).toString();
+                    }
+                    loanCaseStatistic.setUserName2(userName2);//8.
+                }
+
+                loanCaseStatistic.setStartDate(step.getBeginTime());//9.
+                loanCaseStatistic.setPlace(getDateAndPlace( step ,loanCase.getId()).get(1));//10.
+                loanCaseStatistic.setEndDate(step.getStatus().equals(StepEnum.COMPLETED.getMsg()) ? step.getUpdateTime() : step.getStatus());//11.
+
+                loanCaseStatistics.add(loanCaseStatistic);
+            }
+
+        }
+        // 对 loanCaseStatistics 进行排序
+        loanCaseStatistics.sort(Comparator
+                .comparing(LoanCaseStatistic::getStartDate)
+                .thenComparing(LoanCaseStatistic::getUserName1));
+
+
+        return ResultUtil.success("success",loanCaseStatistics);
+    }
+
     @GetMapping("/loanCaseReport")
     @ApiOperation("获取业务统计表")
     public Result loanCaseReport(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@@ -181,27 +257,171 @@ public class StatisticsController {
         List<LoanCaseStatistic> loanCaseStatistics = new ArrayList<>();
 
         List<LoanCaseSimpleVO> loanCases = loanService.findLoanCaseBetweenRange(beginTime, endTime);
+
         for (LoanCaseSimpleVO loanCase : loanCases){
             LoanCaseStatistic loanCaseStatistic = new LoanCaseStatistic();
-            List<StepVO> steps = stepService.getParentStepByCaseId(loanCase.getId());
+            List<StepVO> steps = stepService.getChildStepByCaseIdBetweenRange(loanCase.getId(), beginTime, endTime);
             for (StepVO step : steps){
-                loanCaseStatistic.setStartDate(loanCase.getCreateTime());//2.
+                if (step.getStatus().equals(StepEnum.UNSTART.getMsg()) || step.getUserId1() == null)
+                    continue;
+
+                loanCaseStatistic.setWeekSeq(calculateWeekSequence(LocalDate.parse(step.getBeginTime(), fmt)));
+                List<String> dateAndPlace = getDateAndPlace(step, loanCase.getId());
+                if(!dateAndPlace.isEmpty())
+                    loanCaseStatistic.setDisburseDate(dateAndPlace.get(0));//2.
                 loanCaseStatistic.setCustomerName(customerService.findByCustomerIdAndIsDelete(loanCase.getCustomerId(), false).getName());//3.
                 loanCaseStatistic.setBusinessType(loanCase.getBusinessType());//4.
                 loanCaseStatistic.setAmount(loanCase.getTotalLoanAmount());//5.
+
+//                String stepName = "";
+//                if(step.getCode()==StepPropertyEnum.CHANNEL_PUSH.getCode())
+//                    stepName += "(取证)";
+//                else if (step.getCode()==StepPropertyEnum.CHANNEL_PUSH_2.getCode())
+//                    stepName += "(送证)";
+//                else
+//                    stepName = step.getStepName();
                 loanCaseStatistic.setStepName(step.getStepName());//6.
 
-                loanCaseStatistic.setEndDate(loanCase.getUpdateTime());//9.
+                if (step.getCode().equals(StepPropertyEnum.BALANCE_REPAY.getCode())||step.getCode().equals(StepPropertyEnum.DISBURSE_START.getCode())){
+                    List<String> data = getDateAndPlace(step, loanCase.getId());
+                    if (data.size()>=3)
+                        loanCaseStatistic.setUserName1(data.get(2));
+                    if (data.size()==4)
+                        loanCaseStatistic.setUserName2(data.get(3));
+                }else {
+                    String[] ids = new String[]{};
+                    if (step.getUserIds() != null)
+                        ids = step.getUserIds().split(",");
+                    String userName1 = "";
+                    if (ids.length > 0)
+                        userName1 = userService.findByIdAndIsDelete(Long.parseLong(ids[0])).getRealName();
+                    loanCaseStatistic.setUserName1(userName1);//7.
+
+                    String userName2 = "";
+                    Stream<String> distinct = Arrays.stream(ids).distinct();
+                    List<Long> userIds = distinct.map(Long::parseLong).collect(Collectors.toList());
+                    if(userIds.size() > 1){
+                        List<User> users = userService.findByIdsAndIsDelete(userIds.subList(1, userIds.size()));//保证了第一个不会出现在后面
+                        userName2 = users.stream().map(User::getRealName).collect(Collectors.toList()).toString();
+                    }
+                    loanCaseStatistic.setUserName2(userName2);//8.
+                }
+
+                loanCaseStatistic.setStartDate(step.getBeginTime());//9.
+                if(dateAndPlace.size()>1)
+                    loanCaseStatistic.setPlace(dateAndPlace.get(1));//10.
+                loanCaseStatistic.setEndDate(step.getStatus().equals(StepEnum.COMPLETED.getMsg()) ? step.getUpdateTime() : step.getStatus());//11.
+
+                loanCaseStatistics.add(loanCaseStatistic);
             }
 
+            // 对 loanCaseStatistics 进行排序
+            loanCaseStatistics.sort(Comparator
+                    .comparing(LoanCaseStatistic::getStartDate)
+                    .thenComparing(LoanCaseStatistic::getUserName1));
 
-            loanCaseStatistics.add(loanCaseStatistic);
         }
 
-
         return ResultUtil.success("success",loanCaseStatistics);
     }
 
+    private String calculateWeekSequence(LocalDate date) {
+        // 获取月份的第一天
+        LocalDate firstDayOfMonth = date.withDayOfMonth(1);
+
+        // 获取第一天是星期几(1=Monday, 7=Sunday)
+        int firstDayOfWeek = firstDayOfMonth.getDayOfWeek().getValue();
+
+        // 计算指定日期是当月第几天
+        int dayOfMonth = date.getDayOfMonth();
+
+        // 计算是第几周 (W1, W2, W3...)TODO:取余计算需要从0开始
+        // 例如:如果1号是周三,那么1-3号属于第一周,4-10号属于第二周...
+        int weekSequence = ((dayOfMonth + firstDayOfWeek - 2) / 7) + 1;
+
+        return "W" + weekSequence;
+    }
+
+    private List<String > getDateAndPlace(StepVO  step , Long caseId){
+        List<String> list = new ArrayList<>();//有两个,第一个是date,第二个是place
+        String date = "";
+        String place = "";
+        String mainUSerName="";
+        String assistUserName="";
+        if (step.getCode().equals(StepPropertyEnum.EVIDENCE_CONFIRMATION.getCode())){
+            List<CollateralPlan> collateralPlans = collateralPlanService.findByCaseIdAndIsDelete(caseId, false);
+
+            for (CollateralPlan plan : collateralPlans){
+                if (plan.getTime()== null || plan.getPlace()== null)
+                    continue;
+                if (plan.getFlag().equals(DecisionEnum.ENTER_WAREHOUSE.getMsg())){
+                    date = date.isEmpty() ? plan.getTime() :date.concat("、").concat(plan.getTime());
+                    place = place.isEmpty() ? plan.getPlace() :place.concat("、").concat(plan.getPlace());
+                }
+            }
+            list.add(date);
+            list.add(place);
+        }else if (step.getCode().equals(StepPropertyEnum.DELIVERY_CONFIRMATION.getCode())){
+            List<CollateralPlan> collateralPlans = collateralPlanService.findByCaseIdAndIsDelete(caseId, false);
+
+            for (CollateralPlan plan : collateralPlans){
+                if (plan.getTime()== null || plan.getPlace()== null)
+                    continue;
+                if (plan.getFlag().equals(DecisionEnum.OUT_WAREHOUSE.getMsg())){
+                    date = date.isEmpty() ? plan.getTime() :date.concat("、").concat(plan.getTime());
+                    place = place.isEmpty() ? plan.getPlace() :place.concat("、").concat(plan.getPlace());
+                }
+            }
+            list.add(date);
+            list.add(place);
+        }else if (step.getCode().equals(StepPropertyEnum.DISBURSE_START.getCode())) {
+            List<Disbursement> disbursements = disbursementService.getDisbursementByCaseId(caseId);
+            for (Disbursement disbursement : disbursements){
+                if(disbursement.getPlannedTime()!=null)
+                    date = date.isEmpty() ? disbursement.getPlannedTime() :date.concat("、").concat(disbursement.getPlannedTime());
+                if(disbursement.getLocationId()!=null){
+                    LocationDatum locationDatum = locationDatumService.findById(disbursement.getLocationId());
+                    place = place.isEmpty() ? locationDatum.getSimpleAddress() :place.concat("、").concat(locationDatum.getSimpleAddress());
+                }
+                User user = userService.findByIdAndIsDelete(disbursement.getMainUserId());
+                if (user !=null)
+                    mainUSerName = mainUSerName.isEmpty() ? user.getRealName() :mainUSerName.concat("、").concat(user.getRealName());
+                User user1 = userService.findByIdAndIsDelete(disbursement.getAssistUserId());
+                if (user1 !=null)
+                    assistUserName = assistUserName.isEmpty() ? user1.getRealName() :assistUserName.concat( "、").concat(user1.getRealName());
+            }
+
+            list.add( date);
+            list.add(place);
+            list.add(mainUSerName);
+            list.add(assistUserName);
+        }else if(step.getCode().equals(StepPropertyEnum.BALANCE_REPAY.getCode())){
+            Repayment repayment = repaymentService.findByCaseIdAndIsDelete(caseId, false);
+            if (repayment!=null){
+                List<RepaymentRecord> records = repaymentRecordService.findByRepaymentIdAndIsInterestAndIsDelete(repayment.getId(), false);
+                for (RepaymentRecord record : records){
+                    if (record.getRepayTime()!= null || record.getRepayLocation()== null)
+                        date = date.isEmpty() ? record.getRepayTime() :date.concat("、").concat(record.getRepayTime());
+                    if(record.getRepayLocation()!=null)
+                        place = place.isEmpty() ? record.getRepayLocation() :place.concat("、").concat(record.getRepayLocation());
+
+                    User user = userService.findByIdAndIsDelete(record.getMainUser());
+                    if (user !=null)
+                        mainUSerName = mainUSerName.isEmpty() ? user.getRealName() :mainUSerName.concat("、").concat(user.getRealName());
+                    User user1 = userService.findByIdAndIsDelete(record.getAssistUser());
+                    if (user1 !=null)
+                        assistUserName = assistUserName.isEmpty() ? user1.getRealName() :assistUserName.concat( "、").concat(user1.getRealName());
+
+                }
+            }
+            list.add( date);
+            list.add(place);
+            list.add(mainUSerName);
+            list.add(assistUserName);
+        }
+        return list;
+    }
+
     @GetMapping("/loanCaseReport/export")
     @ApiOperation("导出业务统计表")
     public void exportLaonCaseReport(@RequestBody List<LoanCaseStatistic> loanCaseStatistics, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@@ -223,7 +443,7 @@ public class StatisticsController {
                 })
                 .collect(Collectors.toList());
         //查询概览运营数据,提供给Excel模板文件
-        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileUploadConfig.getUploadDir() + "出账明细.xlsx");
+        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileUploadConfig.getUploadDir() + "工作计划排期&业务统计表.xlsx");
         try {
             //基于提供好的模板文件创建一个新的Excel表格对象
             XSSFWorkbook excel = new XSSFWorkbook(inputStream);
@@ -339,4 +559,31 @@ public class StatisticsController {
 
     }
 
+    // 示例:合并连续相同userName1的情况
+    private void mergeSameCells(XSSFSheet sheet, List<LoanCaseStatistic> dataList, int columnIndex) {
+        int startRow = 3; // 数据起始行
+        String currentValue = null;
+        int mergeStart = startRow;
+
+        for (int i = 0; i < dataList.size(); i++) {
+//            String value = getCellValueByColumn(dataList.get(i), columnIndex);
+            String value = " ";
+            if (currentValue == null || !currentValue.equals(value)) {
+                // 发现不同的值,检查是否需要合并前面的单元格
+                if (i > mergeStart) {
+                    // 合并单元格
+                    sheet.addMergedRegion(new CellRangeAddress(mergeStart, i + startRow - 1, columnIndex, columnIndex));
+                }
+                currentValue = value;
+                mergeStart = i + startRow;
+            }
+        }
+
+        // 处理最后一组
+        if (mergeStart < dataList.size() + startRow - 1) {
+            sheet.addMergedRegion(new CellRangeAddress(mergeStart, dataList.size() + startRow - 1, columnIndex, columnIndex));
+        }
+    }
+
+
 }

+ 2 - 2
src/main/java/com/loan/system/controller/wechat/StepController.java

@@ -36,11 +36,11 @@ public class StepController {
     @PutMapping("/skipStep")
     @ApiOperation("跳过当前环节")
     @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','LEAD_SALES', 'ASSIST_SALES', 'FINANCE', 'BACK_OFFICE')")
-    public Result skipStep(@RequestParam("stepName")String stepName,@RequestParam("caseId")Long caseId){
+    public Result skipStep(@RequestParam("stepCode")Integer stepCode,@RequestParam("caseId")Long caseId){
         if(caseId==null||!loanService.existsByIdAndIsDelete(caseId))
             throw  new DescribeException(ExceptionEnum.PROJECT_NOT_EXIST);
 
-        stepService.skipStep(stepName,caseId);
+        stepService.skipStep(stepCode,caseId);
         return ResultUtil.success("success");
     }
 }

+ 2 - 5
src/main/java/com/loan/system/controller/wechat/UploadController.java

@@ -35,7 +35,7 @@ import java.util.*;
 import static com.loan.system.domain.enums.ExceptionEnum.STEP_HAS_NOT_PROCESS;
 
 @RestController
-@RequestMapping("/wechat/file")
+@RequestMapping("/wechat/ossfile")
 @Api(tags = "文件处理接口")
 public class UploadController {
     @Autowired
@@ -48,7 +48,7 @@ public class UploadController {
     /** 上传文件(支持任意类型,只要在白名单内) */
     @PostMapping("/upload/{caseId}/{type}")
     @ApiOperation("文件上传")//若合同不是统一提交,则合同1,合同2,合同3
-    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES','ASSIST_SALES')")
+//    @PreAuthorize("@pms.hasAnyRoles('LEAD_SALES','ASSIST_SALES','EXTERNAL','FINANCE',)")
     public Result uploadFile(@PathVariable("caseId") Long caseId,@PathVariable("type") String fileType,@RequestParam(required = false) MultipartFile file,@RequestParam Map<String , String>  isDelete) throws IOException{
         Iterator<Map.Entry<String, String>> iterator = isDelete.entrySet().iterator();
         while (iterator.hasNext()) {
@@ -84,8 +84,6 @@ public class UploadController {
 
         // 创建目录
         File uploadDir = new File(config.getUploadDir(), caseId + "/" + fileType);
-        System.out.println(uploadDir);
-        System.out.println(newFileName);
         if (!uploadDir.exists() && !uploadDir.mkdirs()) {
             return ResultUtil.error(ExceptionEnum.DIRECTORY_CREATE_ERROR);
         }
@@ -93,7 +91,6 @@ public class UploadController {
         // 使用NIO复制流,稳定性更好
         File destFile = new File(uploadDir, newFileName);
         try (InputStream in = file.getInputStream()) {
-            System.out.println(destFile.toPath());
             Files.copy(in, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
         }
 

+ 31 - 19
src/main/java/com/loan/system/controller/wechat/UserController.java

@@ -23,6 +23,7 @@ import com.loan.system.utils.JwtUtil;
 import com.loan.system.utils.ResultUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
 import org.json.JSONObject;
 import org.slf4j.Logger;
@@ -44,6 +45,7 @@ import java.util.stream.Collectors;
 @RestController
 @RequestMapping("/wechat")
 @Api(tags = "微信用户接口")
+@Slf4j
 public class UserController {//包含内部人员、外部人员
     @Autowired
     private UserService userService;
@@ -59,8 +61,6 @@ public class UserController {//包含内部人员、外部人员
     @Autowired
     private WxService wxService;
 
-    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
-
     /*
     1.先查询user查看是否为内部人员
     2.若不是内部人员,查询外部人员
@@ -70,13 +70,15 @@ public class UserController {//包含内部人员、外部人员
 //    @GetMapping("/get_sessionId")
 //    @ApiOperation("获取微信openid")
     public Map<String, String> get_sessionId(String code){
-        return userService.get_sessionId(code);
+        return wxService.getSessionId(code);
     }
 
     public Result customer_login(Customer customer){
         customer=customerService.findBymobileAndIsDelete(customer.getMobile());
         if (ObjectUtils.isEmpty(customer))
             throw new IllegalArgumentException("Customer object is null");
+        if (!customer.getIsRegister())//若未注册但是存在信息,则设为已注册
+            customerService.updateOpenIdAndIsRegisterById(customer.getId(),customer.getOpenid(),true);
         //为微信用户生成jwt令牌
         Map<String ,Object> claims=new HashMap<>();
         //用openid还是id?
@@ -112,6 +114,7 @@ public class UserController {//包含内部人员、外部人员
         claims.put(JwtClaimsConstant.isCustomer,0);
         String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);
 
+        //内部人员是pc端给定的账号,所以最开始是没有openid的
         if(user.getOpenid()== null){
             userService.setOpenidById(userLoginDTO.getOpenId(),user.getId());
         }
@@ -133,6 +136,7 @@ public class UserController {//包含内部人员、外部人员
      * @return
      */
     public Result register(Customer customer){
+        customer.setIsRegister(true);
         customerService.addCustomer(customer);
         return this.customer_login(customer);
     }
@@ -147,21 +151,29 @@ public class UserController {//包含内部人员、外部人员
         String openId = get_sessionId(weChatLoginDTO.getOpenCode()).get("openid");
         boolean user_is_exist = userService.existsByMobileAndIsDelete(phoneNumber);
         boolean customer_is_exist=customerService.existsBymobileAndIsDelete(phoneNumber);
-        log.info("code:{}",phoneNumber);
-        log.info("用户是否存在:{}",user_is_exist);
+
+//        if(user_is_exist){   //存在用户
+//            UserLoginDTO userLoginDTO = new UserLoginDTO();
+//            userLoginDTO.setTel(phoneNumber);
+//            userLoginDTO.setOpenId(openId);
+//            return this.user_login(userLoginDTO);
+//        }else { //存在客户
+//            return ResultUtil.error(ExceptionEnum.USER_NOT_IN_SYSTEM.getCode(),ExceptionEnum.USER_NOT_IN_SYSTEM.getMsg());
+//        }
+
         if(!customer_is_exist&&!user_is_exist){
             Customer customer = new Customer();
             customer.setMobile(phoneNumber);
             customer.setOpenid(openId);
             return this.register(customer);
-        }else if(customer_is_exist){   //存在客户
-            Customer customer=customerService.findBymobileAndIsDelete(phoneNumber);
-            return this.customer_login(customer);
-        }else { //存在用户
+        }else if(user_is_exist){   //存在用户
             UserLoginDTO userLoginDTO = new UserLoginDTO();
             userLoginDTO.setTel(phoneNumber);
-            userLoginDTO.setOpenId(openId);
             return this.user_login(userLoginDTO);
+        }else { //存在客户
+            Customer customer=customerService.findBymobileAndIsDelete(phoneNumber);
+            customer.setOpenid(openId);
+            return this.customer_login(customer);
         }
     }
     @PostMapping("/loginTest")
@@ -177,13 +189,13 @@ public class UserController {//包含内部人员、外部人员
             Customer customer = new Customer();
             customer.setMobile(phoneNumber);
             return this.register(customer);
-        }else if(customer_is_exist){   //存在客户
-            Customer customer=customerService.findBymobileAndIsDelete(phoneNumber);
-            return this.customer_login(customer);
-        }else { //存在用户
+        }else if(user_is_exist){   //存在用户
             UserLoginDTO userLoginDTO = new UserLoginDTO();
             userLoginDTO.setTel(phoneNumber);
             return this.user_login(userLoginDTO);
+        }else { //存在客户
+            Customer customer=customerService.findBymobileAndIsDelete(phoneNumber);
+            return this.customer_login(customer);
         }
     }
 //    @PostMapping("/login")
@@ -240,15 +252,15 @@ public class UserController {//包含内部人员、外部人员
 
     @GetMapping("/sales")
     @ApiOperation("查询所有业务员")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER')")
-    public Result findAllSales(@RequestParam String stepName){
-        if (stepName==null)
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','BACK_OFFICE')")
+    public Result findAllSales(@RequestParam Integer stepCode){
+        if (stepCode==null)
             throw new DescribeException(ExceptionEnum.INPUT_ERROR);
-        return ResultUtil.success("success", userService.listSales(stepName));
+        return ResultUtil.success("success", userService.listSales(stepCode));
     }
     @GetMapping("/finances")
     @ApiOperation("查询所有财务")
-    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER')")
+    @PreAuthorize("@pms.hasAnyRoles('SYSTEM_ADMIN','APPROVER','BACK_OFFICE')")
     public Result findAllFinances(){
 
         return ResultUtil.success("success", userService.findAllFinances());

+ 33 - 0
src/main/java/com/loan/system/controller/wechat/test.java

@@ -0,0 +1,33 @@
+package com.loan.system.controller.wechat;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class test {
+    @Data
+    private class Record{
+        double total;//出款
+        double amount;//本次还款
+        double currentAmount;//还款
+    }
+    public static void main(String[] args) {
+        double totalAmount = 900;
+
+        List<Record> records = new ArrayList<>();
+        for (Record record : records){
+            double gap = record.total-record.currentAmount;
+
+            if(gap <= totalAmount){
+                record.amount = gap;
+                totalAmount -= gap;
+            }else{
+                record.amount = totalAmount;
+                totalAmount = 0.0;
+            }
+
+            //再小
+        }
+    }
+}

+ 2 - 4
src/main/java/com/loan/system/domain/dto/ApprovalRecordDTO.java

@@ -12,13 +12,11 @@ import java.io.Serializable;
 @AllArgsConstructor
 @NoArgsConstructor
 public class ApprovalRecordDTO implements Serializable {
-
     private Long caseId;//业务ID
-    private String stepName;//环节名称
+    private Long recordId;
+    private Integer stepCode;//环节名称
     private Long approverId;//审批人ID
     private String decision;//审批结果
     private String comments;//审批意见
 
-
-
 }

+ 21 - 0
src/main/java/com/loan/system/domain/dto/ChannelPushDTO.java

@@ -0,0 +1,21 @@
+package com.loan.system.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ChannelPushDTO implements Serializable {
+    //private Long channelId;
+    //private  String customerName;//客户名称
+    //private  Double loanAmount;// 借款金额
+    private Long caseId;
+    private String collateralIds;// 产证权利人姓名(多个押品)
+    private Long recommenderId;
+    private String flag;
+}

+ 28 - 0
src/main/java/com/loan/system/domain/dto/ClearDetailDTO.java

@@ -0,0 +1,28 @@
+package com.loan.system.domain.dto;
+
+import com.loan.system.domain.entity.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ClearDetailDTO implements Serializable {
+    private Long id;
+    private Long contractId;
+    private Long caseId;
+    private String disbursementTime;//出款时间
+    private Double disbursementAmount;//出款金额
+    private String repayTime;//回款时间(手动)
+    private Double currentAmount;//当前出款时间的累计回款金额
+    private Double repayAmount;//当前出款时间的回款金额
+    private Double totalInterest;
+
+}

+ 8 - 1
src/main/java/com/loan/system/domain/dto/CollateralDTO.java

@@ -16,11 +16,18 @@ public class CollateralDTO implements Serializable {
     private Long caseId;//业务id
     //private Long contractId;//合同id
     private String collateralName;//押品名称
+    private String idNumber;
+    private String mobile;
     private String collateralType;//押品类型/业务属性
     private Long ownerCustomerId;//押品所属客户id
-    private double allocatedAmount;//押品分配金额
+    private Double allocatedAmount;//押品分配金额
     private String address;//押品信息地址
     private String currentAddress;//押品实际地址
+    private String collateralNo;
+    private String collateralArea;
     private Boolean isInvolvedInLitigation;//是否涉及诉讼
+    private String remark;
+    private Boolean isMortgaged;
+    private String mortgagedUser;
     private String staus;//押品状态
 }

+ 1 - 0
src/main/java/com/loan/system/domain/dto/CollateralPlanApprovalDTO.java

@@ -21,5 +21,6 @@ public class CollateralPlanApprovalDTO implements Serializable {
     private String comments;
     private String decision;
     private Long userId;//取证/出证确认人
+    private String location;
 
 }

+ 3 - 1
src/main/java/com/loan/system/domain/dto/CollateralPlanDTO.java

@@ -15,7 +15,9 @@ public class CollateralPlanDTO extends CollateralDTO{
     private Long caseId;
     private String collateralName;
     private String collateralTime;
-    private String collateralPlace;
+    private LocationDatumDTO locationDatum;
     private String comments;
     private String flag;
+    private String contactName;
+    private String mobile;
 }

+ 39 - 0
src/main/java/com/loan/system/domain/dto/CompleteMultipartUploadDTO.java

@@ -0,0 +1,39 @@
+package com.loan.system.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 完成分块上传请求DTO
+ * @author system
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class CompleteMultipartUploadDTO implements Serializable {
+    private String fileName;
+    private String uploadId;
+    private String randomUUID;
+    private String ext;
+    private String originalName;
+    private Long fileSize;
+    private Map<String, String> isDelete;
+    private List<PartETagDTO> partETagsList;
+    
+    /**
+     * 分块ETag DTO
+     */
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class PartETagDTO implements Serializable {
+        private Integer partNumber;
+        private String eTag;
+    }
+}
+

+ 5 - 0
src/main/java/com/loan/system/domain/dto/ContractDTO.java

@@ -23,4 +23,9 @@ public class ContractDTO implements Serializable {
     private Double interestRate;//年利率
     private Integer loanPeriod;//借款期限
     private String content;//合同内容
+    private String bankName;
+    private String bankAccount;
+    private Double amountRate;
+    private Double serviceCost;
+    private Double loanRate;
 }

+ 16 - 0
src/main/java/com/loan/system/domain/dto/ContractWrapperDTO.java

@@ -0,0 +1,16 @@
+package com.loan.system.domain.dto;
+
+import com.loan.system.domain.entity.Contract;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.catalina.LifecycleState;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ContractWrapperDTO{
+    List<ContractDTO> contractDTOS;
+}

+ 4 - 0
src/main/java/com/loan/system/domain/dto/CustomerDTO.java

@@ -15,6 +15,7 @@ import java.io.Serializable;
 @AllArgsConstructor
 @NoArgsConstructor
 public class CustomerDTO implements Serializable {
+    private Long id;
     private String openid;
     private String name;
     private String sex;
@@ -22,4 +23,7 @@ public class CustomerDTO implements Serializable {
     private String mobile;
     private String bankAccount;
     private String bankName;
+    private String marriedStatus;
+    private String contactAddress;
+    private String businessLicense;
 }

+ 19 - 0
src/main/java/com/loan/system/domain/dto/DisburseBankInfoDTO.java

@@ -0,0 +1,19 @@
+package com.loan.system.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class DisburseBankInfoDTO implements Serializable {
+    private Long id;
+    private Double amount;
+    private String payee;
+    private String mobile;
+    private String disburseAccount;
+    private String disburseBank;
+}

+ 4 - 7
src/main/java/com/loan/system/domain/dto/DisbursementDTO.java

@@ -10,21 +10,18 @@ import javax.persistence.Table;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.time.Instant;
+import java.util.Map;
 
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 public class DisbursementDTO implements Serializable {
+    private Long id;
     private Long caseId;//业务id
-//    private Long payoutApproveUserId;//出款审批人 id
-//    private Long payoutOperatorUserId;//财务出款人
-//    private Long applyBy;//出款上报人id
     private Double plannedAmount;//计划金额
     private String plannedTime;//计划时间
-    private String plannedLocation;//计划地点
+    private LocationDatumDTO locationDatumDTO;
     private String plannedComment;//计划说明
-    private String disbursementStatus;//出款状态
-    //private Long contractId;//合同id
-
+    private Map<Long,Double> contractAndAmount;
 
 }

+ 11 - 4
src/main/java/com/loan/system/domain/dto/DisbursementRecordDTO.java

@@ -1,6 +1,7 @@
 package com.loan.system.domain.dto;
 
 import com.loan.system.domain.entity.BaseEntity;
+import com.loan.system.domain.entity.DisburseBankInfo;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -10,16 +11,22 @@ import javax.persistence.Entity;
 import javax.persistence.Table;
 import java.io.Serializable;
 import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
 
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 public class DisbursementRecordDTO implements Serializable {
+    private Long id;
     private Long disbursementId;
+    private Long caseId;
     private Double amount;
-    private String receiptName;//收款人
-    private String disbursementLocation;
-    private String disbursementAccount;
-    private String disbursementBank;
+    private List<DisburseBankInfoDTO> disburseBankInfos;
+    private String comment;
+    private Map<Long,Double> contractIdAndAmount;//每个合同对应的出款金额
+    private String createTime;
+    private String approvalIds;
+    private String financeApprovalIds;
 
 }

+ 1 - 2
src/main/java/com/loan/system/domain/dto/DisbursementStartDTO.java

@@ -11,7 +11,6 @@ import java.util.Map;
 @AllArgsConstructor
 @NoArgsConstructor
 public class DisbursementStartDTO implements Serializable {
-    private Long caseId;
+    private DisbursementRecordDTO disbursementRecordDTO;
     private Map<Long,String> contractAndPawn;
-    private String comment;
 }

+ 76 - 0
src/main/java/com/loan/system/domain/dto/EsignCreateFlowDTO.java

@@ -0,0 +1,76 @@
+package com.loan.system.domain.dto;
+
+import lombok.Data;
+
+/**
+ * 创建e签宝签署流程DTO
+ */
+@Data
+public class EsignCreateFlowDTO {
+    
+    /**
+     * 合同ID
+     */
+    private Long contractId;
+    
+    /**
+     * 案件ID
+     */
+    private Long caseId;
+    
+    /**
+     * 合同文档URL(需要签署的合同文件地址)或文件ID(fileId)
+     */
+    private String documentUrl;
+    
+    /**
+     * e签宝文件ID(上传后获得)
+     */
+    private String fileId;
+    
+    /**
+     * 合同文档名称
+     */
+    private String documentName;
+    
+    /**
+     * 客户姓名
+     */
+    private String customerName;
+    
+    /**
+     * 客户手机号
+     */
+    private String customerMobile;
+    
+    /**
+     * 客户身份证号
+     */
+    private String customerIdNumber;
+    
+    /**
+     * 业务方姓名
+     */
+    private String businessName;
+    
+    /**
+     * 业务方手机号
+     */
+    private String businessMobile;
+    
+    /**
+     * 业务方身份证号(可选)
+     */
+    private String businessIdNumber;
+    
+    /**
+     * 签署截止时间(时间戳,毫秒)
+     */
+    private Long signDeadline;
+    
+    /**
+     * 备注
+     */
+    private String remark;
+}
+

+ 43 - 0
src/main/java/com/loan/system/domain/dto/EsignFillTemplateDTO.java

@@ -0,0 +1,43 @@
+package com.loan.system.domain.dto;
+
+import com.loan.system.domain.pojo.ContractInformation;
+import lombok.Data;
+
+/**
+ * e签宝合同模板填充DTO
+ */
+@Data
+public class EsignFillTemplateDTO {
+    private Long contractId;//合同id
+    private Long caseId;
+    private String contractName;
+    private String customerName;
+    private String customerMobile;
+    private String customerIdNumber;
+    
+    /**
+     * 业务方姓名
+     */
+    private String businessName;
+    
+    /**
+     * 业务方手机号
+     */
+    private String businessMobile;
+    
+    /**
+     * 业务方身份证号(可选)
+     */
+    private String businessIdNumber;
+    
+    /**
+     * 签署截止时间(时间戳,毫秒)
+     */
+    private Long signDeadline;
+    
+    /**
+     * 备注
+     */
+    private String remark;
+}
+

+ 68 - 0
src/main/java/com/loan/system/domain/dto/EsignTemplateFillDTO.java

@@ -0,0 +1,68 @@
+package com.loan.system.domain.dto;
+
+import com.loan.system.domain.pojo.ContractInformation;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+/**
+ * e签宝模板填充请求DTO
+ */
+@Data
+@ApiModel("e签宝模板填充请求")
+public class EsignTemplateFillDTO {
+    
+    @ApiModelProperty(value = "合同ID", required = true)
+    @NotNull(message = "合同ID不能为空")
+    private Long contractId;
+    
+    @ApiModelProperty(value = "案件ID", required = true)
+    @NotNull(message = "案件ID不能为空")
+    private Long caseId;
+    
+    @ApiModelProperty(value = "e签宝模板ID", required = true)
+    @NotBlank(message = "模板ID不能为空")
+    private String templateId;
+
+    @NotBlank(message = "合同文件名不能为空")
+    private String contractName;
+    
+//    @ApiModelProperty(value = "模板字段填充映射(key为模板控件ID,value为填充值)", notes = "如果为空,将使用contract信息自动映射")
+//    private Map<String, String> formFields;
+//
+//    @ApiModelProperty(value = "合同信息", notes = "用于自动映射字段,如果formFields为空则使用此对象")
+//    private ContractInformation contract;
+    
+    @ApiModelProperty(value = "客户姓名", required = true)
+    @NotBlank(message = "客户姓名不能为空")
+    private String customerName;
+    
+    @ApiModelProperty(value = "客户手机号", required = true)
+    @NotBlank(message = "客户手机号不能为空")
+    private String customerMobile;
+    
+    @ApiModelProperty(value = "客户身份证号")
+    private String customerIdNumber;
+    
+    @ApiModelProperty(value = "业务方名称", required = true)
+    @NotBlank(message = "业务方名称不能为空")
+    private String businessName;
+    
+    @ApiModelProperty(value = "业务方手机号", required = true)
+    @NotBlank(message = "业务方手机号不能为空")
+    private String businessMobile;
+    
+    @ApiModelProperty(value = "业务方身份证号")
+    private String businessIdNumber;
+    
+    @ApiModelProperty(value = "签署截止时间(时间戳,毫秒)")
+    private Long signDeadline;
+    
+    @ApiModelProperty(value = "备注")
+    private String remark;
+}
+

+ 5 - 5
src/main/java/com/loan/system/domain/dto/LoanCaseDTO.java

@@ -18,22 +18,22 @@ import java.util.Map;
 @NoArgsConstructor
 @AllArgsConstructor
 public class LoanCaseDTO implements Serializable {
-    private Long customerId;//客户id
-    //private Long caseId;//业务id
     private String businessAttrs;//业务属性
     private List<ContractDTO> contracts;//合同
     //private String address;//房产实际地点
+    private CustomerDTO customer;
     private List<Map<String,List<Integer>> > collateralAndContract;
+    private Map<Integer,List<Integer>> contractSeqAndCollateralSeq;
     private List<CollateralDTO> collateral;//押品
     private String channelName;//渠道名称
     private String group;//组别
     private Long recommenderId;//推荐人id
     private String businessType;//业务类型
-    private CustomersOtherDTO customers1;//其它客户1
-    private CustomersOtherDTO customers2;//其它客户2
+    private CustomerDTO customers1;//其它客户1
+    private CustomerDTO customers2;//其它客户2
     private Double totalLoanAmount;//总借款金额
 //    private List<DocumentDTO> documents;// 文件
-    private String marriedStatus;//婚姻状况
+    private String riskComment;
     private String currentAddress;// 房产产证地址
     private Boolean isIllegal;//是否涉及诉讼
 }

+ 23 - 0
src/main/java/com/loan/system/domain/dto/LocationDatumDTO.java

@@ -0,0 +1,23 @@
+package com.loan.system.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class LocationDatumDTO implements Serializable {
+    private Long id;
+
+    private String simpleAddress;
+    
+    private BigDecimal longitude;
+    
+    private BigDecimal latitude;
+    
+    private String detailAddress;
+}

+ 25 - 0
src/main/java/com/loan/system/domain/dto/RepayBankInfoDTO.java

@@ -0,0 +1,25 @@
+package com.loan.system.domain.dto;
+
+import com.loan.system.domain.entity.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class RepayBankInfoDTO implements Serializable {
+    private Double amount;
+    private String remitter;
+    private String mobile;
+    private String repayAccount;
+    private String repayBank;
+
+}

+ 21 - 6
src/main/java/com/loan/system/domain/dto/RepaymentRecordDTO.java

@@ -1,9 +1,11 @@
 package com.loan.system.domain.dto;
 
+import com.loan.system.domain.entity.RepayBankInfo;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import javax.persistence.Column;
 import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
@@ -13,14 +15,27 @@ import java.util.Map;
 @AllArgsConstructor
 @NoArgsConstructor
 public class RepaymentRecordDTO implements Serializable {
-
+    private Long recordId;
+    //计划
+    private String remitter;
+    private String mobile;
     private Double amount;
-    private String repayTime;
-    private String repayBank;
-    private String repayLocation;
-    private String repayAccount;
+    private LocationDatumDTO locationDatum;
+    private String planTime;
+    private Long planUserId;
+
+    //回款操作
+    private Map<Long,Double> clearInterest;
+    private Long mainUserId;
+    private Double interestAmount;//单次利息
+    private String repayComment;
     private String repayWay;
-    private Boolean isInterest;//是否为费息
+    private String repayTime;
+    private Boolean isInterest;
+    private List<ClearDetailDTO> clearDetailList;
     private Map<Long,Double> contractIdAndAmount;
+    private Map<Long,Double> contractIdAndInterestAmount;
+    private List<RepayBankInfoDTO> repayBankInfoList;
+
 
 }

+ 64 - 0
src/main/java/com/loan/system/domain/dto/TemplateMessageSendDTO.java

@@ -0,0 +1,64 @@
+package com.loan.system.domain.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 微信模板消息发送请求DTO
+ * @author system
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TemplateMessageSendDTO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 接收者 openid(必填)
+     */
+    @NotBlank(message = "openid 不能为空")
+    private String openid;
+
+    /**
+     * 模板 ID(必填)
+     */
+    @NotBlank(message = "template_id 不能为空")
+    private String templateId;
+
+    /**
+     * 小程序内部页面路径(可选)
+     * 例如: pages/review/detail?id=1
+     */
+    private String page;
+
+    /**
+     * H5 外部链接(可选)
+     */
+    private String url;
+
+    /**
+     * 跳转其他小程序(可选)
+     */
+    private Map<String, Object> miniprogram;
+
+    /**
+     * 模板数据(必填)
+     * 格式: {"thing1": {"value": "内容1"}, "thing2": {"value": "内容2"}}
+     */
+    private Map<String, Map<String, String>> data;
+
+    /**
+     * 防重入id(可选)
+     * 同一个用户,同一个 client_msg_id 只能发送一次
+     */
+    private String clientMsgId;
+}
+

+ 3 - 3
src/main/java/com/loan/system/domain/entity/ApprovalRecord.java

@@ -5,7 +5,7 @@ import lombok.*;
 import javax.persistence.*;
 import java.time.Instant;
 
-@Builder
+
 @Entity
 @Table(name = "approval_record", indexes = {
         @Index(name = "idx_case_id", columnList = "case_id")
@@ -20,8 +20,8 @@ public class ApprovalRecord extends BaseEntity{
     @Column(name = "case_id")
     private Long caseId;
 
-    @Column(name = "step_name", length = 100)
-    private String stepName;
+    @Column(name = "step_code", length = 20)
+    private Integer stepCode;
 
     @Column(name = "approver_id")
     private Long approverId;

+ 53 - 0
src/main/java/com/loan/system/domain/entity/ChannelPush.java

@@ -0,0 +1,53 @@
+package com.loan.system.domain.entity;
+
+import lombok.*;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.validation.constraints.Size;
+import java.time.Instant;
+
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "channel_push")
+public class ChannelPush extends BaseEntity {
+    private static final long serialVersionUID = 36L;
+
+    @Column(name = "case_id")
+    private Long caseId;
+
+    @Size(max = 10)
+    @Column(name = "flag", length = 10)
+    private String flag;
+
+    @Size(max = 100)
+    @Column(name = "collateral_ids", length = 100)
+    private String collateralIds;
+
+    @Column(name = "user_id")
+    private Long userId;
+
+    @Column(name = "assign_time")
+    private String assignTime;
+
+    @Column(name = "recommender_id")
+    private Long recommenderId;
+
+    @Size(max = 255)
+    @Column(name = "comment")
+    private String comment;
+
+    @Column(name = "confirm_time")
+    private String confirmTime;
+
+    @Column(name = "status")
+    private String status;
+
+    @Column(name = "is_delete")
+    private Boolean isDelete;
+
+}

+ 57 - 0
src/main/java/com/loan/system/domain/entity/ClearDetail.java

@@ -0,0 +1,57 @@
+package com.loan.system.domain.entity;
+
+import lombok.*;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import java.time.Instant;
+
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "clear_detail")
+public class ClearDetail extends BaseEntity {
+    private static final long serialVersionUID = 41L;
+    @Column(name = "contract_id")
+    private Long contractId;
+
+    @Column(name = "case_id")
+    private Long caseId;
+
+    @Column(name = "disbursement_time")
+    private String disbursementTime;
+
+    @Column(name = "disbursement_amount")
+    private Double disbursementAmount;
+
+    @Column(name = "repay_time")
+    private String repayTime;
+
+    @Column(name = "current_amount")
+    private Double currentAmount;
+
+    @Column(name = "repay_amount")
+    private Double repayAmount;
+
+    @Column(name = "current_interest")
+    private Double currentInterest;
+
+    @Column(name = "total_interest")
+    private Double totalInterest;
+
+    @Column(name = "the_order")
+    private Integer theOrder;
+
+    @Column(name = "create_time")
+    private String createTime;
+
+    @Column(name = "update_time")
+    private String updateTime;
+
+    @Column(name = "is_delete")
+    private Boolean isDelete;
+
+}

+ 26 - 4
src/main/java/com/loan/system/domain/entity/Collateral.java

@@ -2,10 +2,7 @@ package com.loan.system.domain.entity;
 
 import lombok.*;
 
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.Table;
+import javax.persistence.*;
 import java.math.BigDecimal;
 import java.time.Instant;
 
@@ -25,6 +22,12 @@ public class Collateral extends BaseEntity{
     @Column(name = "collateral_name", length = 50)
     private String collateralName;
 
+    @Column(name = "id_number",length = 20)
+    private String idNumber;
+
+    @Column(name = "mobile",length = 11)
+    private String mobile;
+
     @Column(name = "collateral_type", length = 30)
     private String collateralType;
 
@@ -43,12 +46,31 @@ public class Collateral extends BaseEntity{
     @Column(name = "eval_price", precision = 18, scale = 2)
     private Double evalPrice;
 
+    @Column(name = "collateral_no",length = 50)
+    private String collateralNo;
+
+    @Column(name = "collateral_area")
+    private String collateralArea;
+
     @Column(name = "is_involved_in_litigation")
     private Boolean isInvolvedInLitigation;
 
+    @Column(name = "operate_status")
+    private String operateStatus;
+
     @Column(name = "staus", length = 20)
     private String staus;
 
+    @Lob
+    @Column(name = "remark")
+    private String remark;
+
+    @Column(name = "is_mortgaged")
+    private Boolean isMortgaged;
+
+    @Column(name = "mortgaged_user",length = 50)
+    private String mortgagedUser;
+
     @Column(name = "create_time")
     private String createTime;
 

+ 21 - 3
src/main/java/com/loan/system/domain/entity/CollateralPlan.java

@@ -26,14 +26,26 @@ public class CollateralPlan extends BaseEntity {
     @Column(name = "time")
     private String time;
 
+    @Column(name = "location_id")
+    private Long locationId;
+
+    @Column(name = "contact_name",length = 20)
+    private String contactName;
+
+    @Column(name = "mobile",length = 11)
+    private String mobile;
+
     @Column(name = "place")
     private String place;
 
+    @Column(name = "address")
+    private String address;
+
     @Column(name = "flag", length = 10)
     private String flag;
 
     @Column(name = "comments")
-    private String comments;
+    private String comments;//计划说明
 
     @Column(name = "create_time")
     private String createTime;
@@ -45,14 +57,20 @@ public class CollateralPlan extends BaseEntity {
     private Boolean isDelete;
 
     @Column(name = "status", length = 10)
-    private String status;
+    private String status;//执行状态
 
     @Column(name = "approval_record_id")
     private Long approvalRecordId;
 
     @Column(name = "operator_comments")
-    private String operatorComments;
+    private String operatorComments;//送证人意见
 
     @Column(name = "operator_id", length = 20)
     private Long operatorId;//送证/取证员
+
+    @Column(name = "recommender_id")
+    private Long recommenderId;
+
+    @Column(name = "recommender_comments")
+    private String recommenderComments;
 }

+ 22 - 4
src/main/java/com/loan/system/domain/entity/Contract.java

@@ -32,17 +32,20 @@ public class Contract extends BaseEntity{
     @Column(name = "contract_version")
     private Integer contractVersion;
 
-    @Column(name = "contract_amount", precision = 15, scale = 2)
+    @Column(name = "contract_amount", precision = 18, scale = 2)
     private Double contractAmount;
 
+    @Column(name = "actual_amount", precision = 18, scale = 2)
+    private Double actualAmount;
+
     @Column(name = "interest_rate", precision = 5, scale = 4)
-    private Double interestRate;
+    private Double interestRate;//暂时没用
 
     @Column(name = "interest_amount", precision = 18, scale = 2)
     private Double interestAmount;
 
-    @Column(name = "is_cleared")
-    private Boolean isCleared;
+    @Column(name = "cleared_status",length = 10)
+    private String clearedStatus;
 
     @Column(name = "loan_period")
     private Integer loanPeriod;
@@ -69,6 +72,21 @@ public class Contract extends BaseEntity{
     @Column(name = "is_push")
     private Boolean isPush;
 
+    @Column(name = "bank_name",length = 200)
+    private String bankName;
+
+    @Column(name = "bank_account" , length = 200)
+    private String bankAccount;
+
+    @Column(name = "amount_rate")
+    private Double amountRate;
+
+    @Column(name = "service_cost")
+    private Double serviceCost;
+
+    @Column(name = "loan_rate")
+    private Double loanRate;
+
     @Column(name = "create_time")
     private String createTime;
 

+ 50 - 0
src/main/java/com/loan/system/domain/entity/ContractDisbursement.java

@@ -0,0 +1,50 @@
+package com.loan.system.domain.entity;
+
+import lombok.*;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.math.BigDecimal;
+import java.time.Instant;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "contract_disbursement")
+public class ContractDisbursement extends BaseEntity {
+    private static final long serialVersionUID = 38L;
+    @NotNull
+    @Column(name = "contract_id", nullable = false)
+    private Long contractId;
+
+    @NotNull
+    @Column(name = "disbursement_record_id", nullable = false)
+    private Long disbursementRecordId;
+
+    @Column(name = "case_id")
+    private Long caseId;
+
+    @Column(name = "amount", precision = 18, scale = 2)
+    private Double amount;
+
+    @Size(max = 255)
+    @Column(name = "comment")
+    private String comment;
+
+    @Column(name = "is_plan")
+    private Boolean isPlan;
+
+    @Column(name = "create_time")
+    private String createTime;
+
+    @Column(name = "update_time")
+    private String updateTime;
+
+    @Column(name = "is_delete")
+    private Boolean isDelete;
+
+}

+ 3 - 0
src/main/java/com/loan/system/domain/entity/ContractRepayment.java

@@ -28,6 +28,9 @@ public class ContractRepayment extends BaseEntity{
     @Column(name = "amount" , precision = 18, scale = 2)
     private Double amount;
 
+    @Column(name = "interest_amount" , precision = 18, scale = 2)
+    private Double interestAmount;//因为利息与本金单位是不一样的
+
     @Column(name = "comment" )
     private String comment;
 

+ 11 - 2
src/main/java/com/loan/system/domain/entity/Customer.java

@@ -47,10 +47,19 @@ public class Customer extends BaseEntity{
     private String bankName;
 
     @Column(name = "is_illegal", length = 1)
-    private Boolean isIllegal;
+    private Boolean isIllegal;//实名认证
 
     @Column(name = "face_auth")
-    private Boolean faceAuth;
+    private Boolean faceAuth;//是否人脸识别认证
+
+    @Column(name = "contact_address")
+    private String contactAddress;
+
+    @Column(name = "business_license")
+    private String businessLicense;
+
+    @Column(name = "is_register")
+    private Boolean isRegister;
 
     @Column(name = "create_time")
     private String createTime;

+ 50 - 0
src/main/java/com/loan/system/domain/entity/DisburseBankInfo.java

@@ -0,0 +1,50 @@
+package com.loan.system.domain.entity;
+
+import lombok.*;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "disburse_bank_info")
+public class DisburseBankInfo extends BaseEntity {
+    private static final long serialVersionUID = 42L;
+    
+    @NotNull
+    @Column(name = "record_id", nullable = false)
+    private Long recordId;
+
+    @Column(name = "amount")
+    private Double amount;
+
+    @Size(max = 50)
+    @Column(name = "payee", length = 50)
+    private String payee;
+
+    @Column(name = "mobile", length = 11)
+    private String mobile;
+
+    @Size(max = 30)
+    @Column(name = "disburse_account", length = 30)
+    private String disburseAccount;
+
+    @Size(max = 100)
+    @Column(name = "disburse_bank", length = 100)
+    private String disburseBank;
+
+    @Column(name = "create_time")
+    private String createTime;
+
+    @Column(name = "update_time")
+    private String updateTime;
+
+    @Column(name = "is_delete")
+    private Boolean isDelete;
+
+}

+ 12 - 27
src/main/java/com/loan/system/domain/entity/Disbursement.java

@@ -6,12 +6,9 @@ import javax.persistence.*;
 import java.math.BigDecimal;
 import java.time.Instant;
 
-@Getter
-@Setter
 @Entity
 @Table(name = "disbursement", indexes = {
         @Index(name = "idx_case_id", columnList = "case_id"),
-        @Index(name = "idx_disbursement_status", columnList = "disbursement_status")
 })
 @Data
 @AllArgsConstructor
@@ -22,41 +19,29 @@ public class Disbursement extends BaseEntity{
     @Column(name = "case_id")
     private Long caseId;
 
-    @Column(name = "payout_approve_user_id")
-    private Long payoutApproveUserId;
+    @Column(name = "operator_user_id")
+    private Long operatorUserId;
 
-    @Column(name = "payout_operator_user_id")
-    private Long payoutOperatorUserId;
-
-    @Column(name = "apply_by")
-    private Long applyBy;
+    @Column(name = "planned_amount", precision = 18, scale = 2)
+    private Double plannedAmount;
 
-    @Column(name = "apply_at")
-    private String applyAt;
+    @Column(name = "location_id")
+    private Long locationId;
 
     @Column(name = "planned_time", length = 200)
     private String plannedTime;
 
-    @Column(name = "planned_amount", precision = 18, scale = 2)
-    private Double plannedAmount;
-
-    @Column(name = "planned_location")
-    private String plannedLocation;
+    @Column(name = "approval_id")
+    private Long approvalId;
 
     @Column(name = "planned_comment")
     private String plannedComment;
 
-    @Column(name = "disbursement_status", length = 30)
-    private String disbursementStatus;
-
-    @Column(name = "contract_id")
-    private Long contractId;
-
-    @Column(name="disbursement_comment", length = 200)
-    private String disbursementComment;
+    @Column(name = "main_user_id")
+    private Long mainUserId;
 
-    @Column(name="confirm_comment", length = 200)
-    private String confirmComment;
+    @Column(name = "assist_user_id")
+    private Long assistUserId;
 
     @Column(name = "create_time")
     private String createTime;

+ 22 - 13
src/main/java/com/loan/system/domain/entity/DisbursementRecord.java

@@ -18,28 +18,37 @@ public class DisbursementRecord extends BaseEntity{
     private static final long serialVersionUID = 15L;
 
     @Column(name = "disbursement_id")
-    private Long disbursementId;
+    private Long disbursementId;//计划id
 
-    @Column(name = "finance_id",length = 20)
-    private Long financeId;
+    @Column(name = "case_id")
+    private Long caseId;
 
     @Column(name = "amount", precision = 18, scale = 2)
-    private Double amount;
+    private Double amount;//实际出款金额
+
+    @Column(name = "approval_ids")
+    private String approvalIds; //审批id
+
+    @Column(name = "finance_approval_ids")
+    private String financeApprovalIds;//财务审批id
 
     @Column(name = "create_time")
-    private String createTime;
+    private String createTime;//出款时间
+
+    @Column(name = "comment")
+    private String comment;
 
-    @Column(name = "receipt_name",length = 20)
-    private String receiptName;
+    @Column(name = "confirm_user_id")
+    private Long confirmUserId;
 
-    @Column(name ="disbursement_location" ,length = 255)
-    private String disbursementLocation;
+    @Column(name = "confirm_comment")
+    private String confirmComment;
 
-    @Column(name ="disbursement_account" ,length = 20)
-    private String disbursementAccount;
+    @Column(name = "confirm_version")
+    private Long confirmVersion;
 
-    @Column(name ="disbursement_bank" ,length = 255)
-    private String disbursementBank;
+    @Column(name = "update_time")
+    private String updateTime;
 
     @Column(name = "is_delete")
     private Boolean isDelete;

+ 153 - 0
src/main/java/com/loan/system/domain/entity/EsignFlow.java

@@ -0,0 +1,153 @@
+package com.loan.system.domain.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+
+/**
+ * e签宝签署流程实体类
+ */
+@Entity
+@Table(name = "esign_flow", indexes = {
+        @Index(name = "idx_flow_id", columnList = "flow_id"),
+        @Index(name = "idx_contract_id", columnList = "contract_id"),
+        @Index(name = "idx_case_id", columnList = "case_id")
+})
+@Data
+/*
+ * 不调用父类BaseEntity的equals和hashCode方法
+ * 仅使用当前类EsignFlow声明的字段进行比较
+ */
+@EqualsAndHashCode(callSuper = false)
+@AllArgsConstructor
+@NoArgsConstructor
+public class EsignFlow extends BaseEntity {
+    private static final long serialVersionUID = 39L;
+
+    /**
+     * e签宝流程ID
+     */
+    @Column(name = "flow_id", length = 100, unique = true)
+    private String flowId;
+
+    /**
+     * 关联的合同ID
+     */
+    @Column(name = "contract_id")
+    private Long contractId;
+
+    /**
+     * 关联的案件ID
+     */
+    @Column(name = "case_id")
+    private Long caseId;
+
+    /**
+     * 流程状态
+     * 0-草稿 1-签署中 2-签署完成 3-签署失败 4-已撤销 5-已过期 6-已拒签
+     */
+    @Column(name = "flow_status")
+    private Integer flowStatus;
+
+    /**
+     * 流程状态描述
+     */
+    @Column(name = "flow_status_desc", length = 50)
+    private String flowStatusDesc;
+
+    /**
+     * 签署文档ID(e签宝返回),不是数据库里的附件id
+     */
+    @Column(name = "document_id", length = 100)
+    private String documentId;
+
+    /**
+     * 签署文档下载地址
+     */
+    @Column(name = "document_url", length = 500)
+    private String documentUrl;
+
+    /**
+     * 签署文档名称
+     */
+    @Column(name = "document_name", length = 200)
+    private String documentName;
+
+    /**
+     * 客户签署人账号ID(e签宝返回)
+     */
+    @Column(name = "customer_account_id", length = 100)
+    private String customerAccountId;
+
+    /**
+     * 业务方签署人账号ID(e签宝返回)
+     */
+    @Column(name = "business_account_id", length = 100)
+    private String businessAccountId;
+
+    /**
+     * 客户签署状态
+     * 0-待签署 1-已签署 2-已拒签 3-已过期
+     */
+    @Column(name = "customer_sign_status")
+    private Integer customerSignStatus;
+
+    /**
+     * 业务方签署状态
+     * 0-待签署 1-已签署 2-已拒签 3-已过期
+     */
+    @Column(name = "business_sign_status")
+    private Integer businessSignStatus;
+
+    /**
+     * 客户签署时间
+     */
+    @Column(name = "customer_sign_time", length = 50)
+    private String customerSignTime;
+
+    /**
+     * 业务方签署时间
+     */
+    @Column(name = "business_sign_time", length = 50)
+    private String businessSignTime;
+
+    /**
+     * 流程创建时间(e签宝返回)
+     */
+    @Column(name = "create_time_esign", length = 50)
+    private String createTimeEsign;
+
+    /**
+     * 流程完成时间(e签宝返回)
+     */
+    @Column(name = "complete_time_esign", length = 50)
+    private String completeTimeEsign;
+
+    /**
+     * 备注信息
+     */
+    @Column(name = "remark", length = 500)
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    @Column(name = "create_time")
+    private String createTime;
+
+    /**
+     * 更新时间
+     */
+    @Column(name = "update_time")
+    private String updateTime;
+
+    /**
+     * 是否删除
+     */
+    @Column(name = "is_delete")
+    private Boolean isDelete = false;
+}
+

+ 9 - 3
src/main/java/com/loan/system/domain/entity/LoanCase.java

@@ -22,6 +22,12 @@ public class LoanCase extends BaseEntity{
     @Column(name = "customer_id")
     private Long customerId;
 
+    @Column(name = "other_id1")
+    private Long otherId1;
+
+    @Column(name = "other_id2")
+    private Long otherId2;
+
     @Column(name = "business_type" , length = 20)
     private String businessType;
 
@@ -40,6 +46,9 @@ public class LoanCase extends BaseEntity{
     @Column(name = "total_loan_amount", precision = 18, scale = 2)
     private Double totalLoanAmount;
 
+    @Column(name = "risk_comment")
+    private String riskComment;
+
     @Column(name = "is_complete", length = 10)
     private String isComplete;
 
@@ -51,8 +60,5 @@ public class LoanCase extends BaseEntity{
 
     @Column(name = "is_delete")
     private Boolean isDelete;
-    @ManyToOne(fetch = FetchType.LAZY)
-    @JoinColumn(name = "customer_id", insertable = false, updatable = false, referencedColumnName = "id")
-    private Customer customer;
 
 }

+ 46 - 0
src/main/java/com/loan/system/domain/entity/LocationDatum.java

@@ -0,0 +1,46 @@
+package com.loan.system.domain.entity;
+
+import lombok.*;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.math.BigDecimal;
+import java.time.Instant;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "location_data")
+public class LocationDatum extends BaseEntity {
+    private static final long serialVersionUID = 40L;
+    @Size(max = 100)
+    @Column(name = "simple_address", length = 100)
+    private String simpleAddress;
+
+    @NotNull
+    @Column(name = "longitude", nullable = false, precision = 10, scale = 6)
+    private Double longitude;
+
+    @NotNull
+    @Column(name = "latitude", nullable = false, precision = 10, scale = 6)
+    private Double latitude;
+
+    @Size(max = 200)
+    @Column(name = "detail_address", length = 200)
+    private String detailAddress;
+
+    @Column(name = "create_time")
+    private String createTime;
+
+    @Column(name = "update_time")
+    private String updateTime;
+
+    @NotNull
+    @Column(name = "is_delete", nullable = false)
+    private Boolean isDelete = false;
+
+}

+ 3 - 0
src/main/java/com/loan/system/domain/entity/PawnTicketInfo.java

@@ -20,6 +20,9 @@ public class PawnTicketInfo extends BaseEntity{
     @Column(name = "contract_id")
     private Long contractId;
 
+    @Column(name = "record_id")
+    private Long recordId;
+
     @Size(max = 255)
     @Column(name = "pawn_ticket_no")
     private String pawnTicketNo;

+ 50 - 0
src/main/java/com/loan/system/domain/entity/RepayBankInfo.java

@@ -0,0 +1,50 @@
+package com.loan.system.domain.entity;
+
+import lombok.*;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.time.Instant;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "repay_bank_info")
+public class RepayBankInfo extends BaseEntity {
+    private static final long serialVersionUID = 37L;
+    @NotNull
+    @Column(name = "record_id", nullable = false)
+    private Long recordId;
+
+    @Column(name = "amount")
+    private Double amount;
+
+    @Size(max = 50)
+    @Column(name = "remitter", length = 50)
+    private String remitter;
+
+    @Column(name = "mobile" , length = 11)
+    private String mobile;
+
+    @Size(max = 30)
+    @Column(name = "repay_account", length = 30)
+    private String repayAccount;
+
+    @Size(max = 100)
+    @Column(name = "repay_bank", length = 100)
+    private String repayBank;
+
+    @Column(name = "create_time")
+    private String createTime;
+
+    @Column(name = "update_time")
+    private String updateTime;
+
+    @Column(name = "is_delete")
+    private Boolean isDelete;
+
+}

+ 49 - 14
src/main/java/com/loan/system/domain/entity/RepaymentRecord.java

@@ -20,41 +20,76 @@ public class RepaymentRecord extends BaseEntity {
     @Column(name = "repayment_id")
     private Long repaymentId;
 
+    //计划申报
     @Column(name = "plan_user_id" , length = 20)
-    private Long planUserId;
+    private Long planUserId;//计划申报人
+
+    @Column(name = "remitter" , length = 50)
+    private String remitter;
+
+    @Column(name = "mobile", length = 11)
+    private String mobile;
 
     @Column(name = "amount", precision = 18, scale = 2)
     private Double amount;
 
-    @Column(name = "repay_time")
-    private String repayTime;
+    @Column(name = "location_id")
+    private Long locationId;
+    @Size(max = 200)
+    @Column(name = "repay_location", length = 200)
+    private String repayLocation;//计划地点
+
+    @Column(name = "plan_time")
+    private String planTime;
+
+    //审批
+    @Column(name = "approval_id")
+    private Long approvalId;
+
+    //回款操作
 
     @Size(max = 200)
     @Column(name = "repay_bank", length = 200)
     private String repayBank;
 
-    @Size(max = 200)
-    @Column(name = "repay_location", length = 200)
-    private String repayLocation;
-
     @Size(max = 20)
     @Column(name = "repay_account", length = 20)
     private String repayAccount;
 
-    @Column(name = "approval_comment")
-    private String approvalComment;
+    //TODO:汇款人暂时可以根据step获取(主从)
+    @Column(name = "main_user")
+    private Long mainUser;
 
-    @Column(name = "approval_decision")
-    private String approvalDecision;
+    @Column(name = "assist_user")
+    private Long assistUser;
 
-    @Column(name = "approval_user_id")
-    private Long approvalUserId;
+    @Column(name = "interest_amount", precision = 18, scale = 2)
+    private Double interestAmount;
+
+    @Column(name = "repay_time")
+    private String repayTime;//回款时间
 
     @Column(name = "repay_way",length = 20)
     private String repayWay;
 
     @Column(name = "is_interest")
-    private Boolean isInterest;
+    private Boolean isInterest;//是否开始利息回款(不包括本金+利息的情况)
+
+    @Column(name = "repay_comment")
+    private String repayComment;
+
+    //财务
+    @Column (name = "finance_id")
+    private Long financeId;
+
+    @Column(name = "finance_comment")
+    private String financeComment;//财务确认意见
+
+    @Column(name = "confirm_version")
+    private Long confirmVersion;
+
+    @Column(name = "confirm_time")
+    private String confirmTime;//财务确认时间
 
     @Column(name = "is_delete")
     private Boolean isDelete;

+ 2 - 2
src/main/java/com/loan/system/domain/entity/Step.java

@@ -31,8 +31,8 @@ public class Step extends BaseEntity{
     @Column(name = "user_id1")
     private Long userId1;
 
-    @Column(name = "user_id2")
-    private Long userId2;
+    @Column(name = "user_ids")
+    private String userIds;
 
     @Column(name = "begin_time")
     private String beginTime;

+ 33 - 0
src/main/java/com/loan/system/domain/entity/UserRecommender.java

@@ -0,0 +1,33 @@
+package com.loan.system.domain.entity;
+
+import lombok.*;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.time.Instant;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@Table(name = "user_recommender")
+public class UserRecommender extends BaseEntity{
+    private static final long serialVersionUID = 35L;
+    @NotNull
+    @Column(name = "user_id", nullable = false)
+    private Long userId;
+
+    @NotNull
+    @Column(name = "recommender_id", nullable = false)
+    private Long recommenderId;
+
+    @Column(name = "create_time")
+    private String createTime;
+
+    @Column(name = "update_time")
+    private String updateTime;
+
+    @Column(name = "is_delete")
+    private Boolean isDelete;
+
+}

+ 19 - 12
src/main/java/com/loan/system/domain/enums/ContractEnum.java

@@ -6,23 +6,30 @@ package com.loan.system.domain.enums;
  */
 public enum ContractEnum {
 
-    UNSTART("UNSTART","未开始"),
-    PROCESS("PROCESS","进行中"),
-    COMPLETED("COMPLETED","已完成");
+    LENDER(1, "出借人"),
+    BUSINESS_LICENSE(2, "营业执照"),
+    CONTRACT_COPIES(3, "合同份数"),
+    CONTRACT_PARTY_A(4, "合同落款甲方"),
+    CONTACT_ADDRESS(5, "联系地址"),
+    MINIMUM_DAYS(6, "最少天数"),
+    PAWN_INTEREST_RATE(7, "当金利率"),
+    MONTHLY_COMPREHENSIVE_FEE(8, "月综合费用"),
+    LOAN_INTEREST_RATE(9, "借款利率"),
+    SIGNATURE_ADDRESS(10, "签订地址");
 
-    private final String status;
-    private final String msg;
+    private final Integer code;
+    private final String label;
 
-    public String getStatus() {
-        return status;
+    ContractEnum(Integer code, String label) {
+        this.code = code;
+        this.label = label;
     }
 
-    public String getMsg() {
-        return msg;
+    public Integer getCode() {
+        return code;
     }
 
-    ContractEnum(String status, String msg) {
-        this.status = status;
-        this.msg = msg;
+    public String getLabel() {
+        return label;
     }
 }

+ 9 - 0
src/main/java/com/loan/system/domain/enums/DecisionEnum.java

@@ -9,10 +9,16 @@ public enum DecisionEnum {
     PROCESS("PROCESS", "处理中"),
     COMPLETE("COMPLETE", "已完成"),
 
+    //合同
+    CLEAR_NOT("CLEAR_NOT", "未结清"),
+    CLEAR_AMOUNT_YES("CLEAR_AMOUNT_YES", "本金已结清"),
+    CLEAR_YES("CLEAR_INTEREST_YES", "全部已结清"),
+
 
     PASS("PASS", "通过"),
     REJECT("REJECT", "驳回"),
     TERMINATE("TERMINATE", "终结"),
+    //押品
     WAITING_APPROVAL("WAIT_APPROVAL", "待审批"),
     REJECTION("REJECTION", "拒绝"),
     WAITING_EXECUTION("WAIT_EXECUTION", "待执行"),
@@ -20,6 +26,9 @@ public enum DecisionEnum {
     ENTER_WAREHOUSE("ENTER_WAREHOUSE", "入库"),
     OUT_WAREHOUSE("OUT_WAREHOUSE", "出库"),
 
+    ENTER_PLAN("ENTER_PLAN", "入库计划"),
+    OUT_PLAN("OUT_PLAN", "出库计划"),
+
 
     //出款状态
     DISBURSEMENT_PROCESS("DISBURSEMENT_PROCESS", "出款处理中"),

+ 13 - 13
src/main/java/com/loan/system/domain/enums/ExceptionEnum.java

@@ -48,6 +48,7 @@ public enum ExceptionEnum {
     FILE_CHECK_FAILED(-50,"文件校验错误"),
     IO_CLOSE_ERROR(-51,"读写错误"),
     FILE_NOT_IMAGE(-52,"文件不是图片类型"),
+    VERSION_CONTROLLER_FAILED(-53,"启用版本控制失败"),
 
     // -45 文件类型限制
 
@@ -67,6 +68,11 @@ public enum ExceptionEnum {
     CODE_ERROR(-117, "验证码错误"),
     PASSWORD_IS_NULL(-118, "密码不能为空"),
     PASSWORD_ERROR(-119, "密码错误"),
+    ASSISTANT__NOT_EXIST(-120, "辅办人员不存在"),
+    RECOMMENDER_NOT_EXIST(-121,"渠道推荐人不存在" ),
+    USER_NOT_IN_SYSTEM(-122, "账号未授权登录小程序"),
+    CUSTOMER_NOT_AUTHENTICATED(-123, "客户未实名认证,无法创建签署流程。请先引导完成认证。"),
+    COMPANY_NOT_AUTHENTICATED(-123, "业务方未实名认证,无法创建签署流程。请先引导完成认证。"),
 
 
     PROJECT_EXIST(-200,"项目已存在"),
@@ -86,7 +92,7 @@ public enum ExceptionEnum {
     CONTRACT_NOT_EXIST(-251, "合同不存在"),
     CONTRACT_MATERIAL_EXIST(-255, "合同材料已存在"),
     CONTRACT_MATERIAL_NOT_EXIST(-256, "合同材料不存在"),
-    CONTRACT_MATERIAL_HAS_NOT_EXIST(-257,"存在材料不存在"),
+    CONTRACT_SIGN_NOT_COMPLETE(-257,"合同未全部签署"),
     CONTRACT_TYPE_EXIST(-260, "合同类型已存在"),
     CONTRACT_TYPE_NOT_EXIST(-261, "合同类型不存在"),
     CONTRACT_TYPE_ERROR(-263,"合同类型超出限制"),
@@ -116,19 +122,11 @@ public enum ExceptionEnum {
     COLLATERAL_PLAN_HAS_ENOUGH(-319,"计划数已达上限"),
 
 
-    COLLATERAL_NOT_EXIST(-310, "抵押物不存在"),
-    COLLATERAL_PLAN_NOT_EXIST(-311,"计划不存在"),
-    COLLATERAL_PLAN_NOT_ALREADY_APPROVED(-312,"计划未处于待审批状态"),
-    COLLATERAL_PLAN_NOT_ALREADY_EXECUTION(-313,"计划未处于待执行状态"),
-    COLLATERAL_PLAN_NOT_EQUAL_WAREHOUSE(-314,"计划与入库操作不符"),
-    COLLATERAL_PLAN_NOT_EQUAL_OUTBOUND(-315,"计划与出库操作不符"),
-    COLLATERAL_PLAN_ALREADY_EXIST(-316,"该押品已有计划未执行完成,不能重复添加"),
-
 
     STEP_EXIST(-350, "项目环节已存在"),
     STEP_NOT_EXIST(-351, "项目环节不存在"),
-    STEP_MATERIAL_EXIST(-352,"环节材料已存在"),
-    STEP_MATERIAL_NOT_EXIST(-353,"环节材料不存在"),
+    STEP_HAS__NOT_COMPLETE(-352,"当前环节未完成,无法操作"),
+    PRE_TRAIL_HAS_COMPLETE(-353,"审批已完成,无法撤回"),
     STEP_TEMPLATE_NOT_EXIST(-354,"环节模板不存在"),
     STEP_TEMPLATE_READ_ERROR(-355,"环节模板读取失败"),
     STEP_TEMPLATE_LIST_NOT_EXIST(-356,"环节模板边不存在"),
@@ -155,8 +153,10 @@ public enum ExceptionEnum {
     DISBURSEMENT_HAS_COMPLETED(-415, "出款已完成,无法修改"),
     DISBURSEMENT_NOT_EXIST(-416, "出款信息不存在"),
     AMOUNT_HAS_EXCEED_PLANNED_AMOUNT(-417, "金额超出计划金额"),
-    REPAYMENT_HAS_BEEN_REJECTED(-418, "回款款已拒绝,无法新增"),
-    REPAYMENT_HAS_COMPLETED(-415, "出款已完成,无法修改");
+    REPAYMENT_HAS_BEEN_REJECTED(-418, "上次回款未审批通过,无法新增"),
+    REPAYMENT_HAS_COMPLETED(-419, "回款已完成,无法修改"),
+    REPAYMENT_PLAN_HAS_COMPLETED(-420, "此次出款计划已完成,请勿重复操作"),
+    DISBURSEMENT_HAS_BEEN_REJECTED(-421, "上次出款未审批通过,无法新增");
 
     private Integer code;
     private String msg;

+ 2 - 1
src/main/java/com/loan/system/domain/enums/RoleEnum.java

@@ -13,7 +13,8 @@ public enum RoleEnum {
     FINANCE(5, "FINANCE"),//财务人员
     BACK_OFFICE(6, "BACK_OFFICE"),//综合内勤
     EXTERNAL(7, "EXTERNAL"),//外部人员
-    ALL(8, "ALL");
+    CUSTOMER(8, "CUSTOMER"),//内部人员
+    ALL(9, "ALL");
 
     private Integer code;
     private String msg;

+ 196 - 50
src/main/java/com/loan/system/domain/enums/StepPropertyEnum.java

@@ -1,8 +1,8 @@
 package com.loan.system.domain.enums;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.*;
+
+import static com.loan.system.domain.enums.RoleEnum.*;
 
 /**
  * 针对特殊环节的标识设置
@@ -11,44 +11,51 @@ import java.util.List;
  */
 public enum StepPropertyEnum {
     /* ========== 环节定义 ========== */
-    BUSINESS_ACCEPT_PARENT(501, "业务受理环节", true),
-    BUSINESS_ACCEPT(502, "业务受理", false),
+    BUSINESS_ACCEPT_PARENT(501, "业务受理", true),
+    BUSINESS_ADD(502, "新增业务", false),
+    BUSINESS_ACCEPT(503, "业务调查", false),
 
     PRE_TRIAL_PARENT(511, "预审环节", true),
-    PRE_TRIAL(512, "预审", false),
+    PRE_TRIAL(512, "预审处理", false),
 
     APPROVAL_PARENT(521, "审批环节", true),
-    APPROVAL(522, "审批", false),
-
-    CONTRACT_SIGN_PARENT(531, "合同签约环节", true),
-    CONTRACT_SIGN(532, "合同签约", false),
-
-    COLLATERAL_RECEIVE(541, "押品取证环节", true),
-    PLAN_SUBMISSION(542, "取证计划上报", false),
-    APPROVAL_ASSIGNMENT(543, "取证审批分派", false),
-    EVIDENCE_CONFIRMATION(544, "确认取证", false),
-
-    DISBURSE_PARENT(551, "出款环节", true),
-    PLAN_REPORT(552, "计划上报", false),
-    PLAN_AUDIT(553, "计划审核", false),
-    DISBURSE_START(554, "出账启动", false),
-    DISBURSE_AUDIT(555, "出账审核", false),
-    FINANCE_DISBURSE(556, "财务出账", false),
-    DISBURSE_CONFIRM(557, "出账确认", false),
-
-    COLLATERAL_DELIVERY(561, "押品送证环节", true),
-    PLAN_SUBMISSION_2(562, "送证计划上报", false),
-    APPROVAL_ASSIGNMENT_2(563, "送证审批分派", false),
+    APPROVAL(522, "业务一审", false),
+    APPROVAL_2(513, "业务二审", false),
+
+    CONTRACT_SIGN_PARENT(531, "合同签订", true),
+    CONTRACT_SIGN(532, "合同签署", false),
+
+    COLLATERAL_RECEIVE(541, "押品取证", true),
+    PLAN_SUBMISSION(542, "取证申报", false),
+    APPROVAL_ASSIGNMENT(543, "取证派单", false),
+    EVIDENCE_CONFIRMATION(544, "取证确认", false),
+//    CHANNEL_PUSH(545, "推送渠道", false),
+//    CHANNEL_CONFIRMATION(546, "渠道确认", false),
+
+    DISBURSE_PARENT(551, "出帐环节", true),
+    PLAN_REPORT(552, "出帐申报", false),
+    PLAN_AUDIT(553, "出帐派单", false),
+    DISBURSE_START(554, "出帐操作", false),
+    DISBURSE_AUDIT(555, "出帐审批", false),
+    FINANCE_DISBURSE(556, "财务复核", false),
+    DISBURSE_CONFIRM(557, "出帐确认", false),
+
+    COLLATERAL_DELIVERY(561, "押品送证", true),
+    PLAN_SUBMISSION_2(562, "送证申报", false),
+    APPROVAL_ASSIGNMENT_2(563, "送证派单", false),
     DELIVERY_CONFIRMATION(564, "送证确认", false),
+//    CHANNEL_PUSH_2(565, "推送渠道", false),
+//    CHANNEL_RECEIVE(566, "渠道接收", false),
 
     REPAY_PARENT(571, "回款环节", true),
-    REPAY_START(572, "回款启动", false),
-    REPAY_APPROVAL(573, "回款审批", false),
-    FINANCE_CHECK(574, "财务核算", false),
-    BALANCE_REPAY(575, "余额回款", false),
+    REPAY_START(572, "回款申报", false),
+    REPAY_APPROVAL(573, "回款派单", false),
+//    FINANCE_CHECK(574, "财务核算", false),
+    BALANCE_REPAY(575, "回款操作", false),
     FINANCE_CONFIRM(576, "财务确认", false),
-    REPAYMENT_COMPLETE(577, "回款完成", false),
-    CASE_COMPLETE(578, "业务完成", false);
+    REPAYMENT_COMPLETE(577, "完成回款", false),
+    CASE_COMPLETE(578, "业务终结", false),
+    CASE_ARCHIVE(579, "业务归档", false);
 
 
     /* ========== 字段 & 构造 ========== */
@@ -71,38 +78,177 @@ public enum StepPropertyEnum {
         return Arrays.asList(values()); // 顺序与源码声明一致
     }
 
+    public static Map<String, List<String>> roleAndStep() {
+        Map<String, List<String>> map = new HashMap<>();
+
+        /* 1. 业务员 */
+        map.put(LEAD_SALES.getMsg(), Arrays.asList(
+                BUSINESS_ADD.getLabel(),
+                BUSINESS_ACCEPT.getLabel(),
+                PRE_TRIAL.getLabel(),
+                CONTRACT_SIGN.getLabel(),
+                PLAN_SUBMISSION.getLabel(),
+                EVIDENCE_CONFIRMATION.getLabel(),
+//                CHANNEL_PUSH.getLabel(),
+                PLAN_REPORT.getLabel(),
+                DISBURSE_START.getLabel(),
+                DISBURSE_CONFIRM.getLabel(),
+                PLAN_SUBMISSION_2.getLabel(),
+                DELIVERY_CONFIRMATION.getLabel(),
+//                CHANNEL_CONFIRMATION.getLabel(),
+                REPAY_START.getLabel(),
+                BALANCE_REPAY.getLabel(),
+                REPAYMENT_COMPLETE.getLabel()
+        ));
+        map.put(RoleEnum.ASSIST_SALES.getMsg(), Arrays.asList(
+                BUSINESS_ADD.getLabel(),
+                BUSINESS_ACCEPT.getLabel(),
+                PRE_TRIAL.getLabel(),
+                CONTRACT_SIGN.getLabel(),
+                PLAN_SUBMISSION.getLabel(),
+                EVIDENCE_CONFIRMATION.getLabel(),
+                PLAN_REPORT.getLabel(),
+                DISBURSE_START.getLabel(),
+                DISBURSE_CONFIRM.getLabel(),
+                PLAN_SUBMISSION_2.getLabel(),
+                DELIVERY_CONFIRMATION.getLabel(),
+                REPAY_START.getLabel(),
+                BALANCE_REPAY.getLabel(),
+                REPAYMENT_COMPLETE.getLabel()
+        ));
+
+        /* 2. 审批人 */
+        map.put(RoleEnum.APPROVER.getMsg(), Arrays.asList(
+                APPROVAL.getLabel(),
+                APPROVAL_2.getLabel(),
+                APPROVAL_ASSIGNMENT.getLabel(),
+                PLAN_AUDIT.getLabel(),
+                DISBURSE_AUDIT.getLabel(),
+                APPROVAL_ASSIGNMENT_2.getLabel(),
+                REPAY_APPROVAL.getLabel()
+        ));
+
+        /* 3. 财务人员 */
+        map.put(RoleEnum.FINANCE.getMsg(), Arrays.asList(
+                FINANCE_DISBURSE.getLabel(),
+//                FINANCE_CHECK.getLabel(),
+                FINANCE_CONFIRM.getLabel()
+        ));
+
+//        /* 4. 外部人员 */
+//        map.put(RoleEnum.EXTERNAL.getMsg(), Arrays.asList(
+//                CHANNEL_CONFIRMATION.getLabel(),
+//                CHANNEL_RECEIVE.getLabel()
+//        ));
+
+        /* 5. 综合员 */
+        map.put(RoleEnum.BACK_OFFICE.getMsg(), Arrays.asList(
+                CASE_COMPLETE.getLabel()
+        ));
+        return map;
+    }
+
+    public static Map<Integer , List<String>> stepAndRole() {
+        Map<Integer, List<String>> bizRoleMap = new HashMap<>();
+
+        /* 新增业务 */
+        bizRoleMap.put(BUSINESS_ADD.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+
+        /* 业务调查 */
+        bizRoleMap.put(BUSINESS_ACCEPT.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+
+        /* 预审处理 */
+        bizRoleMap.put(PRE_TRIAL.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+
+        /* 业务一审 / 二审 —— 仅审批人,无业务员 */
+        bizRoleMap.put(APPROVAL.getCode(), Arrays.asList(RoleEnum.APPROVER.getMsg()));
+        bizRoleMap.put(APPROVAL_2.getCode(), Arrays.asList(RoleEnum.APPROVER.getMsg()));
+
+        /* 合同签署 */
+        bizRoleMap.put(CONTRACT_SIGN.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+
+        /* 取证相关 */
+        bizRoleMap.put(PLAN_SUBMISSION.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+        bizRoleMap.put(EVIDENCE_CONFIRMATION.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+//        bizRoleMap.put(CHANNEL_PUSH.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+
+        bizRoleMap.put(APPROVAL_ASSIGNMENT.getCode(), Arrays.asList(APPROVER.getMsg()));
+
+        /* 渠道推送/接收 —— 外部人员,业务员不介入 */
+//        bizRoleMap.put(CHANNEL_CONFIRMATION.getCode(), Arrays.asList(EXTERNAL.getMsg()));
+//        bizRoleMap.put(CHANNEL_RECEIVE.getCode(), Arrays.asList(EXTERNAL.getMsg()));
+
+        /* 出帐环节 */
+        bizRoleMap.put(PLAN_REPORT.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+        bizRoleMap.put(DISBURSE_START.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+        bizRoleMap.put(DISBURSE_CONFIRM.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+
+        /* 出帐派单/审批 —— 审批人 */
+        bizRoleMap.put(PLAN_AUDIT.getCode(), Arrays.asList(APPROVER.getMsg()));
+        bizRoleMap.put(DISBURSE_AUDIT.getCode(), Arrays.asList(APPROVER.getMsg()));
+
+        /* 财务出帐 —— 财务人员 */
+        bizRoleMap.put(FINANCE_DISBURSE.getCode(), Arrays.asList(FINANCE.getMsg()));
+
+        /* 送证环节 */
+        bizRoleMap.put(PLAN_SUBMISSION_2.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+        bizRoleMap.put(DELIVERY_CONFIRMATION.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+//        bizRoleMap.put(CHANNEL_PUSH_2.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+
+        /* 送证派单 —— 审批人 */
+        bizRoleMap.put(APPROVAL_ASSIGNMENT_2.getCode(), Arrays.asList(APPROVER.getMsg()));
+
+        /* 回款环节 */
+        bizRoleMap.put(REPAY_START.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+        bizRoleMap.put(BALANCE_REPAY.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+        bizRoleMap.put(REPAYMENT_COMPLETE.getCode(), Arrays.asList(LEAD_SALES.getMsg(), ASSIST_SALES.getMsg()));
+
+        /* 回款派单/财务核算/确认 —— 审批人/财务人员 */
+        bizRoleMap.put(REPAY_APPROVAL.getCode(), Arrays.asList(APPROVER.getMsg()));
+//        bizRoleMap.put(FINANCE_CHECK.getCode(), Arrays.asList(FINANCE.getMsg()));
+        bizRoleMap.put(FINANCE_CONFIRM.getCode(),Arrays.asList(FINANCE.getMsg()));
+
+        /* 业务终结 —— 综合员 */
+        bizRoleMap.put(CASE_COMPLETE.getCode(), Arrays.asList(BACK_OFFICE.getMsg()));
+        bizRoleMap.put(CASE_ARCHIVE.getCode(), Arrays.asList(BACK_OFFICE.getMsg()));
+
+        return bizRoleMap;
+    }
+
     public static List<List<Integer>> seqList(){
         List<List<Integer>> list = new ArrayList<>(Arrays.asList(
                 Arrays.asList(),//受理0
-                Arrays.asList(3),
-                Arrays.asList(),//预审2
-                Arrays.asList(5),
-                Arrays.asList(),//审批4
+                Arrays.asList(2),
+                Arrays.asList(4),
+                Arrays.asList(),//预审3
+                Arrays.asList(6),
+                Arrays.asList(),//审批5
                 Arrays.asList(7),
-                Arrays.asList(),//合同签约6
                 Arrays.asList(9),
-                Arrays.asList(),//押品取证8
-                Arrays.asList(10),
-                Arrays.asList(11),
+                Arrays.asList(),//合同签约8
+                Arrays.asList(11,15,22),
+                Arrays.asList(),//押品取证10
+                Arrays.asList(12),
                 Arrays.asList(13),
-                Arrays.asList(),//出款12
-                Arrays.asList(14),
-                Arrays.asList(15),
+                Arrays.asList(),
+                Arrays.asList(),//出款14
                 Arrays.asList(16),
                 Arrays.asList(17),
                 Arrays.asList(18),
+                Arrays.asList(19),
                 Arrays.asList(20),
-                Arrays.asList(),//送证19
-                Arrays.asList(21),
-                Arrays.asList(22),
-                Arrays.asList(24),
-                Arrays.asList(),//回款23
-                Arrays.asList(25),
                 Arrays.asList(26),
+                Arrays.asList(),//送证21
+                Arrays.asList(23),
+                Arrays.asList(24),
+                Arrays.asList(),
+                Arrays.asList(),//回款25
                 Arrays.asList(27),
                 Arrays.asList(28),
                 Arrays.asList(29),
                 Arrays.asList(30),
+                Arrays.asList(31),
+                Arrays.asList(32),
                 Arrays.asList()
         ));
 

+ 14 - 11
src/main/java/com/loan/system/domain/pojo/ContractInformation.java

@@ -4,26 +4,25 @@ import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import javax.persistence.Column;
+
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 public class ContractInformation {
     //合同信息
     private String contractNo;
-    private String contractType;
-    private String contractUsage;
     private Double contractAmount;//合同金额
-    private Integer contractPeriod;
-    private String startDateTime;
-    private String endDateTime;
-    private Double interestRate;//合同利率
+    private Double amountRate;//当金利率
+    private Double serviceCost;//综合服务费
+    private Double loanRate;//借款利率
+    private String bankName;//客户银行
+    private String bankAccount;//客户银行账号
     //客户信息
     private String name;//客户姓名
     private String idNumber;//客户身份证号
     private String mobile;//客户手机号
-    private String bankName;//客户银行
-    private String bankAccount;//客户银行账号
-    private String signaturePath1;
+    private String signaturePath1;//签名图片
     private String contactAddress1;
     //押品信息
     private String address;//押品地址
@@ -36,11 +35,15 @@ public class ContractInformation {
     private String userMobile;
 
     //else
-    private Double serviceCost;
+    private String contractType;//业务类型
+    private String contractAttr;//业务属性
     private Integer leastDay;
-    private Integer pages1;
+    private String pages1;
     private Integer pages2;
     private String signLocation1;
     private String signLocation2;
+    private Integer contractPeriod;//还款-借款
+    private String startDateTime;//借款日
+    private String endDateTime;//还款日=借款+30
 
 }

+ 9 - 0
src/main/java/com/loan/system/domain/pojo/DateAndAmountPOJO.java

@@ -0,0 +1,9 @@
+package com.loan.system.domain.pojo;
+
+import lombok.Data;
+
+@Data
+public class DateAndAmountPOJO {
+    private String date;
+    private Double amount;
+}

+ 1 - 2
src/main/java/com/loan/system/domain/vo/ApprovalRecordVO.java

@@ -16,7 +16,6 @@ public class ApprovalRecordVO implements Serializable {
     private UserVO approver;//审批人
     private String decision;//审批结果
     private String comments;//审批意见
-    private Long version;
-
+    private String createTime;
 
 }

+ 23 - 0
src/main/java/com/loan/system/domain/vo/ChannelPushVO.java

@@ -0,0 +1,23 @@
+package com.loan.system.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ChannelPushVO implements Serializable {
+    private Long channelId;
+    private  String customerName;//客户名称
+    private  Double loanAmount;// 借款金额
+    private List<CollateralVO> collaterals;// 产证权利人姓名(多个押品)
+    private String channelName;// 渠道名称
+    private String groupName;// 组别
+    private String externalName;// 外部人员名称
+    private String externalComments;
+    private String status;
+}

+ 25 - 0
src/main/java/com/loan/system/domain/vo/ClearDetailVO.java

@@ -0,0 +1,25 @@
+package com.loan.system.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ClearDetailVO implements Serializable {
+    private Long id;
+    private Long contractId;
+    private Long caseId;
+    private String disbursementTime;
+    private Double disbursementAmount;
+    private String repayTime;
+    private Double currentAmount;//目前累计回款
+    private Double repayAmount;//此次回款
+    private Double currentInterest;//目前已回利息
+    private Double totalInterest;//总利息
+    private Integer theOrder;//次序(按时间)
+}

+ 7 - 1
src/main/java/com/loan/system/domain/vo/CollateralPlanVO.java

@@ -1,10 +1,13 @@
 package com.loan.system.domain.vo;
 
+import com.loan.system.domain.entity.BizRecommender;
+import com.loan.system.domain.entity.LocationDatum;
 import com.loan.system.domain.entity.User;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import javax.persistence.Column;
 import java.io.Serializable;
 import java.util.List;
 
@@ -17,10 +20,12 @@ public class CollateralPlanVO implements Serializable {
     private List<CollateralVO> collaterals;
     private UserVO planUser;
     private String time;
-    private String place;
+    private LocationDatum locationDatum;
     private String flag;
     private String comments;
     private String status;
+    private String contactName;
+    private String mobile;
 
     //审批
     private ApprovalRecordVO approvalRecord;
@@ -30,4 +35,5 @@ public class CollateralPlanVO implements Serializable {
     private String executeComments;
     private UserVO executeUser;
 
+
 }

+ 11 - 1
src/main/java/com/loan/system/domain/vo/CollateralVO.java

@@ -4,6 +4,7 @@ import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import javax.persistence.Column;
 import java.io.Serializable;
 import java.math.BigDecimal;
 
@@ -14,12 +15,21 @@ public class CollateralVO implements Serializable {
     private Long id;
     private Long caseId;//业务id
     //private Long contractId;//合同id
-    private String collateralName;//押品名称
+    private String collateralName;//押品名称/产证权利人
+    private String idNumber;
+    private String mobile;
     private String collateralType;//押品类型/业务属性
     private Long ownerCustomerId;//押品所属客户id
     private BigDecimal allocatedAmount;//押品分配金额
     private String address;//押品信息地址
     private String currentAddress;//押品实际地址
+    private String collateralNo;
+    private String collateralArea;
+    private String remark;
+    private Boolean isMortgaged;
+    private String mortgagedUser;
     private Boolean isInvolvedInLitigation;//是否涉及诉讼
+    private String operateStatus;
     private String staus;//押品状态
+
 }

+ 17 - 0
src/main/java/com/loan/system/domain/vo/ComplementVO.java

@@ -0,0 +1,17 @@
+package com.loan.system.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ComplementVO<T>{
+    private T data;
+    private List<ApprovalRecordVO> preApprovalRecords;
+    private String mainUserName;
+    private String assistantUserName;
+}

+ 9 - 2
src/main/java/com/loan/system/domain/vo/ContractVO.java

@@ -15,18 +15,25 @@ public class ContractVO implements Serializable {
     private Long id;
     private String businessAttr;//业务属性
     private Long caseId;//业务id
-    private Long customerId;//客户id
+    private String customerName;
     private String contractNo;// 合同编号
     private String contractName;//合同名称
     private Double contractAmount;//借款金额
+    private Double actualAmount;//实际金额
     private Double interestRate;//年利率
     private Double interestAmount;
-    private Boolean isCleared;
+    private String clearedStatus;
     private Integer loanPeriod;//借款期限
     private UserVO financeUser;
     private String content;//合同内容
+    private EsignFlowVO esignFlow;
     private Boolean signedByCustomer;//是否签署
     private Long signedId;//电子签名附件id
     private String commitedId;//承诺签名id
     private String signedTime;//签署实际
+    private String bankName;
+    private String bankAccount;
+    private Double amountRate;
+    private Double serviceCost;
+    private Double loanRate;
 }

+ 4 - 0
src/main/java/com/loan/system/domain/vo/CustomerVO.java

@@ -23,4 +23,8 @@ public class CustomerVO implements Serializable {
     private String bankName;
     private Boolean isIllegal;
     private Boolean faceAuth;
+    private String contactAddress;
+    private String businessLicense;
+    private Boolean isRegister;
+
 }

+ 20 - 0
src/main/java/com/loan/system/domain/vo/DisburseBankInfoVO.java

@@ -0,0 +1,20 @@
+package com.loan.system.domain.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class DisburseBankInfoVO implements Serializable {
+    private Long id;
+    private Long recordId;
+    private Double amount;
+    private String payee;
+    private String mobile;
+    private String disburseAccount;
+    private String disburseBank;
+}

+ 14 - 20
src/main/java/com/loan/system/domain/vo/DisbursementDetailVO.java

@@ -1,7 +1,9 @@
 package com.loan.system.domain.vo;
 
 import com.loan.system.domain.entity.DisbursementRecord;
+import com.loan.system.domain.entity.LocationDatum;
 import com.loan.system.domain.entity.User;
+import com.loan.system.domain.pojo.DateAndAmountPOJO;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -14,31 +16,23 @@ import java.util.Map;
 @AllArgsConstructor
 @NoArgsConstructor
 public class DisbursementDetailVO implements Serializable {
-    private Long disbursementId;
     private CustomerVO customer;
     private LoanCaseSimpleVO loanCase;
-    //计划上报
-    private Double disbursementAmount;
-    private String currentLocation;
-    private String plannedTime;
-    private String plannedComment;
-    private String rejectComment;
-    //计划审批
-    private List<ApprovalRecordVO> approvalRecords1;
-    //出款启动
-    private String disbursementComment;
-    private String rejectComment2;
-    private Map<Long ,String> contractAndPawn;
 
-    //出款审批
-    private List<ApprovalRecordVO> approvalRecords2;
-    //财务出款
+    //计划上报+审批+业务确认
+    List<DisbursementVO> disbursementPlans;
+
+    //出款启动+出款审批+财务复核
     private List<DisbursementRecordVO> disbursementRecords;
+    private String rejectComment2;
+
+    //每个合同的出款时间
+    private Map<Long , List<DateAndAmountPOJO>> contractAndDateAndAmount;
+
+    private Map<Long,Double> contractAndCurrentAmount;//目前合同对应的出款金额
+
+    private List<String> collateralStatus;//押品状态
     private Double currentAmount;//目前金额
     private Double totalAmount;//总金额
-    //业务确认
-    private String confirmComment;
-    private List<String> collateralStatus;//押品状态
-
 
 }

+ 21 - 7
src/main/java/com/loan/system/domain/vo/DisbursementRecordVO.java

@@ -4,26 +4,40 @@ import com.loan.system.domain.entity.BaseEntity;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import org.apache.catalina.LifecycleState;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Table;
 import java.io.Serializable;
 import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
 
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 public class DisbursementRecordVO implements Serializable {
     private Long id;//记录id
-    private Long disbursementId;//出款单id
-    private Long financeId;
-    private String financeName;
+    private Long disbursementId;//出款计划id
+    private Long caseId;
     private Double amount;//出款金额
     private String createTime;//创建时间
-    private String receiptName;//收款人
-    private String disbursementLocation;//出款地点
-    private String disbursementAccount;//出款账户
-    private String disbursementBank;//出款银行
+    private List<DisburseBankInfoVO> disburseBankInfoVOs;
+    private String comment;
+
+    private List<ApprovalRecordVO> approvalRecordVOs;
+    private List<ApprovalRecordVO> financeApprovalRecordVOs;
+
+    private UserVO confirmUser;
+    private String confirmComment;
+    private Long confirmVersion;
+
+    private UserVO mainUser;
+    private UserVO assistUser;
+
+    private Map<Long ,String> contractAndPawn;//当票信息
+
+    private Map<Long,Double> contractIdAndAmount;//每个合同对应的金额
 
 }

Some files were not shown because too many files changed in this diff