fix(mcp): 合同同步补续约关联字段 renewal_of_contract_id
m2.contract_new 实际存在 renew_source_id 列(新合同单向指向续签前合同), 但 ContractNew 实体未映射该列,ContractsSyncEntity 按'表无此列'将续约字段 从变更流与 current 报文中整体省略,导致下游 CRM 无法识别旧合同已被续约, R04 到期事项无法自动关闭。 - ContractNew 补映射 renew_source_id - SyncChangeSchemas.ContractChange 增加 renewal_of_contract_id 输出分量 - ContractsSyncEntity.toItem 输出稳定字符串 id;变更流与 current 共用,一次修好 - SyncEntitiesTest 新增回归:变更流/current 均输出、无续约来源时字段省略 测试:SyncEntitiesTest 9/9,全量 448/448 通过
This commit is contained in:
@@ -159,4 +159,10 @@ public class ContractNew implements Serializable {
|
||||
* 终止详细原因
|
||||
*/
|
||||
private String stopReason;
|
||||
|
||||
/**
|
||||
* 续签来源合同id(新合同指向其续签的旧合同,单向;旧合同行不反向引用)。
|
||||
* 2026-10-15 核对:表实际存在 renew_source_id 列,此前遗漏导致续约关联未进入同步报文。
|
||||
*/
|
||||
private Long renewSourceId;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ import java.util.stream.Collectors;
|
||||
* 字段口径(2026-09-14 字段核对):owner_user_id → sale_id(稳定 user.id);
|
||||
* customer_id 经 property_id 回查 properyt.customerId(金蝶客户 id);金额转分(amount_minor);
|
||||
* start_at/end_at 为带时区 ISO;version 由 modify_time 派生(表无 version 列,决策 1)。
|
||||
* 表无 currency / renewal_of_contract_id / renewal_result / deleted_at 列,按铁律"表里不存在的字段不造"省略,
|
||||
* 表无 currency / renewal_result 列,按铁律"表里不存在的字段不造"省略;
|
||||
* renewal_of_contract_id 由表列 renew_source_id 映射(2026-10-15 核对补入,此前按无列省略属误判),
|
||||
* 软删 tombstone 的 deleted_at 以 modify_time 充当。
|
||||
* 行级权限:Token 绑定人时走 PermissionService.applyContractRowFilter(与 contract.list.query 同一 ACL)。
|
||||
*/
|
||||
@@ -121,6 +122,7 @@ public class ContractsSyncEntity extends AbstractSyncEntity implements SyncEntit
|
||||
stableId(row.getPropertyId()), stableId(row.getSaleId()),
|
||||
row.getSaleName(),
|
||||
Integer.valueOf(1).equals(row.getExpireRemind()), row.getRemindDays(),
|
||||
stableId(row.getRenewSourceId()),
|
||||
ChangesEngine.iso(effective), mt, deleted ? ChangesEngine.iso(effective) : null));
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ final class SyncChangeSchemas {
|
||||
@JsonProperty("owner_name") String ownerName,
|
||||
@JsonProperty("expire_remind") boolean expireRemind,
|
||||
@JsonProperty("remind_days") Integer remindDays,
|
||||
@JsonProperty("renewal_of_contract_id") String renewalOfContractId,
|
||||
@JsonProperty("updated_at") String updatedAt,
|
||||
long version,
|
||||
@JsonProperty("deleted_at") String deletedAt) {
|
||||
|
||||
@@ -22,9 +22,12 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -227,6 +230,37 @@ class SyncEntitiesTest {
|
||||
assertEquals("2026-09-01T13:54:21+08:00", rows.get(0).item().get("deleted_at"), "软删行返回 tombstone");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contractsMapsRenewalSourceIdToWireField() {
|
||||
// 回归:m2.contract_new.renew_source_id 是续签关联列(新合同单向指向旧合同)。
|
||||
// 2026-10-15 前实体未映射该列,变更流与 current 报文均丢续约关系,
|
||||
// 下游 CRM 无法识别"旧合同已被续约",R04 事项永远不会自动关闭。
|
||||
ContractsSyncEntity entity = new ContractsSyncEntity(contractNewMapper, properytMapper, permissionService);
|
||||
ContractNew contract = new ContractNew();
|
||||
contract.setId(23L);
|
||||
contract.setContractNo("452542C007");
|
||||
contract.setContractStatus(2);
|
||||
contract.setPropertyId(1L);
|
||||
contract.setSoftDelete(0);
|
||||
contract.setRenewSourceId(22L);
|
||||
contract.setModifyTime(Timestamp.valueOf("2026-10-15 13:30:00"));
|
||||
// fetchCurrent 会原地 sort mapper 返回的列表,桩必须每次给可变新实例
|
||||
when(contractNewMapper.selectList(any())).thenAnswer(inv -> new ArrayList<>(List.of(contract)));
|
||||
when(properytMapper.selectBatchIds(anyList())).thenReturn(List.of());
|
||||
|
||||
List<SyncRow> rows = entity.fetchPage(null, 2000L, null, null, 3, null);
|
||||
assertEquals("22", rows.get(0).item().get("renewal_of_contract_id"), "变更流必须输出续约前合同");
|
||||
|
||||
List<Map<String, Object>> current = entity.fetchCurrent(List.of(23L), null);
|
||||
assertEquals(1, current.size());
|
||||
assertEquals("22", current.get(0).get("renewal_of_contract_id"), "current 接口必须同步输出续约前合同");
|
||||
|
||||
// 非续签合同的该列为 NULL:字段按 NON_NULL 整体省略,不得出现 null 值键
|
||||
contract.setRenewSourceId(null);
|
||||
List<SyncRow> plain = entity.fetchPage(null, 2000L, null, null, 3, null);
|
||||
assertFalse(plain.get(0).item().containsKey("renewal_of_contract_id"), "无续约来源时字段应省略");
|
||||
}
|
||||
|
||||
@Test
|
||||
void contractsRejectUnknownLegacyStatus() {
|
||||
ToolCallException error = assertThrows(ToolCallException.class,
|
||||
|
||||
Reference in New Issue
Block a user