# -*- coding: utf-8 -*- """票 07 客户侧接线脚本(常量 + Service create/update + 导入执行器聚合 + 测试构造点)""" import io, sys sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') # 1) CustomerConstants:字典分组常量(插在类尾 } 之前) p = 'crm-customer/src/main/java/com/crm/customer/constant/CustomerConstants.java' txt = open(p, encoding='utf-8').read() assert txt.rstrip().endswith('}') idx = txt.rstrip().rfind('}') block = ('\n /*-------- 字典分组编码(crm-dict 字段-分组绑定,202609 引用计数接线)--------*/\n\n' ' String DICT_GROUP_CUSTOMER_TYPE = "customer_type";\n\n' ' String DICT_GROUP_INDUSTRY = "industry";\n') txt = txt.rstrip()[:idx].rstrip() + '\n' + block + '}\n' open(p, 'w', encoding='utf-8').write(txt) print('CustomerConstants ok') # 2) CustomerServiceImpl:依赖 + create/update 随迁 + detail 字面量换常量 p = 'crm-customer/src/main/java/com/crm/customer/service/impl/CustomerServiceImpl.java' txt = open(p, encoding='utf-8').read() old = (' private final CustomerOwnershipWriter ownershipWriter;\n' ' private final CustomerNoGenerator noGenerator;\n') new = (' private final CustomerOwnershipWriter ownershipWriter;\n' ' private final CustomerNoGenerator noGenerator;\n' ' private final com.crm.dict.service.DictReferenceService dictReferenceService;\n') assert old in txt, 'svc fields anchor' txt = txt.replace(old, new, 1) old = (' public CustomerServiceImpl(CustomerOplogWriter oplogWriter,\n' ' CustomerDedupService dedupService,\n' ' DictQueryService dictQueryService,\n' ' CompanyLookupPort companyLookupPort,\n' ' CustomerOwnershipWriter ownershipWriter,\n' ' CustomerNoGenerator noGenerator) {\n') new = (' public CustomerServiceImpl(CustomerOplogWriter oplogWriter,\n' ' CustomerDedupService dedupService,\n' ' DictQueryService dictQueryService,\n' ' CompanyLookupPort companyLookupPort,\n' ' CustomerOwnershipWriter ownershipWriter,\n' ' CustomerNoGenerator noGenerator,\n' ' com.crm.dict.service.DictReferenceService dictReferenceService) {\n') assert old in txt, 'svc ctor anchor' txt = txt.replace(old, new, 1) old = ' this.noGenerator = noGenerator;\n }' new = ' this.noGenerator = noGenerator;\n this.dictReferenceService = dictReferenceService;\n }' assert old in txt, 'svc ctor body anchor' txt = txt.replace(old, new, 1) # create:save 成功后三字典字段入账 old = (' oplogWriter.write(entity.getId(), CustomerConstants.ACTION_CREATE,\n' ' "新增客户【" + entity.getCustomerName() + "】");\n' ' return savedResult(entity);') new = (' // 字典编码引用计数随迁(202609 拍板 1):三个字典字段入账,与创建同一事务\n' ' dictReferenceService.applyFieldChange(CustomerConstants.DICT_GROUP_CUSTOMER_TYPE,\n' ' null, entity.getCustomerType());\n' ' dictReferenceService.applyFieldChange(CustomerConstants.DICT_GROUP_INDUSTRY,\n' ' null, entity.getIndustryCode());\n' ' dictReferenceService.applyFieldChange(CustomerConstants.DICT_GROUP_INDUSTRY,\n' ' null, entity.getIndustryChildCode());\n' ' oplogWriter.write(entity.getId(), CustomerConstants.ACTION_CREATE,\n' ' "新增客户【" + entity.getCustomerName() + "】");\n' ' return savedResult(entity);') assert old in txt, 'svc create anchor' txt = txt.replace(old, new, 1) # update:拷贝前抓旧值,CAS 成功后随迁 old = (' // 先 diff(copy 会污染旧值)再全量覆盖;version 一并拷贝以驱动 CAS 比对\n' ' List changes = diffFields(exist, dto);') new = (' // 先 diff(copy 会污染旧值)再全量覆盖;version 一并拷贝以驱动 CAS 比对\n' ' // 字典字段旧值在拷贝前抓取(202609 拍板 1 引用计数随迁用)\n' ' String oldCustomerType = exist.getCustomerType();\n' ' String oldIndustryCode = exist.getIndustryCode();\n' ' String oldIndustryChildCode = exist.getIndustryChildCode();\n' ' List changes = diffFields(exist, dto);') assert old in txt, 'svc update pre-copy anchor' txt = txt.replace(old, new, 1) old = (' if (!updated) {\n' ' throw new BusinessErrorException(CustomerConstants.CODE_CAS_FAIL,\n' ' "客户已被他人修改,请刷新后重试");\n' ' }\n') new = (' if (!updated) {\n' ' throw new BusinessErrorException(CustomerConstants.CODE_CAS_FAIL,\n' ' "客户已被他人修改,请刷新后重试");\n' ' }\n' ' // 字典编码引用计数随迁(202609 拍板 1):全量覆盖语义,与编辑同一事务\n' ' dictReferenceService.applyFieldChange(CustomerConstants.DICT_GROUP_CUSTOMER_TYPE,\n' ' oldCustomerType, exist.getCustomerType());\n' ' dictReferenceService.applyFieldChange(CustomerConstants.DICT_GROUP_INDUSTRY,\n' ' oldIndustryCode, exist.getIndustryCode());\n' ' dictReferenceService.applyFieldChange(CustomerConstants.DICT_GROUP_INDUSTRY,\n' ' oldIndustryChildCode, exist.getIndustryChildCode());\n') assert old in txt, 'svc update post-cas anchor' txt = txt.replace(old, new, 1) # detail:字面量换常量 txt = txt.replace('getNames("customer_type",', 'getNames(CustomerConstants.DICT_GROUP_CUSTOMER_TYPE,', 1) txt = txt.replace('getNames("industry",', 'getNames(CustomerConstants.DICT_GROUP_INDUSTRY,', 1) open(p, 'w', encoding='utf-8').write(txt) print('CustomerServiceImpl ok') # 3) CustomerImportExecutor:依赖 + 聚合 delta + 收尾 batchApply p = 'crm-customer/src/main/java/com/crm/customer/task/CustomerImportExecutor.java' txt = open(p, encoding='utf-8').read() old = (' private final CustomerOwnershipWriter ownershipWriter;\n' ' private final CustomerNoGenerator noGenerator;\n') new = (' private final CustomerOwnershipWriter ownershipWriter;\n' ' private final CustomerNoGenerator noGenerator;\n' ' private final com.crm.dict.service.DictReferenceService dictReferenceService;\n') assert old in txt, 'exec fields anchor' txt = txt.replace(old, new, 1) # run 循环:声明聚合容器 old = (' // 文件内编号 → 客户 id(新增行落库后回填 / 命中行直接放),供联系人 sheet 挂接\n' ' Map customerIdByNo = new HashMap<>();\n') new = (' // 文件内编号 → 客户 id(新增行落库后回填 / 命中行直接放),供联系人 sheet 挂接\n' ' Map customerIdByNo = new HashMap<>();\n' ' // 字典编码引用变化聚合(202609 拍板 1 / ADR-0015:导入路径禁逐行增减,收尾一次 batchApply)\n' ' Map> dictDeltas = new HashMap<>();\n') assert old in txt, 'exec loop anchor' txt = txt.replace(old, new, 1) # 两个调用点传容器 old = 'Customer created = createCustomer(p, task, submitter);' new = 'Customer created = createCustomer(p, task, submitter, dictDeltas);' assert old in txt, 'exec create call anchor' txt = txt.replace(old, new, 1) old = 'if (applyUpdate(p, task)) {' new = 'if (applyUpdate(p, task, dictDeltas)) {' assert old in txt, 'exec update call anchor' txt = txt.replace(old, new, 1) # 客户 sheet 循环结束后冲账(联系人行与字典无关,越早冲账越少飘移窗口) old = ' // ===== 联系人 sheet:原子单元独立,失败不回滚主记录 =====' new = (' // 字典编码引用计数收尾冲账(客户 sheet 全部落库后一次 batchApply)\n' ' dictReferenceService.batchApply(dictDeltas);\n\n' ' // ===== 联系人 sheet:原子单元独立,失败不回滚主记录 =====') assert old in txt, 'exec flush anchor' txt = txt.replace(old, new, 1) # createCustomer 签名 + 入账 old = (' private Customer createCustomer(CustomerImportAnalyzer.RowPlan p,\n' ' CustomerImportTask task, AuthUser submitter) {') new = (' private Customer createCustomer(CustomerImportAnalyzer.RowPlan p,\n' ' CustomerImportTask task, AuthUser submitter,\n' ' Map> dictDeltas) {') assert old in txt, 'exec create sig anchor' txt = txt.replace(old, new, 1) old = (' customerMapper.insert(c);\n' ' oplogWriter.writeSystem(c.getId(), CustomerConstants.ACTION_CREATE,\n' ' "导入任务【" + task.getId() + "】新增客户");\n' ' return c;\n }') new = (' customerMapper.insert(c);\n' ' accumulateDictDelta(dictDeltas, CustomerConstants.DICT_GROUP_CUSTOMER_TYPE, null, c.getCustomerType());\n' ' accumulateDictDelta(dictDeltas, CustomerConstants.DICT_GROUP_INDUSTRY, null, c.getIndustryCode());\n' ' oplogWriter.writeSystem(c.getId(), CustomerConstants.ACTION_CREATE,\n' ' "导入任务【" + task.getId() + "】新增客户");\n' ' return c;\n }') assert old in txt, 'exec create body anchor' txt = txt.replace(old, new, 1) # applyUpdate 签名 + 旧值抓取 + 入账 old = (' private boolean applyUpdate(CustomerImportAnalyzer.RowPlan p, CustomerImportTask task) {\n' ' Customer c = p.getMatched();\n' ' CustomerImportRow r = p.getRow();\n' ' boolean changed = false;\n') new = (' private boolean applyUpdate(CustomerImportAnalyzer.RowPlan p, CustomerImportTask task,\n' ' Map> dictDeltas) {\n' ' Customer c = p.getMatched();\n' ' CustomerImportRow r = p.getRow();\n' ' boolean changed = false;\n' ' String oldCustomerType = c.getCustomerType();\n' ' String oldIndustryCode = c.getIndustryCode();\n') assert old in txt, 'exec update sig anchor' txt = txt.replace(old, new, 1) old = (' if (!changed) {\n' ' return false;\n' ' }\n' ' customerMapper.updateById(c);') new = (' if (!changed) {\n' ' return false;\n' ' }\n' ' accumulateDictDelta(dictDeltas, CustomerConstants.DICT_GROUP_CUSTOMER_TYPE, oldCustomerType, c.getCustomerType());\n' ' accumulateDictDelta(dictDeltas, CustomerConstants.DICT_GROUP_INDUSTRY, oldIndustryCode, c.getIndustryCode());\n' ' customerMapper.updateById(c);') assert old in txt, 'exec update body anchor' txt = txt.replace(old, new, 1) # 聚合助手(挂在 insertFail 前) old = ' private void insertFail(CustomerImportTask task, String sheetName, int rowNum,' new = (' /** 行级字典 delta 聚合:空白归一 null、相等不动,正加负减并入嵌套 map(收尾一次 batchApply) */\n' ' private static void accumulateDictDelta(Map> deltas,\n' ' String groupCode, String oldCode, String newCode) {\n' ' String oldCodeNorm = isBlank(oldCode) ? null : oldCode.trim();\n' ' String newCodeNorm = isBlank(newCode) ? null : newCode.trim();\n' ' if (oldCodeNorm == null ? newCodeNorm == null : oldCodeNorm.equals(newCodeNorm)) {\n' ' return;\n' ' }\n' ' if (oldCodeNorm != null) {\n' ' deltas.computeIfAbsent(groupCode, k -> new HashMap<>()).merge(oldCodeNorm, -1, Integer::sum);\n' ' }\n' ' if (newCodeNorm != null) {\n' ' deltas.computeIfAbsent(groupCode, k -> new HashMap<>()).merge(newCodeNorm, 1, Integer::sum);\n' ' }\n' ' }\n\n' ' private void insertFail(CustomerImportTask task, String sheetName, int rowNum,') assert old in txt, 'exec helper anchor' txt = txt.replace(old, new, 1) open(p, 'w', encoding='utf-8').write(txt) print('CustomerImportExecutor ok')