# 合同变更功能设计方案 ## 一、需求理解(通俗说明) - **谁能变更**:只有「已激活」的合同可以发起变更。即:合同状态=可用(contract_status='3')且 激活标识=是(active_flag='1')。 - **做什么**: 1. 做一个「合同变更」列表页,风格和合同信息列表类似,能新增;点新增就跳到「合同变更申请」页。 2. 在「合同变更申请」页里先选一个合同,选完后把该合同的信息、物料等查出来填到页面,用户可修改后提交(暂存或走审批)。 3. 要能按合同查「合同变更历史」——某合同共发生过几次变更、每次变更单号、时间、状态、是否已回写等。 4. 变更审批通过后,要把「变更后的内容」写回正式表:合同表(erp_contract_info)、合同物料表(erp_contract_material)。 下面按「表结构」和「业务逻辑」分开说,不写具体代码,只讲逻辑与 SQL 表结构。 --- ## 二、表结构设计(MySQL) 思路:**一次变更 = 一张变更单 + 变更后的合同信息快照 + 变更后的物料快照**。 审批通过后,用快照表的数据去更新正式合同表、合同物料表,并标记「已回写」,便于查历史、防重复回写。 ### 1. 合同变更主表:erp_contract_change 存「一次变更申请」的公共信息,关联原合同。 | 字段名 | 类型 | 说明 | | ------------------ | -------------- | --------------------------------------- | | contract_change_id | bigint, PK, 自增 | 合同变更ID | | tenant_id | varchar(20) | 租户编号 | | contract_id | bigint | 原合同ID(关联 erp_contract_info.contract_id) | | contract_code | varchar(64) | 原合同编号(冗余,列表展示用) | | contract_name | varchar(255) | 原合同名称(冗余,列表展示用) | | change_code | varchar(64) | 变更单编号(如:HTBG-2025-001) | | change_reason | varchar(500) | 变更原因 | | change_status | varchar(32) | 变更状态:1暂存 2审批中 3可用 | | flow_status | varchar(32) | 流程状态(与工作流一致) | | apply_time | datetime | 申请时间 | | write_back_flag | char(1) | 是否已回写:0否 1是 | | write_back_time | datetime | 回写时间 | | remark | varchar(255) | 备注 | | active_flag | char(1) | 激活标识(1是 0否) | | del_flag | char(1) | 删除标志(0存在 1删除) | | create_dept | bigint | 创建部门 | | create_by | bigint | 创建人 | | create_time | datetime | 创建时间 | | update_by | bigint | 更新人 | | update_time | datetime | 更新时间 | **说明**: - 列表页、历史记录都查这张表;按 contract_id 查即该合同的变更历史。 - write_back_flag 防止重复回写;只有 change_status='3'(可用)且流程通过后才执行回写并置为 1。 --- ### 2. 合同变更信息快照表:erp_contract_change_info 存「变更后的合同主信息」快照,字段与 erp_contract_info 对齐(去掉合同主键 contract_id,改为关联变更单)。 - **主键**:change_info_id(bigint, 自增)。 - **关联**:contract_change_id(bigint,关联 erp_contract_change.contract_change_id)。 - **其余字段**:与 erp_contract_info 一致(如 contract_code、contract_name、total_price、contract_date、contract_manager_id 等所有业务字段),但这里表示的是「变更后」要写回合同表的值。 - 公共字段:tenant_id, remark, del_flag, create_dept, create_by, create_time, update_by, update_time。 **约束**:一个变更单对应一条快照(1:1)。回写时:用这条记录 UPDATE erp_contract_info SET ... WHERE contract_id = 主表中的 contract_id。 --- ### 3. 合同变更物料快照表:erp_contract_change_material 存「变更后的合同物料」快照,字段与 erp_contract_material 对齐。 - **主键**:change_material_id(bigint, 自增)。 - **关联**:contract_change_id(bigint),不存 contract_id(回写时再填原 contract_id)。 - **其余字段**:与 erp_contract_material 一致(如 material_flag, product_name, specification_description, material_id, amount, unit_id, before_price, tax_rate, including_price, subtotal, remark 等)。 - 公共字段:tenant_id, active_flag, del_flag, create_dept, create_by, create_time, update_by, update_time。 **回写逻辑**: - 先按原 contract_id 删除 erp_contract_material 中该合同下所有物料; - 再把本变更单下 erp_contract_change_material 的记录插入到 erp_contract_material,contract_id 填原合同ID。 --- ### 4. 合同变更付款方式快照表:erp_contract_change_payment_method 存「变更后的合同付款方式」快照,字段与 erp_contract_payment_method 一一对齐。 - **主键**:change_payment_id(bigint, 自增)。 - **关联**:contract_change_id(bigint),关联 erp_contract_change.contract_change_id;不存 contract_id(回写时再填原 contract_id)。 - **业务字段**(与 erp_contract_payment_method 一致):sort_order、payment_stage_id、payment_deadline、payment_percentage、invoice_percentage、payment_amount、payment_description、remark。 - **公共字段**:tenant_id、active_flag、del_flag、create_dept、create_by、create_time、update_by、update_time。 **回写逻辑**: - 先按原 contract_id 删除 erp_contract_payment_method 中该合同下所有付款方式; - 再把本变更单下 erp_contract_change_payment_method 的记录插入到 erp_contract_payment_method,contract_id 填原合同ID,其他字段从快照表拷贝。 --- ## 三、建表 SQL 示例 ```sql -- 合同变更主表 DROP TABLE IF EXISTS `erp_contract_change`; CREATE TABLE `erp_contract_change` ( `contract_change_id` bigint NOT NULL AUTO_INCREMENT COMMENT '合同变更ID', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `contract_id` bigint NOT NULL COMMENT '原合同ID', `contract_code` varchar(64) DEFAULT NULL COMMENT '原合同编号', `contract_name` varchar(255) DEFAULT NULL COMMENT '原合同名称', `change_code` varchar(64) DEFAULT NULL COMMENT '变更单编号', `change_reason` varchar(500) DEFAULT NULL COMMENT '变更原因', `change_status` varchar(32) DEFAULT NULL COMMENT '变更状态(1暂存 2审批中 3可用)', `flow_status` varchar(32) DEFAULT NULL COMMENT '流程状态', `apply_time` datetime DEFAULT NULL COMMENT '申请时间', `write_back_flag` char(1) DEFAULT '0' COMMENT '是否已回写(0否 1是)', `write_back_time` datetime DEFAULT NULL COMMENT '回写时间', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `active_flag` char(1) DEFAULT '1' COMMENT '激活标识(1是 0否)', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0存在 1删除)', `create_dept` bigint DEFAULT NULL COMMENT '创建部门', `create_by` bigint DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` bigint DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`contract_change_id`), KEY `idx_contract_id` (`contract_id`), KEY `idx_change_status` (`change_status`), KEY `idx_change_code` (`change_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='合同变更主表'; -- 合同变更信息快照表(字段与 erp_contract_info 对齐,此处仅列关键字段,其余按 erp_contract_info 补全) DROP TABLE IF EXISTS `erp_contract_change_info`; CREATE TABLE `erp_contract_change_info` ( `change_info_id` bigint NOT NULL AUTO_INCREMENT COMMENT '变更信息快照ID', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `contract_change_id` bigint NOT NULL COMMENT '合同变更ID', `contract_flag` char(1) DEFAULT NULL COMMENT '有无合同(1有 2无)', `customer_contract_code` varchar(128) DEFAULT NULL COMMENT '客户合同编号', `contract_code` varchar(64) DEFAULT NULL COMMENT '合同编号', `contract_name` varchar(255) DEFAULT NULL COMMENT '合同名称', `contract_category` varchar(32) DEFAULT NULL COMMENT '合同大类', `contract_type` varchar(32) DEFAULT NULL COMMENT '合同类型', `business_direction` varchar(16) DEFAULT NULL COMMENT '业务方向', `contract_dept_id` bigint DEFAULT NULL COMMENT '部门', `contract_date` datetime DEFAULT NULL COMMENT '合同签订日期', `total_price` decimal(16,2) DEFAULT NULL COMMENT '合同总价', `one_customer_id` bigint DEFAULT NULL COMMENT '甲方公司', `one_represent` varchar(128) DEFAULT NULL COMMENT '甲方授权代表', `one_date` datetime DEFAULT NULL COMMENT '甲方签字日期', `two_customer_id` bigint DEFAULT NULL COMMENT '乙方公司', `two_represent` varchar(128) DEFAULT NULL COMMENT '乙方授权代表', `two_date` datetime DEFAULT NULL COMMENT '乙方签字日期', `contract_manager_id` bigint DEFAULT NULL COMMENT '合同负责人', `template_id` bigint DEFAULT NULL COMMENT '合同模板ID', `oss_id` varchar(512) DEFAULT NULL COMMENT '附件ID', `payment_account_id` bigint DEFAULT NULL COMMENT '付款账户ID', `payment_method` varchar(128) DEFAULT NULL COMMENT '付款方式', `signature_appendix` bigint DEFAULT NULL COMMENT '签字合同附件', `warranty_period` int DEFAULT NULL COMMENT '质保期(天)', `internal_contract_code` varchar(128) DEFAULT NULL COMMENT '内部合同号', `external_contract_code` varchar(128) DEFAULT NULL COMMENT '外部合同号', `order_contract_code` varchar(128) DEFAULT NULL COMMENT '订单号', `project_contract_code` varchar(128) DEFAULT NULL COMMENT '项目号', `delivery_start` int DEFAULT NULL COMMENT '交付启动期限', `warranty_period_description` varchar(128) DEFAULT '' COMMENT '质保期描述', `delivery_location` varchar(255) DEFAULT '' COMMENT '交货地点', `ship_method` varchar(128) DEFAULT '' COMMENT '运输方式', `tax_rate` decimal(16,2) DEFAULT NULL COMMENT '合同税率', `signing_place` varchar(200) DEFAULT '' COMMENT '签订地点', `material_remark` varchar(500) DEFAULT '' COMMENT '合同物料备注', `contract_template_flag` char(1) DEFAULT NULL COMMENT '合同模板标识', `capitalized_amount` varchar(128) DEFAULT NULL COMMENT '合同大写金额', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', `create_dept` bigint DEFAULT NULL COMMENT '创建部门', `create_by` bigint DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` bigint DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`change_info_id`), UNIQUE KEY `uk_contract_change_id` (`contract_change_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='合同变更信息快照'; -- 合同变更物料快照表(字段与 erp_contract_material 对齐) DROP TABLE IF EXISTS `erp_contract_change_material`; CREATE TABLE `erp_contract_change_material` ( `change_material_id` bigint NOT NULL AUTO_INCREMENT COMMENT '变更物料快照ID', `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', `contract_change_id` bigint NOT NULL COMMENT '合同变更ID', `material_flag` char(1) DEFAULT NULL COMMENT '标准物料标识', `product_name` varchar(255) DEFAULT NULL COMMENT '产品名称', `specification_description` varchar(255) DEFAULT NULL COMMENT '规格描述', `material_id` bigint DEFAULT NULL COMMENT '物料ID', `relation_material_id` bigint DEFAULT NULL COMMENT '销售物料ID', `amount` decimal(16,2) DEFAULT NULL COMMENT '数量', `unit_id` bigint DEFAULT NULL COMMENT '单位ID', `before_price` decimal(16,2) DEFAULT NULL COMMENT '未税单价', `tax_rate` decimal(16,2) DEFAULT NULL COMMENT '税率', `including_price` decimal(16,2) DEFAULT NULL COMMENT '含税单价', `subtotal` decimal(16,2) DEFAULT NULL COMMENT '小计', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `sort_order` int DEFAULT 0 COMMENT '排序号', `active_flag` char(1) DEFAULT '1' COMMENT '激活标识', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', `create_dept` bigint DEFAULT NULL COMMENT '创建部门', `create_by` bigint DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` bigint DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`change_material_id`), KEY `idx_contract_change_id` (`contract_change_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='合同变更物料快照'; -- 合同变更付款方式快照表(字段与 erp_contract_payment_method 对齐) DROP TABLE IF EXISTS `erp_contract_change_payment_method`; CREATE TABLE `erp_contract_change_payment_method` ( `change_payment_id` bigint NOT NULL AUTO_INCREMENT COMMENT '变更付款方式快照ID', `tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户编号', `contract_change_id` bigint NOT NULL COMMENT '合同变更ID', `sort_order` int NULL DEFAULT NULL COMMENT '排序号', `payment_stage_id` bigint NULL DEFAULT NULL COMMENT '付款节点ID', `payment_deadline` decimal(8, 0) NULL DEFAULT NULL COMMENT '支付期限', `payment_percentage` decimal(8, 0) NULL DEFAULT NULL COMMENT '支付比例', `invoice_percentage` decimal(8, 0) NULL DEFAULT NULL COMMENT '发票比例', `payment_amount` decimal(16, 2) NULL DEFAULT NULL COMMENT '支付金额', `payment_description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '付款条款', `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', `active_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '激活标识(1是 0否)', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', `create_by` bigint NULL DEFAULT NULL COMMENT '创建人', `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', `update_by` bigint NULL DEFAULT NULL COMMENT '更新人', `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`change_payment_id`) USING BTREE, KEY `idx_contract_change_id` (`contract_change_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='合同变更付款方式快照' ROW_FORMAT=DYNAMIC; ``` 说明:erp_contract_change_info 若与现有 erp_contract_info 字段有新增,需在快照表里同步增加一列,保证回写时能完整覆盖。erp_contract_change_payment_method 与 erp_contract_payment_method 字段保持一致,便于回写时逐字段拷贝。 --- ## 四、业务逻辑(通俗描述) ### 1. 合同变更列表页(类似合同信息) - **数据来源**:查表 erp_contract_change(可加 del_flag=0、按租户等条件)。 - **能力**:列表展示变更单号、关联合同、变更状态、申请时间、是否已回写等;支持按合同、状态、时间筛选。 - **新增**:点「新增」跳转到「合同变更申请」页(不传变更单 id,即新建)。 ### 2. 合同变更申请页 - **选合同**: - 下拉/选择器只查「可变更」的合同:`erp_contract_info` 中 `contract_status = '3'` 且 `active_flag = '1'`(已激活)。 - 若已有合同变更单 id(编辑/查看),则直接带出已选的 contract_id。 - **带出合同信息**: - 选好合同后,后端根据 contract_id 查 erp_contract_info、erp_contract_material、**erp_contract_payment_method**,返回前端。 - 前端把合同主信息、物料列表、**付款方式列表**展示在表单中,允许编辑。 - **保存/提交**: - **暂存**:插入或更新 erp_contract_change(change_status=1),并插入/更新 erp_contract_change_info 一条、erp_contract_change_material 多条、**erp_contract_change_payment_method 多条**。 - **提交审批**:同上,但 change_status=2,并走工作流(flow_status 由流程更新)。 ### 3. 合同变更历史 - **入口**:可在合同变更列表页增加「按合同查历史」;或在合同信息详情/列表中提供「变更历史」入口。 - **查询**:按 contract_id 查 erp_contract_change,按申请时间倒序,得到该合同下全部变更单。 - **展示**:变更单号、申请时间、变更状态、是否已回写、变更原因等;点某条可进详情,详情从 erp_contract_change_info、erp_contract_change_material、**erp_contract_change_payment_method** 取数据展示(只读即可)。 ### 4. 变更通过后回写合同表、合同物料表与合同付款方式表 - **触发时机**:流程审批通过且 change_status 更新为 '3'(可用)时(可在流程监听/回调里做)。 - **前置条件**:write_back_flag = '0',避免重复回写。 - **回写步骤**: 1. 用 erp_contract_change 的 contract_id 找到原合同。 2. **合同主信息**:用 erp_contract_change_info 中该 contract_change_id 的唯一一条记录,按字段更新 erp_contract_info(WHERE contract_id = 原 contract_id)。注意:不更新 contract_id、合同状态等由系统控制的字段,只更新业务可变更字段。 3. **合同物料**: - 删除:DELETE FROM erp_contract_material WHERE contract_id = 原 contract_id; - 插入:把 erp_contract_change_material 中该 contract_change_id 下的记录逐条 INSERT 到 erp_contract_material,contract_id 填原 contract_id,其他字段从快照表拷贝。 4. **合同付款方式**: - 删除:DELETE FROM erp_contract_payment_method WHERE contract_id = 原 contract_id; - 插入:把 erp_contract_change_payment_method 中该 contract_change_id 下的记录逐条 INSERT 到 erp_contract_payment_method,contract_id 填原 contract_id,其他字段从快照表拷贝。 5. 更新 erp_contract_change:write_back_flag='1', write_back_time=当前时间。 --- ## 五、小结 | 项目 | 说明 | | ----- | ------------------------------------------------------------------------------------------------------------------------ | | 可变更范围 | 仅合同状态=可用 且 激活标识=是 的合同 | | 列表与申请 | 列表查 erp_contract_change;申请页选合同后查 erp_contract_info + erp_contract_material + erp_contract_payment_method 带出并编辑,保存到变更主表+三张快照表 | | 历史 | 按 contract_id 查 erp_contract_change,详情用 change_info、change_material、change_payment_method | | 回写 | 审批通过后,用 change_info 更新 contract_info,用 change_material 全量替换该 contract_id 下物料,用 change_payment_method 全量替换该 contract_id 下付款方式,并标记已回写 | 按上述表结构与逻辑实现即可满足「只有合同激活后可变更、可查变更历史、变更后内容回写合同表、合同物料表与合同付款方式表」的需求。