14 KiB
合同管理 — 04 API 规格(重写移植版)
步骤 1 产出 | 日期:2026-05-31
权威源:shanxia/crm/backend/ruoyi-modules/ruoyi-crm/+ 快照docs/crm-migration/reference/java/contract/
状态:后端 ✅ 已重写(2026-06-01);前端 ✅ Fresh Mint 适配完成
验收清单:08-verify.mdHTTP 约定:对齐原 CRM(GET/PUT/DELETE/POST 与 Java Controller 一致),不采用旧门户「全 POST」包装
0. 路径与响应约定
0.1 原 CRM(Java / RuoYi)
| 项 | 值 |
|---|---|
| Controller 前缀 | /crm/contract(及 /crm/contract/approval 等子模块) |
| 网关前缀 | 前端 request 通常走 /prod-api,完整路径如 /prod-api/crm/contract/list |
| 列表分页入参 | Query:CrmContractBo 字段 + pageNum / pageSize |
| 列表分页出参 | TableDataInfo:{ rows, total } |
| 单条出参 | R<T>:{ code, msg, data } |
| 详情 GET | GET /crm/contract/{id} → data 为 CrmContractVo(含 products、canApprove) |
0.2 shanxia2.0 Python 目标
| 项 | 值 |
|---|---|
| Router 挂载 | APIRouter(prefix="/web/crm") → 完整前缀 /api/web/crm |
| 子路径 | 与 Java 相同(如 GET .../contract/list,不是 POST .../contract/list) |
| 租户 | 所有查询/写入带 tenant_no(Java 靠 TenantEntity;Python 从当前用户上下文取) |
| 主键 | VARCHAR(64) UUID(Java 为 Long;API 层接受 string,避免前端精度问题) |
| 列表响应 | 兼容 RuoYi:{ rows/list, total } 包在统一 success_response 的 data 内 |
| 导出 | POST .../export,响应 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
0.3 前端(已拷贝 CRM)实际请求路径
拷贝页调用 /crm/contract/...(非 /web/crm)。步骤 7 机械适配时二选一:
- 推荐:Vite 增加 proxy:
/crm→{backend}/api/web/crm(保留原 URL + 原 HTTP 动词) - 或改
http的baseURL,仍保持各函数method: 'get'|'put'与 Java 一致
1. 合同主模块 — CrmContractController
Java:@RequestMapping("/crm/contract")
Service:ICrmContractService / CrmContractServiceImpl
| # | HTTP | 路径 | Java Service 方法 | 请求体 / Query | 响应 data | 前端函数 | 后端 |
|---|---|---|---|---|---|---|---|
| C01 | GET | /list |
queryPageList |
Query: CrmContractBo + pageNum, pageSize |
{ rows, total } |
listContract |
❌ |
| C02 | GET | /{id} |
queryById |
Path: id |
CrmContractVo |
getContract |
❌ |
| C03 | POST | / |
insertByBo |
Body: CrmContractBo(AddGroup) |
新合同 id |
addContract |
❌ |
| C04 | PUT | / |
updateByBo |
Body: CrmContractBo(EditGroup) |
— | updateContract |
❌ |
| C05 | DELETE | /{ids} |
deleteWithValidByIds |
Path: 逗号或单 id | — | delContract |
❌ |
| C06 | PUT | /submitApproval |
submitApproval |
{ id, approverIds[], remark? } |
— | submitApproval |
❌ |
| C07 | PUT | /batchSubmitApproval |
batchSubmitApproval |
{ ids[], approverIds[], remark? } |
成功条数 int |
batchSubmitApproval |
❌ |
| C08 | POST | /saveTemplate |
saveTemplate |
{ id, templateContent, settlementClauseDrafts? } |
— | saveTemplate |
❌ |
| C09 | GET | /getTemplate/{id} |
getTemplate |
Path: id |
富文本 HTML 字符串 | getTemplate |
❌ |
| C10 | PUT | /withdrawApproval |
withdrawApproval |
{ id, remark? } |
— | withdrawApproval |
❌ |
| C11 | PUT | /startExecute |
startExecute |
{ id, remark? } |
— | startExecute / executeContract |
❌ |
| C12 | PUT | /finish |
finishContract |
{ id, remark? } |
— | finishContract / completeContract |
❌ |
| C13 | PUT | /invalid |
invalidContract |
{ id, invalidReason, remark? } |
— | invalidContract |
❌ |
| C14 | POST | /uploadFile |
uploadFile |
{ id, fileUrl } |
— | uploadFile |
❌ |
| C15 | POST | /uploadAttachments |
uploadAttachments / uploadAttachmentsWithCategory |
{ id, attachments[]? | ossIds? } |
— | uploadAttachments |
❌ |
| C16 | POST | /export |
queryList + Excel |
Body/Query: CrmContractBo |
xlsx 流 | exportContract |
❌ |
| C17 | GET | /statistics/overview |
statisticsOverview |
Query: ownerUserId?, beginTime?, endTime? |
Map 统计 | getStatisticsOverview |
❌ |
| C18 | GET | /statistics/status |
statisticsByStatus |
— | List Map | getStatisticsByStatus |
❌ |
| C19 | GET | /statistics/type |
statisticsByType |
— | List Map | getStatisticsByType |
❌ |
| C20 | GET | /statistics/trend |
statisticsTrend |
Query: type=month, beginTime?, endTime? |
List Map | getStatisticsTrend |
❌ |
| C21 | GET | /statistics/expiring |
queryExpiring |
Query: days=30 |
List<CrmContractVo> |
getExpiringContracts |
❌ |
| C22 | POST | /renew/{parentContractId} |
renewContract |
Path + Body: CrmContractBo |
新合同 id | renewContract |
❌ |
| C23 | POST | /createFromOpportunity/{opportunityId} |
createFromOpportunity |
Path + Body | — | createFromOpportunity |
❌ |
| C24 | POST | /createFromQuote/{quoteId} |
createFromQuote |
Path + Body | — | createFromQuote |
❌ |
| C25 | PUT | /sign |
signContract |
{ id, actualSignDate, contractFile, remark? } |
— | (form 页可能用) | ❌ |
| C26 | PUT | /batch/assign |
batchAssignOwner |
{ ids[], ownerUserId, remark? } |
— | (列表批量分配) | ❌ |
| C27 | GET | /download/{id} |
downloadContractPdf |
Path: id |
文件路径/URL 字符串 | (详情下载) | ❌ |
1.1 CrmContractBo 列表/筛选字段(Query)
与前端 ContractQuery 对齐:
| 字段 | 说明 |
|---|---|
contractNo, contractName, customerName |
分别 like;三者同值时 OR 联合搜索 |
customerId, opportunityId, quoteId |
精确 |
contractType, status, approvalStatus |
精确 |
excludeStatus |
ne status(列表默认排除 invalid) |
ownerUserId |
Tab「我的合同」 |
pendingApprovalUserId |
Tab「待审批」→ 子查询 crm_contract_approval |
approvedByUserId |
Tab「已审批」→ 历史审批记录反查 contract_id |
minAmount, maxAmount |
金额区间(Java 在 params 中) |
signDateBegin, signDateEnd |
签订日期 |
beginTime, endTime |
创建时间 |
pageNum, pageSize |
分页 |
1.2 CrmContractVo 关键返回字段
列表/详情需包含:canApprove(列表逐行计算)、products(详情)、supplierName(详情 enrichment)、回款/开票金额字段、attachmentFiles(JSON 或逗号 ossId)。
1.3 列表页统计卡片说明
index.vue 未调用 C17;Tab 顶栏统计由当前页列表数据前端聚合(loadStatistics)。C17–C21 仍须在 Service 层实现(其它页/报表可能使用)。
2. 合同审批 — CrmContractApprovalController
Java:@RequestMapping("/crm/contract/approval")
Service:ICrmContractApprovalService
| # | HTTP | 路径 | Service 方法 | 请求 | 前端 | 后端 |
|---|---|---|---|---|---|---|
| A01 | GET | /list |
queryPageList |
Query: CrmContractApprovalBo + 分页 |
listApproval |
❌ |
| A02 | GET | /{id} |
queryById |
Path: id |
getApproval |
❌ |
| A03 | GET | /flow/{contractId} |
queryListByContractId |
Path: contractId |
listContractApproval / getContractApprovalFlow |
❌ |
| A04 | POST | /pass |
approvePass |
{ id, approvalOpinion? } |
approvePass |
❌ |
| A05 | POST | /reject |
approveReject |
{ id, approvalOpinion } |
approveReject |
❌ |
| A06 | POST | /transfer |
approveTransfer |
{ id, targetApproverId, transferReason } |
approveTransfer |
❌ |
| A07 | POST | /export |
queryList + Excel |
Query/Bo | exportApproval |
❌ |
| A08 | POST | / |
approveContractByContractId |
{ id: contractId, approvalResult, approvalOpinion, attachments?, remark? } |
approveContract / rejectContract |
❌ |
| A09 | POST | /refreshNames |
refreshApproverNames |
{ contractId? } |
refreshApproverNames |
❌ |
注意:列表页 ApprovalDialog 使用 A08(按 合同 id + approvalResult);独立审批页可用 A04/A05(按 审批记录 id)。
3. 合同变更 — CrmContractChangeController
Java:@RequestMapping("/crm/contract/change")
Service:ICrmContractChangeService
| # | HTTP | 路径 | Service 方法 | 前端 | 后端 |
|---|---|---|---|---|---|
| H01 | GET | /list |
queryPageList |
listChange |
❌ |
| H02 | GET | /{id} |
queryById |
getChange |
❌ |
| H03 | POST | / |
insertByBo |
addChange |
❌ |
| H04 | PUT | / |
updateByBo |
updateChange |
❌ |
| H05 | DELETE | /{ids} |
deleteWithValidByIds |
delChange |
❌ |
| H06 | POST | /submitApproval |
(Controller TODO 空实现) | submitChangeApproval |
❌ |
| H07 | PUT | /withdraw/{id} |
withdrawChange |
withdrawChange |
❌ |
| H08 | PUT | /execute/{id} |
executeChange |
executeChange |
❌ |
| H09 | POST | /export |
queryList + Excel |
exportChange |
❌ |
4. 合同模板库 — CrmContractTemplateController
Java:@RequestMapping("/crm/contract/template")
与 C08/C09 区别:此处是 模板主数据;C08/C09 是 单份合同实例 的富文本内容。
| # | HTTP | 路径 | Service 方法 | 前端 | 后端 |
|---|---|---|---|---|---|
| T01 | GET | /list |
queryPageList |
listTemplate |
❌ |
| T02 | GET | /{id} |
queryById |
getTemplate(template.ts) |
❌ |
| T03 | POST | / |
insertByBo |
addTemplate |
❌ |
| T04 | PUT | / |
updateByBo |
updateTemplate |
❌ |
| T05 | DELETE | /{ids} |
deleteWithValidByIds |
delTemplate |
❌ |
| T06 | PUT | /enable/{id} |
enableTemplate |
enableTemplate |
❌ |
| T07 | PUT | /disable/{id} |
disableTemplate |
disableTemplate |
❌ |
| T08 | POST | /export |
queryList + Excel |
exportTemplate |
❌ |
5. 合同产品 — ICrmContractProductService(无独立 Controller)
产品随合同 Bo 的 products[] 在 C03/C04 中批量写入;Service 另提供:
| Service 方法 | 用途 | 后端 |
|---|---|---|
queryListByContractId |
详情/列表展开行 | ❌(合入 C02) |
insertByBo / updateByBo / deleteByIds |
产品 CRUD | ❌ |
updateDeliveryStatus |
交付进度 | ❌ |
框架合同 调价区间 crm_contract_product_price_tier 在 insertByBo / 提交审批校验中处理(见 ServiceImpl)。
6. Service 层无 Controller 入口(内部 / 跨模块)
| Service 方法 | 调用方 | 说明 | 后端 |
|---|---|---|---|
updateReceivedAmount(contractId) |
回款 Service | 回写 received_amount / unreceived_amount |
❌ |
updateInvoiceAmount(contractId) |
开票 Service | 回写 invoice_amount / uninvoice_amount |
❌ |
generateContractNo(supplierId, signDate) |
insertByBo 内部 |
供方 + 日期规则编号 | ❌ |
createFromTemplate(templateId, bo) |
无 Controller | 若前端后续需要再暴露 | ❌ |
queryList(bo) |
C16 导出 | 同 C01 条件无分页 | ❌ |
定时任务:合同模块 无 专用 cron;到期提醒走 C21 查询接口。
7. 前端跨模块依赖(非 contract Controller)
| 用途 | HTTP | 路径 | 使用页面 |
|---|---|---|---|
| 操作日志 | GET | /monitor/operlog/list |
detail.vue |
| OSS 批量 | GET | /resource/oss/listByIds/{ids} |
index / form / detail / Upload |
| OSS 下载 | GET | /resource/oss/download/{ossId} |
附件预览下载 |
| 商机名 | GET | /crm/opportunity/{id} |
index 详情抽屉 / form |
| 报价单号 | GET | /crm/quote/{id} |
index 详情抽屉 / form |
shanxia2.0 需确认上述路由是否已有 Python 等价实现或需同步移植。
8. Python 实现映射(计划)
| 层 | 目标文件 |
|---|---|
| 路由 | backend/app/api/crm_contract_routes.py |
| 子路由 | backend/app/api/crm_contract_sub_routes.py(approval / change / template) |
| CRUD | backend/app/crud/crm/contract.py |
| 审批 | backend/app/crud/crm/contract_approval.py |
| 产品 | backend/app/crud/crm/contract_product.py |
| 变更 | backend/app/crud/crm/contract_change.py |
| 模板库 | backend/app/crud/crm/contract_template.py |
| Schema | backend/app/schemas/crm/contract.py 等 |
| 导出列 | backend/app/utils/crm_export_columns.py(CONTRACT_*_EXPORT_COLUMNS 已存在) |
| 注册 | backend/app/api/crm.py 末尾 register_contract_routes |
翻译顺序(对应断点步骤 15–26):C01+C02 → C03–C05 → C06–C07+A* → C10–C15 → C16–C21 → C22–C27 → H* / T* → 恢复 quote.to_contract → C24。
9. 权限码(Java 注释,实现时可对接 menuCode)
| 权限 | 接口组 |
|---|---|
crm:contract:list |
C01, C17–C21 |
crm:contract:query |
C02, C09, C27 |
crm:contract:add |
C03, C23, C24 |
crm:contract:edit |
C04, C08, C10, C14–C15 |
crm:contract:remove |
C05 |
crm:contract:submit |
C06–C07 |
crm:contract:execute |
C11–C12 |
crm:contract:invalid |
C13 |
crm:contract:export |
C16 |
crm:contract:renew |
C22 |
crm:contract:sign |
C25 |
crm:contract:assign |
C26 |
crm:contract:approval:* |
A01–A09 |
crm:contract:change:* |
H01–H09 |
crm:contract:template:* |
T01–T08 |
10. 完成标准(重写后勾选)
- 27 个主接口 C01–C27 HTTP 动词与 Java 一致
- 9 个审批接口 A01–A09
- 9 个变更接口 H01–H09(H06 与 Java 行为对齐或明确实现)
- 8 个模板接口 T01–T08
- 内部方法
updateReceivedAmount/updateInvoiceAmount可供回款/开票模块调用 createFromQuote恢复报价转合同- 前端拷贝页在 proxy 配置下可直接调用,无需改为全 POST
变更记录
| 日期 | 说明 |
|---|---|
| 2026-05-31 | 步骤 1:自 Java Controller + Service 接口全量梳理;废弃旧「全 POST 门户」规格 |