From a61031512ca3fd6142409a68f87ec59fc9f9039b Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Wed, 2 Sep 2026 17:53:16 +0800 Subject: [PATCH] commit --- .lithe/.gitignore | 3 + .lithe/project.json | 4 + .lithe/run/generated.json | 1016 +++++++++++++++++ .lithe/toolchains/requirements.json | 29 + .project | 28 + .scratch/customer-module/REVIEW-HANDOFF.md | 206 ++++ .scratch/customer-module/issues/01-answer.md | 38 + .../issues/01-domain-context.md | 40 +- .scratch/customer-module/issues/02-answer.md | 176 +++ .../issues/02-customer-main-table.md | 179 ++- .scratch/customer-module/issues/03-answer.md | 94 ++ .../issues/03-ownership-views.md | 93 +- .scratch/customer-module/issues/04-answer.md | 87 ++ .../04-create-dedup-and-company-lookup.md | 87 +- .scratch/customer-module/issues/05-answer.md | 56 + .../issues/05-customer-stage.md | 58 +- .scratch/customer-module/issues/06-answer.md | 76 ++ .../issues/06-contacts-crud.md | 77 +- .scratch/customer-module/issues/07-answer.md | 86 ++ .../issues/07-detail-follow-oplog.md | 86 +- .scratch/customer-module/issues/08-answer.md | 97 ++ .../customer-module/issues/08-transfer.md | 98 +- .scratch/customer-module/issues/09-answer.md | 60 + .scratch/customer-module/issues/09-import.md | 61 +- .scratch/customer-module/issues/10-answer.md | 116 ++ .../issues/10-overdue-reminder-rule.md | 116 +- .scratch/customer-module/issues/11-answer.md | 53 + .../issues/11-saved-view-integration.md | 54 +- .scratch/customer-module/map.md | 102 +- .../tickets/01-module-skeleton.md | 16 + .../tickets/02-customer-crud-dedup.md | 19 + .../tickets/03-contact-subentity.md | 18 + .../tickets/04-workspaces-ownership.md | 24 + .../tickets/05-customer-stage-seam.md | 17 + .../tickets/06-detail-follow-oplog.md | 23 + .../tickets/07-customer-transfer.md | 19 + .../tickets/08-customer-import.md | 19 + .../tickets/09-overdue-reminder.md | 18 + .../tickets/10-saved-view-integration.md | 17 + .../tickets/11-opportunity-customer-wiring.md | 18 + .../tickets/12-prototype-alignment.md | 22 + .../tickets/13-api-documentation.md | 19 + .scratch/customer-module/tickets/README.md | 46 + .../demo/shot-12-13-create-result.png | Bin 72798 -> 61495 bytes .../demo/shot-12-14-detail-after-create.png | Bin 72798 -> 57726 bytes .../opportunity-e2e/opp-schema-migration.sql | 120 ++ .scratch/patch-naming-trap.md | 45 + .scratch/patch_ctx_53.md | 1 + .scratch/patch_map_53_55.md | 3 + .settings/org.eclipse.core.resources.prefs | 2 + .settings/org.eclipse.m2e.core.prefs | 4 + AGENTS.md | 46 + CONTEXT-MAP.md | 1 + crm-app/.classpath | 57 + crm-app/.factorypath | 157 +++ crm-app/.project | 34 + .../org.eclipse.core.resources.prefs | 4 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + crm-app/.settings/org.eclipse.jdt.core.prefs | 20 + crm-app/.settings/org.eclipse.m2e.core.prefs | 4 + crm-auth/.classpath | 57 + crm-auth/.factorypath | 157 +++ crm-auth/.project | 34 + .../org.eclipse.core.resources.prefs | 5 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + crm-auth/.settings/org.eclipse.jdt.core.prefs | 20 + crm-auth/.settings/org.eclipse.m2e.core.prefs | 4 + crm-base/.classpath | 57 + crm-base/.factorypath | 131 +++ crm-base/.project | 34 + .../org.eclipse.core.resources.prefs | 4 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + crm-base/.settings/org.eclipse.jdt.core.prefs | 20 + crm-base/.settings/org.eclipse.m2e.core.prefs | 4 + crm-customer/CONTEXT.md | 57 + crm-dict/.classpath | 57 + crm-dict/.factorypath | 132 +++ crm-dict/.project | 34 + .../org.eclipse.core.resources.prefs | 4 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + crm-dict/.settings/org.eclipse.jdt.core.prefs | 20 + crm-dict/.settings/org.eclipse.m2e.core.prefs | 4 + crm-file/.classpath | 57 + crm-file/.factorypath | 147 +++ crm-file/.project | 34 + .../org.eclipse.core.resources.prefs | 4 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + crm-file/.settings/org.eclipse.jdt.core.prefs | 20 + crm-file/.settings/org.eclipse.m2e.core.prefs | 4 + crm-lead/.classpath | 57 + crm-lead/.factorypath | 157 +++ crm-lead/.project | 34 + .../org.eclipse.core.resources.prefs | 4 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + crm-lead/.settings/org.eclipse.jdt.core.prefs | 20 + crm-lead/.settings/org.eclipse.m2e.core.prefs | 4 + crm-opportunity/.classpath | 57 + crm-opportunity/.factorypath | 157 +++ crm-opportunity/.project | 34 + .../org.eclipse.core.resources.prefs | 4 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + .../.settings/org.eclipse.jdt.core.prefs | 20 + .../.settings/org.eclipse.m2e.core.prefs | 4 + .../domain/entity/Opportunity.java | 7 +- crm-preference/.classpath | 57 + crm-preference/.factorypath | 131 +++ crm-preference/.project | 34 + .../org.eclipse.core.resources.prefs | 4 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + .../.settings/org.eclipse.jdt.core.prefs | 20 + .../.settings/org.eclipse.m2e.core.prefs | 4 + crm-rule/.classpath | 57 + crm-rule/.factorypath | 157 +++ crm-rule/.project | 34 + .../org.eclipse.core.resources.prefs | 5 + .../.settings/org.eclipse.jdt.apt.core.prefs | 4 + crm-rule/.settings/org.eclipse.jdt.core.prefs | 20 + crm-rule/.settings/org.eclipse.m2e.core.prefs | 4 + 118 files changed, 6245 insertions(+), 47 deletions(-) create mode 100644 .lithe/.gitignore create mode 100644 .lithe/project.json create mode 100644 .lithe/run/generated.json create mode 100644 .lithe/toolchains/requirements.json create mode 100644 .project create mode 100644 .scratch/customer-module/REVIEW-HANDOFF.md create mode 100644 .scratch/customer-module/issues/01-answer.md create mode 100644 .scratch/customer-module/issues/02-answer.md create mode 100644 .scratch/customer-module/issues/03-answer.md create mode 100644 .scratch/customer-module/issues/04-answer.md create mode 100644 .scratch/customer-module/issues/05-answer.md create mode 100644 .scratch/customer-module/issues/06-answer.md create mode 100644 .scratch/customer-module/issues/07-answer.md create mode 100644 .scratch/customer-module/issues/08-answer.md create mode 100644 .scratch/customer-module/issues/09-answer.md create mode 100644 .scratch/customer-module/issues/10-answer.md create mode 100644 .scratch/customer-module/issues/11-answer.md create mode 100644 .scratch/customer-module/tickets/01-module-skeleton.md create mode 100644 .scratch/customer-module/tickets/02-customer-crud-dedup.md create mode 100644 .scratch/customer-module/tickets/03-contact-subentity.md create mode 100644 .scratch/customer-module/tickets/04-workspaces-ownership.md create mode 100644 .scratch/customer-module/tickets/05-customer-stage-seam.md create mode 100644 .scratch/customer-module/tickets/06-detail-follow-oplog.md create mode 100644 .scratch/customer-module/tickets/07-customer-transfer.md create mode 100644 .scratch/customer-module/tickets/08-customer-import.md create mode 100644 .scratch/customer-module/tickets/09-overdue-reminder.md create mode 100644 .scratch/customer-module/tickets/10-saved-view-integration.md create mode 100644 .scratch/customer-module/tickets/11-opportunity-customer-wiring.md create mode 100644 .scratch/customer-module/tickets/12-prototype-alignment.md create mode 100644 .scratch/customer-module/tickets/13-api-documentation.md create mode 100644 .scratch/customer-module/tickets/README.md create mode 100644 .scratch/opportunity-e2e/opp-schema-migration.sql create mode 100644 .scratch/patch-naming-trap.md create mode 100644 .scratch/patch_ctx_53.md create mode 100644 .scratch/patch_map_53_55.md create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 crm-app/.classpath create mode 100644 crm-app/.factorypath create mode 100644 crm-app/.project create mode 100644 crm-app/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-app/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-app/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-app/.settings/org.eclipse.m2e.core.prefs create mode 100644 crm-auth/.classpath create mode 100644 crm-auth/.factorypath create mode 100644 crm-auth/.project create mode 100644 crm-auth/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-auth/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-auth/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-auth/.settings/org.eclipse.m2e.core.prefs create mode 100644 crm-base/.classpath create mode 100644 crm-base/.factorypath create mode 100644 crm-base/.project create mode 100644 crm-base/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-base/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-base/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-base/.settings/org.eclipse.m2e.core.prefs create mode 100644 crm-customer/CONTEXT.md create mode 100644 crm-dict/.classpath create mode 100644 crm-dict/.factorypath create mode 100644 crm-dict/.project create mode 100644 crm-dict/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-dict/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-dict/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-dict/.settings/org.eclipse.m2e.core.prefs create mode 100644 crm-file/.classpath create mode 100644 crm-file/.factorypath create mode 100644 crm-file/.project create mode 100644 crm-file/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-file/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-file/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-file/.settings/org.eclipse.m2e.core.prefs create mode 100644 crm-lead/.classpath create mode 100644 crm-lead/.factorypath create mode 100644 crm-lead/.project create mode 100644 crm-lead/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-lead/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-lead/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-lead/.settings/org.eclipse.m2e.core.prefs create mode 100644 crm-opportunity/.classpath create mode 100644 crm-opportunity/.factorypath create mode 100644 crm-opportunity/.project create mode 100644 crm-opportunity/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-opportunity/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-opportunity/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-opportunity/.settings/org.eclipse.m2e.core.prefs create mode 100644 crm-preference/.classpath create mode 100644 crm-preference/.factorypath create mode 100644 crm-preference/.project create mode 100644 crm-preference/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-preference/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-preference/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-preference/.settings/org.eclipse.m2e.core.prefs create mode 100644 crm-rule/.classpath create mode 100644 crm-rule/.factorypath create mode 100644 crm-rule/.project create mode 100644 crm-rule/.settings/org.eclipse.core.resources.prefs create mode 100644 crm-rule/.settings/org.eclipse.jdt.apt.core.prefs create mode 100644 crm-rule/.settings/org.eclipse.jdt.core.prefs create mode 100644 crm-rule/.settings/org.eclipse.m2e.core.prefs diff --git a/.lithe/.gitignore b/.lithe/.gitignore new file mode 100644 index 0000000..ad7dd23 --- /dev/null +++ b/.lithe/.gitignore @@ -0,0 +1,3 @@ +run/local.json +toolchains/local.json +**/*.tmp diff --git a/.lithe/project.json b/.lithe/project.json new file mode 100644 index 0000000..04c3952 --- /dev/null +++ b/.lithe/project.json @@ -0,0 +1,4 @@ +{ + "defaultRunConfiguration": "spring-boot.maven:crm-app", + "version": 1 +} \ No newline at end of file diff --git a/.lithe/run/generated.json b/.lithe/run/generated.json new file mode 100644 index 0000000..b074e0e --- /dev/null +++ b/.lithe/run/generated.json @@ -0,0 +1,1016 @@ +{ + "configurations": [ + { + "args": [], + "confidence": "native", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "application", + "extensions": { + "java": { + "source": "crm-app/src/main/java/com/crm/app/CrmAppApplication.java" + }, + "maven": { + "mainClass": "com.crm.app.CrmAppApplication", + "module": "crm-app" + } + }, + "id": "java-main:com.crm.app.CrmAppApplication", + "name": "CrmAppApplication", + "provider": "java.main", + "source": "crm-app/src/main/java/com/crm/app/CrmAppApplication.java", + "toolchains": { + "java": "project-jdk", + "maven": "project-maven" + } + }, + { + "args": [], + "confidence": "native", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "application", + "extensions": { + "java": { + "source": "crm-auth/src/main/java/com/crm/auth/AuthApplication.java" + }, + "maven": { + "mainClass": "com.crm.auth.AuthApplication", + "module": "crm-auth" + } + }, + "id": "java-main:com.crm.auth.AuthApplication", + "name": "AuthApplication", + "provider": "java.main", + "source": "crm-auth/src/main/java/com/crm/auth/AuthApplication.java", + "toolchains": { + "java": "project-jdk", + "maven": "project-maven" + } + }, + { + "args": [], + "confidence": "native", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "application", + "extensions": { + "java": { + "source": "crm-dict/src/main/java/com/crm/dict/DictApplication.java" + }, + "maven": { + "mainClass": "com.crm.dict.DictApplication", + "module": "crm-dict" + } + }, + "id": "java-main:com.crm.dict.DictApplication", + "name": "DictApplication", + "provider": "java.main", + "source": "crm-dict/src/main/java/com/crm/dict/DictApplication.java", + "toolchains": { + "java": "project-jdk", + "maven": "project-maven" + } + }, + { + "args": [], + "confidence": "native", + "cwd": ".", + "debug": { + "adapter": "jdwp" + }, + "disabled": false, + "env": {}, + "execution": "application", + "extensions": { + "maven": { + "module": "." + } + }, + "id": "current-file", + "name": "Current File", + "provider": "java.current-file", + "toolchains": { + "java": "project-jdk" + } + }, + { + "args": [ + "compose", + "-f", + "docker-compose.yml", + "up" + ], + "command": "docker", + "confidence": "declared", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "service", + "extensions": { + "compose": { + "file": "docker-compose.yml" + } + }, + "id": "compose.stack:compose up", + "name": "compose up", + "provider": "compose.stack", + "source": "docker-compose.yml", + "toolchains": {} + }, + { + "args": [], + "confidence": "declared", + "cwd": ".", + "debug": { + "adapter": "jdwp" + }, + "disabled": false, + "env": {}, + "execution": "service", + "extensions": { + "maven": { + "mainClass": "com.crm.app.CrmAppApplication", + "module": "crm-app" + } + }, + "id": "spring-boot.maven:crm-app", + "name": "crm-app", + "provider": "spring-boot.maven", + "source": "crm-app/pom.xml", + "toolchains": { + "java": "project-jdk", + "maven": "project-maven" + } + }, + { + "args": [ + "compose", + "-f", + "docker-compose.yml", + "up", + "minio" + ], + "command": "docker", + "confidence": "declared", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "service", + "extensions": { + "compose": { + "file": "docker-compose.yml", + "service": "minio" + } + }, + "id": "compose.service:minio", + "name": "minio", + "provider": "compose.service", + "source": "docker-compose.yml", + "toolchains": {} + }, + { + "args": [ + "compose", + "-f", + "docker-compose.yml", + "up", + "minio-init" + ], + "command": "docker", + "confidence": "declared", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "service", + "extensions": { + "compose": { + "file": "docker-compose.yml", + "service": "minio-init" + } + }, + "id": "compose.service:minio-init", + "name": "minio-init", + "provider": "compose.service", + "source": "docker-compose.yml", + "toolchains": {} + }, + { + "args": [ + "compose", + "-f", + "docker-compose.yml", + "up", + "mysql" + ], + "command": "docker", + "confidence": "declared", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "service", + "extensions": { + "compose": { + "file": "docker-compose.yml", + "service": "mysql" + } + }, + "id": "compose.service:mysql", + "name": "mysql", + "provider": "compose.service", + "source": "docker-compose.yml", + "toolchains": {} + }, + { + "args": [ + "compose", + "-f", + "docker-compose.yml", + "up", + "nginx" + ], + "command": "docker", + "confidence": "declared", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "service", + "extensions": { + "compose": { + "file": "docker-compose.yml", + "service": "nginx" + } + }, + "id": "compose.service:nginx", + "name": "nginx", + "provider": "compose.service", + "source": "docker-compose.yml", + "toolchains": {} + }, + { + "args": [ + "compose", + "-f", + "docker-compose.yml", + "up", + "redis" + ], + "command": "docker", + "confidence": "declared", + "cwd": ".", + "disabled": false, + "env": {}, + "execution": "service", + "extensions": { + "compose": { + "file": "docker-compose.yml", + "service": "redis" + } + }, + "id": "compose.service:redis", + "name": "redis", + "provider": "compose.service", + "source": "docker-compose.yml", + "toolchains": {} + }, + { + "args": [ + "run", + "build" + ], + "command": "npm", + "confidence": "declared", + "cwd": "tmp/china-division/package", + "disabled": false, + "env": {}, + "execution": "task", + "extensions": { + "npm": { + "manager": "npm", + "script": "build" + } + }, + "id": "npm.script:tmp/china-division/package/build", + "name": "build", + "provider": "npm.script", + "source": "tmp/china-division/package/package.json", + "toolchains": { + "runtime": "project-node" + } + }, + { + "args": [ + "run", + "csv" + ], + "command": "npm", + "confidence": "declared", + "cwd": "tmp/china-division/package", + "disabled": false, + "env": {}, + "execution": "task", + "extensions": { + "npm": { + "manager": "npm", + "script": "csv" + } + }, + "id": "npm.script:tmp/china-division/package/csv", + "name": "csv", + "provider": "npm.script", + "source": "tmp/china-division/package/package.json", + "toolchains": { + "runtime": "project-node" + } + }, + { + "args": [ + "run", + "fetch" + ], + "command": "npm", + "confidence": "declared", + "cwd": "tmp/china-division/package", + "disabled": false, + "env": {}, + "execution": "task", + "extensions": { + "npm": { + "manager": "npm", + "script": "fetch" + } + }, + "id": "npm.script:tmp/china-division/package/fetch", + "name": "fetch", + "provider": "npm.script", + "source": "tmp/china-division/package/package.json", + "toolchains": { + "runtime": "project-node" + } + }, + { + "args": [ + "run", + "format" + ], + "command": "npm", + "confidence": "declared", + "cwd": "tmp/china-division/package", + "disabled": false, + "env": {}, + "execution": "task", + "extensions": { + "npm": { + "manager": "npm", + "script": "format" + } + }, + "id": "npm.script:tmp/china-division/package/format", + "name": "format", + "provider": "npm.script", + "source": "tmp/china-division/package/package.json", + "toolchains": { + "runtime": "project-node" + } + }, + { + "args": [ + "run", + "json" + ], + "command": "npm", + "confidence": "declared", + "cwd": "tmp/china-division/package", + "disabled": false, + "env": {}, + "execution": "task", + "extensions": { + "npm": { + "manager": "npm", + "script": "json" + } + }, + "id": "npm.script:tmp/china-division/package/json", + "name": "json", + "provider": "npm.script", + "source": "tmp/china-division/package/package.json", + "toolchains": { + "runtime": "project-node" + } + }, + { + "args": [ + "run", + "precommit" + ], + "command": "npm", + "confidence": "declared", + "cwd": "tmp/china-division/package", + "disabled": false, + "env": {}, + "execution": "task", + "extensions": { + "npm": { + "manager": "npm", + "script": "precommit" + } + }, + "id": "npm.script:tmp/china-division/package/precommit", + "name": "precommit", + "provider": "npm.script", + "source": "tmp/china-division/package/package.json", + "toolchains": { + "runtime": "project-node" + } + }, + { + "args": [ + "run", + "test" + ], + "command": "npm", + "confidence": "declared", + "cwd": "tmp/china-division/package", + "disabled": false, + "env": {}, + "execution": "task", + "extensions": { + "npm": { + "manager": "npm", + "script": "test" + } + }, + "id": "npm.script:tmp/china-division/package/test", + "name": "test", + "provider": "npm.script", + "source": "tmp/china-division/package/package.json", + "toolchains": { + "runtime": "project-node" + } + } + ], + "generator": { + "fingerprint": "sha256:c3a4b1a72f53a9fd91457473dc3f94bfcfd59320ce7532f81fc2772967924660", + "inputs": { + "crm-app/pom.xml": "sha256:5dab7c63bd1ebac88806b775f7e8e920d81eb500d3c0040ef93486b7df8f20fe", + "crm-app/src/main/java/com/crm/app/CrmAppApplication.java": "sha256:5eecad74f14776e5f2f51eef0d0925dde9874d93da3fb76dfd8d7c3cebe2c809", + "crm-auth/pom.xml": "sha256:7ce7c34655abce46bfabad8f78a78aa3e229d667246debd50d93f0d66b37dcd1", + "crm-auth/src/main/java/com/crm/auth/AuthApplication.java": "sha256:b89cd3423ee3a8d9d2f29cb4bb0b0487c3b5bfad3690f53b23fa2578165b537a", + "crm-auth/src/main/java/com/crm/auth/config/AuthProperties.java": "sha256:5494038ac9326bf073ae8ea2283e9ef6acfcbcc45d1b14d2f367027b5246e50c", + "crm-auth/src/main/java/com/crm/auth/config/DataInitializer.java": "sha256:827da4020226cb1233c374687b0dd9883a95d5bdbde7e63cd9a4704f078fd637", + "crm-auth/src/main/java/com/crm/auth/config/DataScopeModuleStartupValidator.java": "sha256:75e4bd8a0dade6ac913e35fccb09c7e4eb822c950912c4d280eff6f41f7539cd", + "crm-auth/src/main/java/com/crm/auth/config/DtClientConfig.java": "sha256:2114d904117bad7c98342e78d70da32c056601c3b67981cd8bc2c4ad15b75b38", + "crm-auth/src/main/java/com/crm/auth/config/PermissionConfig.java": "sha256:1ceaaeca5d97dda255cd062a55c099aa321c7ece83a49e738cae40f614503599", + "crm-auth/src/main/java/com/crm/auth/config/SecurityConfig.java": "sha256:8ba086c26b3bf6e7800d268f5d8f5a39cedfc63c65a4acf3d1274285aae8b1bc", + "crm-auth/src/main/java/com/crm/auth/config/WebMvcConfig.java": "sha256:04437ccb4f7676460c1595a53666eec5d71234c3f685a7b142a0e3a4566843d0", + "crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java": "sha256:f6644b6e138df0f6804c39c0818145231bd91f1a8c14f49d87a03da137362b3e", + "crm-auth/src/main/java/com/crm/auth/controller/AuthController.java": "sha256:4cc4ffa3a8e185d4048e96a16083a6f8a9086d5ba9f3566c3897784a26907bc1", + "crm-auth/src/main/java/com/crm/auth/controller/DataScopeModuleController.java": "sha256:24707eb92b33e23c94fd8696da9bbe6542b6fdb4e4909f2ab754a7f36fbcab81", + "crm-auth/src/main/java/com/crm/auth/controller/DebugTokenController.java": "sha256:5136c1e73b156379c9c47ef408edc86a376e22cd828dabbcb525518994678251", + "crm-auth/src/main/java/com/crm/auth/controller/ResourceController.java": "sha256:6f673cf1d0f375421289c263fd10b11e0271174e7fb848fb44c9c0a2526742ea", + "crm-auth/src/main/java/com/crm/auth/controller/RoleController.java": "sha256:6593d289077c443868e00a91dc14b519c3bf841526e373c8a2fc194ab2e3cfdf", + "crm-auth/src/main/java/com/crm/auth/controller/SystemController.java": "sha256:34aacadc7d3b378decbed82f3fcf353ec4b20f0747f1c8e789a934278dc18818", + "crm-auth/src/main/java/com/crm/auth/domain/dto/AssignDeptDTO.java": "sha256:b5776452277628734f4f33cc28e324a8586e104a09ed3db26a66e46da50bb9dd", + "crm-auth/src/main/java/com/crm/auth/domain/dto/ConfigureAdminRoleDTO.java": "sha256:10bffb2bc91a82e25b6baeef34ed5afb368cf135fe85b9e02a35b2319aeff927", + "crm-auth/src/main/java/com/crm/auth/domain/dto/DataScopeModuleDTO.java": "sha256:878476525479e815a4b86d5ec98acfc82d487b73c1a4ba75ed4e6277786a5f19", + "crm-auth/src/main/java/com/crm/auth/domain/dto/DeptDTO.java": "sha256:3daf18744dc8283cfbb2377baa03dbc2eaafd213b78d2f0a0dd32a1dac22fcd3", + "crm-auth/src/main/java/com/crm/auth/domain/dto/DtSyncResultDTO.java": "sha256:4eed036bb4a10dc12e484779265d77e02630fe1c18cc2b2a26a041307abad1d9", + "crm-auth/src/main/java/com/crm/auth/domain/dto/LoginResultDTO.java": "sha256:cbe6e5be8ae5be4bb50dbc981dc1227f0f87f7d50833e59e7a85fb7219bc0e1a", + "crm-auth/src/main/java/com/crm/auth/domain/dto/ModuleScopeDTO.java": "sha256:c36ae06b69f587a80cc2555285e51c25458689472d9bdeec9fc6d8241e214db7", + "crm-auth/src/main/java/com/crm/auth/domain/dto/ResourceNodeDTO.java": "sha256:ec5d2776017b4922d1fbedc2dc552ddccd2f874cfd6f3f9c75998c1b187f3a8c", + "crm-auth/src/main/java/com/crm/auth/domain/dto/RoleDTO.java": "sha256:491bbb3adde4a8d6798944b4a80b1ed64600de36ce98c1c909a17d10f788ab7c", + "crm-auth/src/main/java/com/crm/auth/domain/dto/UserInfoDTO.java": "sha256:6a67ae5a2b9657558e1e3a2b748c6b9eb8c20306fbf92db2b7aa34f306d67892", + "crm-auth/src/main/java/com/crm/auth/domain/dto/UserListDTO.java": "sha256:ab7a067b5c475673aed6f1631a407213a248f7f5bbbc6a3881fc989d4a89e89d", + "crm-auth/src/main/java/com/crm/auth/domain/dto/UserStatsDTO.java": "sha256:b433c3973bb9b33309edefc6557ded824da14fe960da868935253d49c5db41a8", + "crm-auth/src/main/java/com/crm/auth/domain/entity/AuthIdentity.java": "sha256:2a4113c5fb355dbbed2561ef637227bd21f4b7166d28f882ecfc66dae3a53385", + "crm-auth/src/main/java/com/crm/auth/domain/entity/AuthUser.java": "sha256:ad7207250a7dc2e7c89a490fbd9c0b338dfa735016478c09f98c5472ecbfc4a5", + "crm-auth/src/main/java/com/crm/auth/domain/entity/SysDataScopeModule.java": "sha256:1bcf5ab53116c174305b6727ead8ca28a2e6c154cc0d8809409f965b085827ec", + "crm-auth/src/main/java/com/crm/auth/domain/entity/SysDept.java": "sha256:e77d491da5fa57932f3063c4804b075e4ebcea786d347d2ba7fc8aa65acc31ab", + "crm-auth/src/main/java/com/crm/auth/domain/entity/SysMenu.java": "sha256:98737d45a2e656679b29829a276c6055945768306c0d9d52fa66b5f5b67fe730", + "crm-auth/src/main/java/com/crm/auth/domain/entity/SysRole.java": "sha256:4cbb372cd644ca0bc94110cf0fa8e581f70df9c037c18e3bd36d4d2ff297b9e3", + "crm-auth/src/main/java/com/crm/auth/domain/entity/SysRoleDataScope.java": "sha256:02ef15b122b5171419a661621aa19d0f28b9503851394f02057bf1dde7c38fc6", + "crm-auth/src/main/java/com/crm/auth/domain/entity/SysRoleMenu.java": "sha256:3c5d6140eb799f20f36ef6d1be7ebbc9d9a2216e39d488725ddbaaf772a0716b", + "crm-auth/src/main/java/com/crm/auth/domain/entity/SysUserDept.java": "sha256:8e404f2fd225d2a7a2b3e92e5156ebe6bb33ba138ebe73c1b02a384259a24875", + "crm-auth/src/main/java/com/crm/auth/domain/entity/SysUserRole.java": "sha256:140a43f8fb49d49349aac112637bfde305e5dfab5bca06a637cf7e2e098f5257", + "crm-auth/src/main/java/com/crm/auth/domain/enums/ClientTypeEnum.java": "sha256:0eeffac88788a9b4dc06e0c38d0e4d9bf194cf4295dfd39e1c5d05b0bc780948", + "crm-auth/src/main/java/com/crm/auth/domain/enums/DataScopeEnum.java": "sha256:72d04be69e1867d9960e3868bc1f4fca67de6199158cb0a592fd85729ffbcf84", + "crm-auth/src/main/java/com/crm/auth/domain/enums/IdentityTypeEnum.java": "sha256:0bc939cbde791d36814f6014486ed9d1a183cb11a57e4d1ce776a1074d30e227", + "crm-auth/src/main/java/com/crm/auth/domain/enums/MenuType.java": "sha256:ee434b60a0ac59d05c5b6960cf034c3ab68f55697893aa2f22f06a82b8ba0246", + "crm-auth/src/main/java/com/crm/auth/domain/param/RoleParam.java": "sha256:c85bedbadaefd9b3b69ac3d4136ab4ffe15c1434fd86b19a321f0f01fa41fd27", + "crm-auth/src/main/java/com/crm/auth/domain/param/UserPageParam.java": "sha256:6ef871bdbf3a9056978171f8d728a2940c0190b32c4315e69d6cf91a8c0d8ff9", + "crm-auth/src/main/java/com/crm/auth/mapper/AuthIdentityMapper.java": "sha256:7ce45cc77ce4cdd857359b1712a2d4277bf3f219b11adfa8bd80c5ad4cde856e", + "crm-auth/src/main/java/com/crm/auth/mapper/AuthUserMapper.java": "sha256:f171120d0e519963f359d582ee80ef2507c26b095bcaca4424df6d4013de090f", + "crm-auth/src/main/java/com/crm/auth/mapper/SysDataScopeModuleMapper.java": "sha256:ece2a69d2c2326dabc10f0dae09c45d50a4c571fb64d2b6804c89726924a1ddb", + "crm-auth/src/main/java/com/crm/auth/mapper/SysDeptMapper.java": "sha256:c1b084fe7470db804a7e7acce58795e8b3c648cb7cf0e1eb39cd55cc6fdc1e8f", + "crm-auth/src/main/java/com/crm/auth/mapper/SysMenuMapper.java": "sha256:4379286b74cae51fdfa4a401740868c33aad59139b9d0d44ac5c69b04942b627", + "crm-auth/src/main/java/com/crm/auth/mapper/SysRoleDataScopeMapper.java": "sha256:0d4a08f71cb8d3d39378c12042b13ce48d5d3788c46e842ea2ccf68f5ef26676", + "crm-auth/src/main/java/com/crm/auth/mapper/SysRoleMapper.java": "sha256:f353d96b05e4037d3b2e6b5f093ad7e42570c5aad8651e436a86e9680c1eef21", + "crm-auth/src/main/java/com/crm/auth/mapper/SysRoleMenuMapper.java": "sha256:6117dccd0cc62c9162cf8124387fac1a5dc37373676240bff4688abaff5e5b61", + "crm-auth/src/main/java/com/crm/auth/mapper/SysUserDeptMapper.java": "sha256:96c50f87ea592a36d13fd7bc45c4731b04457c89b688d397356aad9167eee75f", + "crm-auth/src/main/java/com/crm/auth/mapper/SysUserRoleMapper.java": "sha256:111aac60f0630fd2140bd4d8f0a64197909c2d27d92f235a507cc04006d4ebd5", + "crm-auth/src/main/java/com/crm/auth/security/ApiPermissionCache.java": "sha256:2de1a5a139a939bb693e21a5933df08e597d819a2f07335fd7a12b4c8a19074b", + "crm-auth/src/main/java/com/crm/auth/security/ApiPermissionInterceptor.java": "sha256:46b65181f6b4a71cdca7553f5e26fb0606d6f5cfb27ae3972e1ea25923936771", + "crm-auth/src/main/java/com/crm/auth/security/ApiPermissionRule.java": "sha256:0066e2d9349a98093e6429e373771f50975c399d7285481a1551a3e5cca5e19e", + "crm-auth/src/main/java/com/crm/auth/security/AuthLoginUser.java": "sha256:09723717897aaa556ca4436e8745ea2007e9783889b81f8a2369f1dd193898a4", + "crm-auth/src/main/java/com/crm/auth/security/DataScopeInterceptor.java": "sha256:9f532aa062f9a43857db7bec4f3d9799fa34a1eca1d8717ce45e9f6e3aca368f", + "crm-auth/src/main/java/com/crm/auth/security/DataScopeTables.java": "sha256:a8719b5fe729a518219accbd528313fc45ce9ba1166ccab4c74250948237ce52", + "crm-auth/src/main/java/com/crm/auth/security/JwtAuthenticationFilter.java": "sha256:24b94b166f608071e0e4376adfc27024f4be0da0601337c84d5118e6cb24d770", + "crm-auth/src/main/java/com/crm/auth/security/JwtCodec.java": "sha256:25206c4213511a1702950a1d12d1a37e0b3cc290a1021facca2ae52b25066bd4", + "crm-auth/src/main/java/com/crm/auth/security/PermissionGrant.java": "sha256:654333dde9eff14f978568eb91f6ea9328c373b2fa1864e346c0aae995d88627", + "crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java": "sha256:e5a5a87bbd2c903400fb1d08ff0e4f9189da624b6caa01f03c6bfb859514c8a9", + "crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java": "sha256:48a2ef64004bd9014d7aa578889054323928055d20d3e3abae07155e7972fa7e", + "crm-auth/src/main/java/com/crm/auth/security/PreLoginSession.java": "sha256:31d2c3128dc85c311478162cd851561c89cebfa7dd4d46a3bdf4a37f3c302047", + "crm-auth/src/main/java/com/crm/auth/security/PreLoginTicketStore.java": "sha256:db569e72eed070fa7cd9baa03fab72d5f455036bd2e05de2a12a1176d95355e9", + "crm-auth/src/main/java/com/crm/auth/security/SecurityExceptionHandlers.java": "sha256:3dabb81ec7204f3e29562ac97c5fa259935ae47d9460e8dd26fe5ff07c72aff3", + "crm-auth/src/main/java/com/crm/auth/security/SessionStore.java": "sha256:dd5429a4f6bdaafaea807cccf0568b0629e59f18d40847f5612692ef245bab23", + "crm-auth/src/main/java/com/crm/auth/security/impl/JwtCodecImpl.java": "sha256:d4ec6de488fb4401102071cacdd2fa124f2232776310a1e92e84a50af3589cc2", + "crm-auth/src/main/java/com/crm/auth/security/impl/PreLoginTicketStoreImpl.java": "sha256:0d962c5eaeba5d56a3069fd4f70839324062d5d59b48b9b3179ed392938eab6f", + "crm-auth/src/main/java/com/crm/auth/security/impl/SessionStoreImpl.java": "sha256:4d957eb84fef1b8a6b125a2a1c869a0fa471bfb2bebb326b32251a3ce8760800", + "crm-auth/src/main/java/com/crm/auth/service/DeptTreeCache.java": "sha256:d6ae824a5ed6d36f40b6bc408894892571efa9416f864b092131791bdba00bcd", + "crm-auth/src/main/java/com/crm/auth/service/IAuthIdentityService.java": "sha256:0d630acf9099062312832ac736527c8a68ff6756796635b504d7c16abecfffb6", + "crm-auth/src/main/java/com/crm/auth/service/IAuthService.java": "sha256:cd6461e82062614575dccd0252df9ff858c21b00c8a1e03fde4a8df95c3958f9", + "crm-auth/src/main/java/com/crm/auth/service/IAuthUserService.java": "sha256:61f7ac06cfd53ff35e3caefa7bc503e2fc948d76af3e80d549b48bb5fe485667", + "crm-auth/src/main/java/com/crm/auth/service/IDtOrgSyncService.java": "sha256:bc66f18ba85bebab7065a32b188eccc9699c7e736ab1884db832ec0d32363936", + "crm-auth/src/main/java/com/crm/auth/service/IResourceService.java": "sha256:f44361a526b0ef34d661e807379338bccbfe74cdd4ddee8a32c782446e9bceba", + "crm-auth/src/main/java/com/crm/auth/service/ISysDeptService.java": "sha256:c5167638bac3c3f8e3d9477fbb01cd90ae70e7a6320f39a8189a7bab237a9613", + "crm-auth/src/main/java/com/crm/auth/service/ISysMenuService.java": "sha256:0f53ab354c9b550bbb29d773a294898ae079c4976c613d978b46fda97ba73aec", + "crm-auth/src/main/java/com/crm/auth/service/ISysRoleService.java": "sha256:03f213da8fed5ff893a8f97670a5d04d2bdaac5b5bb1a10905f19a30953101f2", + "crm-auth/src/main/java/com/crm/auth/service/IUserQueryService.java": "sha256:4ca9afac23ecf8d57fbb464807675db1afeb70493416e7892e8ece77eae1b33c", + "crm-auth/src/main/java/com/crm/auth/service/RoleScopeStore.java": "sha256:58d2c46dea737a59549c78e5185bca1ecdfc0cb3bf869bcbd2718178e1175557", + "crm-auth/src/main/java/com/crm/auth/service/client/CorpAccessTokenProvider.java": "sha256:c526021ec57507b768054738def64f09a28fbb7d80b842488be78267331fc8d7", + "crm-auth/src/main/java/com/crm/auth/service/client/CorpAccessTokenProviderImpl.java": "sha256:c5b80cbaa5a915f44cbb97cef774089a26902927d045fea238deefd27253f20d", + "crm-auth/src/main/java/com/crm/auth/service/client/DingTalkAuthClient.java": "sha256:22b06f76353ee0a77d98d0cd78d1689ba25461f3433a3dcc871aa1395bc7b19e", + "crm-auth/src/main/java/com/crm/auth/service/client/ThirdPartyAuthClient.java": "sha256:c53108c4a26914e5219a8432e0f1b273c581d4145349f940df6c6fafc1783588", + "crm-auth/src/main/java/com/crm/auth/service/client/ThirdPartyAuthClientFactory.java": "sha256:3135dfc0626795397d5d6c2e0331e919ec1259d6d2aef7d0618e974d040ac59b", + "crm-auth/src/main/java/com/crm/auth/service/client/ThirdPartyUserInfo.java": "sha256:f50c8feeb2b5651b1b2cda52f8d18be849cd0627fe75ea5ef4298e5c6c3630ac", + "crm-auth/src/main/java/com/crm/auth/service/impl/AuthIdentityServiceImpl.java": "sha256:3379660c660604b107c54f2bd94cb69e069885ef2a1e05540f1e82c7cbdee0cd", + "crm-auth/src/main/java/com/crm/auth/service/impl/AuthServiceImpl.java": "sha256:f24fe4e0571f1e89073bc6887d35fb65107c78bbea2aa59bffa2692bf74910fd", + "crm-auth/src/main/java/com/crm/auth/service/impl/AuthUserServiceImpl.java": "sha256:e0b74ee4d14c002963ccc026799f6917f15f247351c57756edab9208577fe0cb", + "crm-auth/src/main/java/com/crm/auth/service/impl/DtOrgSyncServiceImpl.java": "sha256:66096df60d69d20526ecf41fef02c3eb073a83a037753bcd70882a9adf5d5da5", + "crm-auth/src/main/java/com/crm/auth/service/impl/PermissionSeederImpl.java": "sha256:5680823d212d9677db1efb76306bc11ccbeaa09bc804213a6acdb9ace7e47b39", + "crm-auth/src/main/java/com/crm/auth/service/impl/ResourceServiceImpl.java": "sha256:55b06def16cdb0c28d16f80eb0e7581be3694af23a05148de47fb2512cb554f9", + "crm-auth/src/main/java/com/crm/auth/service/impl/RoleScopeStoreImpl.java": "sha256:1f0c8b60f4461d52f1a0dd18135a6748198aa2848ec315854a8c775317845f18", + "crm-auth/src/main/java/com/crm/auth/service/impl/SysDeptServiceImpl.java": "sha256:31fb306eb5fc1e90b005e43c109bfb9c2e2b527c023febc403d1c9fcc0abfd3b", + "crm-auth/src/main/java/com/crm/auth/service/impl/SysMenuServiceImpl.java": "sha256:4b01585e3796c1a95404d3eea9aa223adb43dfe55e342dfa0be42cd93c07be26", + "crm-auth/src/main/java/com/crm/auth/service/impl/SysRoleServiceImpl.java": "sha256:03020ef55d089dd27529194dd6f9a4d4d7d4384014af10ff41e1eccc203cbfa4", + "crm-auth/src/main/java/com/crm/auth/service/impl/UserQueryServiceImpl.java": "sha256:0264b66f6bdbda6f08b5c1f9607889fd9083fd40e457226752a44ad1145fc0fc", + "crm-auth/src/test/java/com/crm/auth/security/ApiPermissionInterceptorTest.java": "sha256:ca9d86c1741bf67c8fdeebfeeaab946a18fb91ba0ee94d26b21efab128b6f964", + "crm-auth/src/test/java/com/crm/auth/security/JwtAuthenticationFilterTest.java": "sha256:ad8e5fca1105639edf3430122bd4e8d0f242b80e201592adfcf75adcbee961ab", + "crm-auth/src/test/java/com/crm/auth/security/SecurityExceptionHandlersTest.java": "sha256:081741d0bf2cbf83de018e27121df200341170fef0455ce0d9554583c87fdda2", + "crm-auth/src/test/java/com/crm/auth/security/impl/JwtCodecImplTest.java": "sha256:4e0876fd6a3e830f988d71a40a15de8c632cc90a0efb109fc038a8dcf95ac1f6", + "crm-auth/src/test/java/com/crm/auth/security/impl/PreLoginTicketStoreImplTest.java": "sha256:4a53ad8689564345a9e39617cf230d9778dd0e26ae60e56570bd8237231fc602", + "crm-auth/src/test/java/com/crm/auth/security/impl/SessionStoreImplTest.java": "sha256:fea08df16d6436f08a2fa3d070d652fb002cf3ab14d1022d5ff4a23a363893a9", + "crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java": "sha256:f000607a7c0287dccb7472ea0a78cf4a158f0eabb660db1cad2db35f9141452f", + "crm-auth/src/test/java/com/crm/auth/security/scope/TestOwnedData.java": "sha256:57bf431ac3272ce22dae7cebad65386283292d7e93de191450580eed19a8382e", + "crm-auth/src/test/java/com/crm/auth/security/scope/TestOwnedDataMapper.java": "sha256:11b1963da8ee4034fc7a5bf2faaac13e74a5cae5cb2839055ece4d307f112218", + "crm-auth/src/test/java/com/crm/auth/service/client/CorpAccessTokenProviderImplTest.java": "sha256:a60e9a309dfb09f7466eccddcc8ff9b686595a37f57eeea31193f84af488d6fa", + "crm-auth/src/test/java/com/crm/auth/service/client/DingTalkAuthClientTest.java": "sha256:8aecb8c649f9442fe5a779f129fcbddbbfe4fe11f6bd08d7cd30e2ac75628808", + "crm-auth/src/test/java/com/crm/auth/service/client/ThirdPartyAuthClientFactoryTest.java": "sha256:df3add282ffa31f883e154bb675911a8f80476cc93c2023957ccf846410b3928", + "crm-auth/src/test/java/com/crm/auth/service/impl/AuthServiceImplTest.java": "sha256:0a9a9f8b3d9246ffcf65dde660b3e83bb4734ce0b7b00ffbba34a153b55d1188", + "crm-auth/src/test/java/com/crm/auth/service/impl/AuthUserServiceImplTest.java": "sha256:970ad171bb781bf1e483352a02792342a5cd4bc0e246a00fd87fe3589d882ed1", + "crm-auth/src/test/java/com/crm/auth/service/impl/DtOrgSyncServiceImplTest.java": "sha256:eebb0677934e558e53404da1f588d27639ded85c69433540f383838bf1a007e3", + "crm-auth/src/test/java/com/crm/auth/service/impl/PermissionSeederImplTest.java": "sha256:bc6a2b3bfbb2f6aca414ca394382bdd951aa5a57f3c5fe4da15b200df1400ce6", + "crm-auth/src/test/java/com/crm/auth/service/impl/ResourceServiceImplTest.java": "sha256:86da176046dd963d20b890019091568a5f5a8c3c731ba1cf4d09ceecfc467627", + "crm-auth/src/test/java/com/crm/auth/service/impl/SysDeptServiceImplTest.java": "sha256:6ab25e66138f38ac51615d8876f00c5127be5308065a1f91d37ae0abcf6d37aa", + "crm-auth/src/test/java/com/crm/auth/service/impl/SysMenuServiceImplTest.java": "sha256:2ce11e37b9eed47625b47fb720657c964ccfd23d9b7b3e7ad46483174797770b", + "crm-auth/src/test/java/com/crm/auth/service/impl/SysRoleServiceImplTest.java": "sha256:e1e04143a64c2f04a8c5f6e2489418b44f871970cf6df70de6a431957b71b546", + "crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java": "sha256:292be9e7eab3e3409dd3859f461f2aa29c345266c694fd50906ef4b537707898", + "crm-base/pom.xml": "sha256:2f80c6cc066bf98bf36ef7bcb731f295f65320c13b1ec74f0abfb232394f957a", + "crm-base/src/main/java/com/crm/base/advice/GlobalDataBindingAdvice.java": "sha256:a37eefea31c33872876d6bbcb5d1315a5f965076d34eb376b8ece3d6ae8aa4bd", + "crm-base/src/main/java/com/crm/base/advice/GlobalExceptionHandlerAdvice.java": "sha256:75e1b52c8e00968df65d017518de90014ce2dd22913f14f6f210f4cb834864fb", + "crm-base/src/main/java/com/crm/base/annotation/DataScope.java": "sha256:9dea657d14d4e956cf12b476c36f433475bdda070770f842afd71193e59bcba6", + "crm-base/src/main/java/com/crm/base/config/CorsConfig.java": "sha256:59fc61e917c6aefff3d6cc754962d8f7e581559ac08d991a1e02a297821d3c05", + "crm-base/src/main/java/com/crm/base/config/CrmSqlInjector.java": "sha256:354b030e8dfecbc590c9ac92c71a771591cd5e8a3ef74d40623bbd31ec8df00e", + "crm-base/src/main/java/com/crm/base/config/CustomIdGenerator.java": "sha256:7b02281892a1599783e213e0761890cf6df3a9b5776d8059eca8019068f185f7", + "crm-base/src/main/java/com/crm/base/config/InsertBatchOnDuplicateKeyUpdate.java": "sha256:a41fdaf6b6dbca65e0930c20f825b2ba36adfe3732838df683bb8497a6dd0691", + "crm-base/src/main/java/com/crm/base/config/JacksonConfig.java": "sha256:4cfa99e884817e98aab9d8e44232b53b5f58d0bc36f5c1ac26f5074e421604cb", + "crm-base/src/main/java/com/crm/base/config/Knife4jConfig.java": "sha256:27edd107207b90d9ae67ca409f169a155da376b2016a11867db8145125cd168b", + "crm-base/src/main/java/com/crm/base/config/MetaObjectFillHandler.java": "sha256:067f8381cf7509c8a890b18b504cce4515029e2b2f571399e7bc5208830e7758", + "crm-base/src/main/java/com/crm/base/config/MybatisPlusConfig.java": "sha256:4373d08df94bba92e821c802aa6949145279887630bc1b688e60bd5e5fde5b96", + "crm-base/src/main/java/com/crm/base/config/RedisConfig.java": "sha256:f4d19db28a5b95e775782f27e1363272091232815b3265cc28dcefe3632bcf74", + "crm-base/src/main/java/com/crm/base/config/ScheduledTaskProperties.java": "sha256:e28ba32d20979fff97050983bea5f3011c3b8701b7949cca28ee1e63dbd41ce5", + "crm-base/src/main/java/com/crm/base/config/SnowflakeIdWorker.java": "sha256:274e02ed875d57b53e973463323bb4cd31f10958adb621471e8697e3b44ff346", + "crm-base/src/main/java/com/crm/base/config/SnowflakeProperties.java": "sha256:b449ff32a40b502575ffecc092285415476c08a1985921070ff89ace8d6424f3", + "crm-base/src/main/java/com/crm/base/constant/CommonConstants.java": "sha256:02af0380cfc77239577f476c27dbd976757f82a111bee0b4231aebaf85488c67", + "crm-base/src/main/java/com/crm/base/domain/dto/BaseDTO.java": "sha256:0eb8e6db2f762a2b049cca069d8b552582e0001fda2bba5aacd643cf150627c0", + "crm-base/src/main/java/com/crm/base/domain/dto/ButtonSeed.java": "sha256:d6ad979da4f3847039de131d8ed51c08702eb5e415e9f9399edeae1bc702cfff", + "crm-base/src/main/java/com/crm/base/domain/dto/PermissionModuleDescriptor.java": "sha256:642edb65c706fc6c962392bc55f97b9c95cdd53b895949e5a4316e3f58069526", + "crm-base/src/main/java/com/crm/base/domain/entity/BaseEntity.java": "sha256:4db036f4aecb2c20237588bf5d41b63f5d3031dbf7118f88d211ead709da195a", + "crm-base/src/main/java/com/crm/base/domain/entity/OwnedEntity.java": "sha256:4b4df5ba6f1e23c5af1a451f46fa8cb2e1cca300dd45b142a40ebf31f194f9b1", + "crm-base/src/main/java/com/crm/base/domain/enums/HasValueEnum.java": "sha256:b360ed23014d014e12d68b00b87c19e4eeee69e50d75fa1552f9050edd325ee8", + "crm-base/src/main/java/com/crm/base/domain/enums/StatusEnum.java": "sha256:1a83948e7a1889773765d74000b8ed73d379a963ec428c63caaea724d795d7cb", + "crm-base/src/main/java/com/crm/base/domain/exception/BusinessErrorException.java": "sha256:760e64cafad056ffb8a708d4e659e43b8b9ace8402428d8210fc86403c83f66b", + "crm-base/src/main/java/com/crm/base/domain/exception/MissingParameterException.java": "sha256:bc13402c89ad61ac9c961afe1664b30060a3773db025351e9019b6d95c05cffd", + "crm-base/src/main/java/com/crm/base/domain/exception/PermissionErrorException.java": "sha256:b3dbea00bca42651e8b0826613713bf8f82f4f1b4f8013715c423a967d949a27", + "crm-base/src/main/java/com/crm/base/domain/exception/ResourceNotExistException.java": "sha256:d470d6a6ebcaa7d197b2913ebaf88c264c7f10638654fb9bd759ea3223c89288", + "crm-base/src/main/java/com/crm/base/domain/param/BaseParam.java": "sha256:e9426aba17a6db8ff9bbfd59de99cda274922809fa6606aa306b100ecff41707", + "crm-base/src/main/java/com/crm/base/domain/result/BatchResult.java": "sha256:353dcf10a9d7fa4562a2928397cc1dbb12129705ffb2fc0618141a26c59129e7", + "crm-base/src/main/java/com/crm/base/domain/result/BatchRunner.java": "sha256:768c018a4ac27d2cf951d23303b8091cfe733592bf5ed82a400911a784861bf5", + "crm-base/src/main/java/com/crm/base/domain/result/PageResult.java": "sha256:c2d1a92ec1735a66a6a2032c1b55cf4e8b81491b36492eb3de225e5f303b6de3", + "crm-base/src/main/java/com/crm/base/domain/result/Result.java": "sha256:1c921e5f795b82a70cb75af51f96c9191b9172e03115110526de060683532415", + "crm-base/src/main/java/com/crm/base/domain/result/ResultCodeEnum.java": "sha256:51d4b5dfc88c6354569b9402eefd5f3929e4a7d1a737a2a8cfc45373986281e0", + "crm-base/src/main/java/com/crm/base/filter/TraceIdFilter.java": "sha256:1749c28d67edc19331ead909d8a2c59e1faeab309e83c9ebcc122cbf5dbf668c", + "crm-base/src/main/java/com/crm/base/mapper/CrmBaseMapper.java": "sha256:de3abda85e7c66ff73a6017c9180fba7d6a63f53908b901bc37e8b45450d5ab2", + "crm-base/src/main/java/com/crm/base/security/DataOwnership.java": "sha256:8e5135079f87b15099718f45ff22cd8355221927f39714e0b305326b5badde7b", + "crm-base/src/main/java/com/crm/base/security/DataScopeLevel.java": "sha256:c6c8f689d6a8d37be995cae05d8aa84b2e5d320e189a3ca195560383c6da8d49", + "crm-base/src/main/java/com/crm/base/security/DataVisibility.java": "sha256:cdba5e06184cd3090c2bd3b7fed7f1ab90c0d85299548570477a2f3603d814f4", + "crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java": "sha256:2a22348173d420761f99cbbe5b27fd65caa7a91f078c66e2fa6978eecbfe9258", + "crm-base/src/main/java/com/crm/base/security/LoginUser.java": "sha256:2b2df2bb043d68703b9c9d05ffc7a77e46eb1a468c7eae74b99f8ac055e8bb64", + "crm-base/src/main/java/com/crm/base/security/SecurityUtils.java": "sha256:ad9476371a8469491b5a1cbcb05d4f9c0f09f2db89820c62c39669b50b97c9e9", + "crm-base/src/main/java/com/crm/base/security/VisibilityScope.java": "sha256:54c8cc47a9eecf899ed1287c56ac7c204bd3b7595a97a49321abb1d478a15a43", + "crm-base/src/main/java/com/crm/base/service/IBaseService.java": "sha256:3339ff5f136dedceff504b808d92901435a84941ea202910a277db1d9a2f02cd", + "crm-base/src/main/java/com/crm/base/service/PermissionSeeder.java": "sha256:5199eb32f326594f5be9d8ea7cfcb4bb27c25fc4fdaec9f67cc8fef459111ed7", + "crm-base/src/main/java/com/crm/base/service/impl/BaseServiceImpl.java": "sha256:58263984a21beca1767f4e47d4461e5871961bbb079e26be56ecff3c7bd88e97", + "crm-base/src/main/java/com/crm/base/utils/AssertUtils.java": "sha256:248a479ac5b47360d95862c1c689b9377b95957dbbd50c02fd370c7d5d4690f1", + "crm-base/src/main/java/com/crm/base/utils/BeanCopyUtils.java": "sha256:7eacd6401558e129804b705b5ae8507dcc322918fa41808d78cafcc66daa821c", + "crm-base/src/main/java/com/crm/base/utils/EnumUtils.java": "sha256:01887e4e14b9c1d2717e885326a79ac940603f1464bbdfb137b5e0cb1009a681", + "crm-base/src/main/java/com/crm/base/utils/ExcelUtil.java": "sha256:774e1bf9facfec300408f53075a3eca44dc297c017edd1ae52bced199420589e", + "crm-base/src/main/java/com/crm/base/utils/PageConverter.java": "sha256:5f442a2131dbe380fe315259802852b0fb202b5946c37e218dfb425eb0882a1b", + "crm-base/src/main/java/com/crm/base/utils/ServletUtils.java": "sha256:ad1ace3d6185344755dd49bf98b5886896d7c46efe5e40f5dd3f3d382e468152", + "crm-base/src/main/java/com/crm/base/utils/TreeUtils.java": "sha256:3568805ded8371718e6314fccf189c5c593a6fe0100023d393feb9c214bc9058", + "crm-base/src/test/java/com/crm/base/advice/GlobalDataBindingAdviceTest.java": "sha256:3658b21e2a40ebce7b7d582f205d13476eda4a46c1447da27a41a98e58973709", + "crm-base/src/test/java/com/crm/base/domain/result/BatchResultTest.java": "sha256:6f52069e873511fe5fbdabdf4a807908f6c3d25289a24edbfbcaac50d96837cd", + "crm-base/src/test/java/com/crm/base/domain/result/BatchRunnerTest.java": "sha256:9924e321f2872b71cb32151b9022cd1b9ef6a23028c5ffdc9aa2ac76c7e214d8", + "crm-base/src/test/java/com/crm/base/security/DataVisibilityTest.java": "sha256:4cfc9f0e3b9fcecadc2cf03eea47681c9aaac8463150ebd39114bcb05a2a8d31", + "crm-base/src/test/java/com/crm/base/utils/PageConverterTest.java": "sha256:3733b99795643d748dbcf95eb58c48e1763830514091ffce18d4e0f5b83f23c6", + "crm-dict/pom.xml": "sha256:4c8ad74e1e9be1f91ef1ce2a05dbd9381bd0e40235aaf3dcf4639b75a927d82d", + "crm-dict/src/main/java/com/crm/dict/DictApplication.java": "sha256:69963ebcce9cfaf21679200c9b0319e92f4347b1d06e1c3b2204794a6126c29b", + "crm-dict/src/main/java/com/crm/dict/config/DictDataInitializer.java": "sha256:317f5b19cc2bb9f5fc443780cdd06adfdb5c81c0e42207dc237890e40d83f3d2", + "crm-dict/src/main/java/com/crm/dict/config/DictPermissionInitializer.java": "sha256:fa74373639b654059e22f99af801b485a870788e7ed650bc9025ba2e5ddc8930", + "crm-dict/src/main/java/com/crm/dict/constant/DictConstants.java": "sha256:33a93f306ced75f960b68c218512839d6a3c114969b9d2ac58e2984103c38bd6", + "crm-dict/src/main/java/com/crm/dict/controller/DictGroupController.java": "sha256:56bd69f480df26f5092feaa8ed4b1d106a15824fd5318176b43f22f2c643f6fc", + "crm-dict/src/main/java/com/crm/dict/controller/DictItemController.java": "sha256:a69be62365a10f010aa6a93fcbf91328c2927fc39c128cba84be2586a54fe7ac", + "crm-dict/src/main/java/com/crm/dict/domain/dto/DictGroupDTO.java": "sha256:0ae4ee6424fa25ac940db5d162bfb39faa54c4cb6fb88bdddf22d7cea3679a4a", + "crm-dict/src/main/java/com/crm/dict/domain/dto/DictItemDTO.java": "sha256:c1fe83b3710554575160c7e4245daa1b4ff7275c65ec0d5509d59d5adbdd234e", + "crm-dict/src/main/java/com/crm/dict/domain/entity/DictGroup.java": "sha256:808c8898f56e8781bda00cabdb27c53b2abab315b631a803819c88f8076e3733", + "crm-dict/src/main/java/com/crm/dict/domain/entity/DictGroupDefault.java": "sha256:80902b0aabd41f2d063b8dc8ece861b0fb87942bc0f0bc56cc758b05e33871c5", + "crm-dict/src/main/java/com/crm/dict/domain/entity/DictItem.java": "sha256:5cb7982e689f87076f500d3ec06eeb0ff416f183e1aa61c9be98a370fc541cdf", + "crm-dict/src/main/java/com/crm/dict/domain/entity/DictRefCount.java": "sha256:51cc9bff1704ae14b95eef4d84f513c6e640ae5f52bb035c8572a1ef51c1f1c6", + "crm-dict/src/main/java/com/crm/dict/domain/param/GroupPageParam.java": "sha256:9f20ea768dc0dfb0f26c570748cc2cfc50bf187cd91d07a6890d9907e7e3835f", + "crm-dict/src/main/java/com/crm/dict/domain/param/ItemPageParam.java": "sha256:e64ce2f662f3b8583f58ed056c405a82d101a7eef581bdd6ba355816d3650a50", + "crm-dict/src/main/java/com/crm/dict/mapper/DictGroupDefaultMapper.java": "sha256:69831375b33470b3b12bba7e68066da4490f8e9ef4bf295ad0a7f82451b87700", + "crm-dict/src/main/java/com/crm/dict/mapper/DictGroupMapper.java": "sha256:69590dd226102d565ae18b8735450d71044df890a41d7ddd969a374f8781204d", + "crm-dict/src/main/java/com/crm/dict/mapper/DictItemMapper.java": "sha256:8fd1d314c0a468960f23df649adbd8e2474d004a71f1d8fdb82aba792a0029e9", + "crm-dict/src/main/java/com/crm/dict/mapper/DictRefCountMapper.java": "sha256:7c34b0e290d0ed73a5eb8b716dcc96e3b1c2844891c643816053648c58459de3", + "crm-dict/src/main/java/com/crm/dict/service/DictQueryService.java": "sha256:9f4facf03ffb11f8f761a4f98df9d694ad2129f393cc3af0e316a55e404fb33a", + "crm-dict/src/main/java/com/crm/dict/service/DictReferenceService.java": "sha256:907182bb7d09e26db379f9134ca3ea16d18f4a1ed7ee5012b7ca67d42b2355e5", + "crm-dict/src/main/java/com/crm/dict/service/IDictGroupService.java": "sha256:e6ce92c85dabac7d82a340c02e1ef23edf51314abe48e986931d5cf0391ac901", + "crm-dict/src/main/java/com/crm/dict/service/IDictItemService.java": "sha256:fbd2165ecc858e9fefdcb3ddc231e3103682e4330ef880a30b1db01465ec71b0", + "crm-dict/src/main/java/com/crm/dict/service/impl/DictGroupServiceImpl.java": "sha256:9bb1f4e26202451251d47c9fa662bc6c1d9b806d65ea66965f30c5d7218e71d7", + "crm-dict/src/main/java/com/crm/dict/service/impl/DictItemServiceImpl.java": "sha256:d93a98be9a5296a97d506232f369975692c1ae6c861b494f8dac7dd44fdf6c9e", + "crm-dict/src/main/java/com/crm/dict/service/impl/DictQueryServiceImpl.java": "sha256:ac5b1ef1f8eae837b91f4ea8a6d72bbf93bcd66336e7f63567a8c65475e5c55c", + "crm-dict/src/main/java/com/crm/dict/service/impl/DictReferenceServiceImpl.java": "sha256:98be664504a4121d1dfc586133aa403951ec404b900c9dfcd58d71fe3c5137f1", + "crm-dict/src/test/java/com/crm/dict/AbstractDictH2Test.java": "sha256:38f0b4398faf536f92a37b967636243ec6f0897cbb7f47881ae44a5c018d24d1", + "crm-dict/src/test/java/com/crm/dict/config/DictDataInitializerTest.java": "sha256:727f27a52c44ee58ee529dbe37a7f12ab241ca739207e97a5e9730be61ea1f4d", + "crm-dict/src/test/java/com/crm/dict/config/DictPermissionInitializerTest.java": "sha256:7af9b8ba95b58f954a53a21aefacb8ad6cebca62e8c87be07728c3a526299533", + "crm-dict/src/test/java/com/crm/dict/service/impl/DictGroupServiceTest.java": "sha256:7ca49c6bdc51333e3cfced34a759ca1444b673aa6cba84a6a20b57f965682531", + "crm-dict/src/test/java/com/crm/dict/service/impl/DictItemServiceTest.java": "sha256:412baa47082985f99b9c624d63ad3a4ca5a0559ec38df16bbe9693bc681f904d", + "crm-dict/src/test/java/com/crm/dict/service/impl/DictQueryServiceTest.java": "sha256:f796243023370c7fb78f9ea970e65c39a5ba0b602c3c2f233ba331640b507a3c", + "crm-dict/src/test/java/com/crm/dict/service/impl/DictReferenceServiceTest.java": "sha256:d78c3494dba2b02d6c9f9344c1e85bb972833622b25bcedb6f75c8096bbe5c7d", + "crm-file/pom.xml": "sha256:475a656769024c9ebc6d146ea421fa15a8a9f8447c88183ef73bc74d670755b0", + "crm-file/src/main/java/com/crm/file/api/FileApi.java": "sha256:16382a42a3497decf43d067f8177f24a2987c357ca8c392b85c708d412c5d7b4", + "crm-file/src/main/java/com/crm/file/config/FileProperties.java": "sha256:9e326877f87615b9095973497a2042511cd0c6342f43c49da188a0c9a2d07a6e", + "crm-file/src/main/java/com/crm/file/config/MinioConfig.java": "sha256:4164cd21371bf113f336d0ec0284386a4a498a2d05c1cb3664dc6361d0722d05", + "crm-file/src/main/java/com/crm/file/config/SchedulingConfig.java": "sha256:5b0646e67225d1df7ee606147d5cfd08c4260f01d789651b6b0cc6eb108007c0", + "crm-file/src/main/java/com/crm/file/constant/FileConstants.java": "sha256:da65d5ab9c2a42869cbb3ef9ec6902ca72c2517cb34457d8456dc325200aeffc", + "crm-file/src/main/java/com/crm/file/controller/FileController.java": "sha256:1bacd0cc3f353744820852bda1efb02acc0419da37e7b3ff5e5337f5f828b475", + "crm-file/src/main/java/com/crm/file/domain/dto/FileDownloadDTO.java": "sha256:b5995c70f0c683f43428355079a6e3401d8d9a852d214a6983b6e5a9d048f050", + "crm-file/src/main/java/com/crm/file/domain/dto/FileInfoDTO.java": "sha256:24207c0a81fc567926ab6d1be6c29457de71da01ebfbdc14ed0cab6aed6a6a0e", + "crm-file/src/main/java/com/crm/file/domain/dto/MultipartInitDTO.java": "sha256:8361c71df6758023ad5dc74c62999bdaf90ba40e2687bcf818cbc45b73cebd2c", + "crm-file/src/main/java/com/crm/file/domain/dto/ThumbnailDTO.java": "sha256:41a4d735972975f179a63a4cd394ea40342c08a960680835db89737c976fae8e", + "crm-file/src/main/java/com/crm/file/domain/dto/UploadSession.java": "sha256:36481cff82f5538e1ff4f2f78111ad90ed3a0f8afecc02abafe82fe817517128", + "crm-file/src/main/java/com/crm/file/domain/entity/FileInfo.java": "sha256:d55cedf9d5f08789e163a127e9bb80b72a332bf59c5bcab2a2c2dafef8d632db", + "crm-file/src/main/java/com/crm/file/mapper/FileInfoMapper.java": "sha256:a361e08d2fe5527a72a5c298cbcfb4297595dca1908f0749e547efe88027fb8e", + "crm-file/src/main/java/com/crm/file/service/IFileInfoService.java": "sha256:edb7b330bcafd61582b93480af6c4f7dce0d1464ade9884172419fe1c5967e6f", + "crm-file/src/main/java/com/crm/file/service/KkFileViewClient.java": "sha256:ecc7dd4ef4619241f5e8fb0da90f4fe1af0c315ac6a6032c4197db0d3767baed", + "crm-file/src/main/java/com/crm/file/service/MultipartUploader.java": "sha256:1cebace4bb09de60c58dc62733f6cd50b58185cd016fa5c4e11eec48c627da66", + "crm-file/src/main/java/com/crm/file/service/ThumbnailPlaceholderService.java": "sha256:b533b089de67ed43ca06b00d715af4685256ac79e33fc1430eece5b3fadd3dc0", + "crm-file/src/main/java/com/crm/file/service/ThumbnailRenderer.java": "sha256:54f7c603bdf50cd60dac3cecbfe98973451319861b5c82f96ec5bd7c2dc06727", + "crm-file/src/main/java/com/crm/file/service/ThumbnailResolver.java": "sha256:347e1b7f5236518df9f7b57ba20d20645f6f352669ce9f2ba025adfb796ea197", + "crm-file/src/main/java/com/crm/file/service/impl/FileApiImpl.java": "sha256:095da270ab0d5f9788bf0e60614b6ba06113943d97ebdefd17bdbf165c66fb5f", + "crm-file/src/main/java/com/crm/file/service/impl/FileInfoServiceImpl.java": "sha256:66fdeed1f189376c89e91f69058b5cf230540cc6f96b7cf4713d331c1a787b44", + "crm-file/src/main/java/com/crm/file/service/impl/ImageThumbnailRenderer.java": "sha256:f112ca0e26c88c44baec2a6447f53c3606443d53c6d22557f4a88340a9e16028", + "crm-file/src/main/java/com/crm/file/service/impl/MultipartUploaderImpl.java": "sha256:4942a100821df744f82ce4d9d39402557c64568f00b0c648c9200d4f2b6c2021", + "crm-file/src/main/java/com/crm/file/service/impl/OfficeThumbnailRenderer.java": "sha256:3d2e95d3f659a27cce69d115784ba2db88c7c1dc279b7e3257e50be70cee7d5c", + "crm-file/src/main/java/com/crm/file/service/impl/PdfThumbnailRenderer.java": "sha256:910f1309832ff353f7616e2f5ce4203ddd61677043d17069efd704ac4701cfd5", + "crm-file/src/main/java/com/crm/file/service/impl/ThumbnailImageUtils.java": "sha256:ea8a9ed22cb5b7cb536a4880bc365c5414e88ddbc264b6d97314978a59939c6e", + "crm-file/src/main/java/com/crm/file/service/impl/ThumbnailResolverImpl.java": "sha256:c476616e16c02c7fbbbb46e9c043fadfcf3568400c6fd5529e74191b887d902a", + "crm-file/src/main/java/com/crm/file/task/OrphanChunkCleanupTask.java": "sha256:aa372e2716d640866aa62ecd56e2096ed3b7ccf047666091e1dad947121ee03b", + "crm-file/src/main/java/com/crm/file/task/ThumbnailGenerationTask.java": "sha256:7f7fe7f4a19da393af2cee987a4085c97026cc2240b455f3fbde7104855de05d", + "crm-file/src/main/java/com/crm/file/task/ThumbnailRetryTask.java": "sha256:f55cd0914feadeb3a03bddca9b4aceb56ba1774251dbcad8fdb971570a6fb50f", + "crm-file/src/main/java/com/crm/file/util/FileNames.java": "sha256:3cf2ee2421d0163cc64754d74985af5e1b976163d45a458f9b43f89e196eed20", + "crm-file/src/test/java/com/crm/file/service/KkFileViewClientTest.java": "sha256:6e9ea082d3aaed3fcb8665a1c5acd7a61449f7bf4056b75100c1448f2990c447", + "crm-file/src/test/java/com/crm/file/service/impl/FileApiImplTest.java": "sha256:dd2e7dd7f59d87efbd0e85afd0f9f2b411c78c5b8393581249d6eb686a993b18", + "crm-file/src/test/java/com/crm/file/service/impl/ImageThumbnailRendererTest.java": "sha256:5db1e8383074a7f644f2dc8b47b2586ab1b5edeff2929f7a23faf729ba309fe8", + "crm-file/src/test/java/com/crm/file/service/impl/MultipartUploaderImplTest.java": "sha256:1cd3b8e95ec120f89fbd9dc6965521be57ffaa6cccc862323373de78421f3a9e", + "crm-file/src/test/java/com/crm/file/service/impl/OfficeThumbnailRendererTest.java": "sha256:694f51cc86ef954cd28d82bfa62eb2d9d8433edbfb2892cbaeec410fe044c815", + "crm-file/src/test/java/com/crm/file/service/impl/PdfThumbnailRendererTest.java": "sha256:a378461da31a59eea2127e4ea0dc828dd840aa7e88318b837fdae87ee2484a11", + "crm-file/src/test/java/com/crm/file/service/impl/ThumbnailResolverImplTest.java": "sha256:38e5071e0bdca078b564c57925e7700b0dc19f9248eda8c929eabbd709697554", + "crm-file/src/test/java/com/crm/file/task/OrphanChunkCleanupTaskTest.java": "sha256:e4fb06547db7750eadbe8bc12ae964697b5edcabc2292f43bb545301cb4b6403", + "crm-file/src/test/java/com/crm/file/task/ThumbnailGenerationTaskTest.java": "sha256:26a1e2ca445f36ef0a1aace547b652055322674a492842208df70406ac765b72", + "crm-file/src/test/java/com/crm/file/task/ThumbnailRetryTaskTest.java": "sha256:97d989e5c11e20aff45ea2fb912ca865c3a1466db603e4b788b26c4dc86c384a", + "crm-lead/pom.xml": "sha256:fa23d0ee2fb0828e66bbfdb17e234ec0d2964dd0866e164a6120a05fdc7890c1", + "crm-lead/src/main/java/com/crm/lead/adapter/PoolLeadOccupationAdapter.java": "sha256:8343a3b3757d37f1c1573ba25540e9ad68be22967373da83e32260edee6c20ff", + "crm-lead/src/main/java/com/crm/lead/config/LeadPermissionInitializer.java": "sha256:901c06d86781f036eb626371b99bbdfadb80f0796a64751f54c4a3ebc5598613", + "crm-lead/src/main/java/com/crm/lead/constant/LeadConstants.java": "sha256:284b291e4067e80755ad17887fd926fb43cf1094760ac49f66d42ee50d8f43f6", + "crm-lead/src/main/java/com/crm/lead/controller/LeadController.java": "sha256:befa15de78c8a6b3975364114394bfbb2eb1b1d53bf048babdc71e22c7ff521f", + "crm-lead/src/main/java/com/crm/lead/creation/LeadCreateContext.java": "sha256:d1cf70296fd0efcd9df0483c3eec1019d28cc13502d783b1aa9e75d8d7b0d219", + "crm-lead/src/main/java/com/crm/lead/creation/LeadCreationPlanner.java": "sha256:789046359fe913f64897ebad39dc2952914f88e0da5c5c986d2fe0816d380cc0", + "crm-lead/src/main/java/com/crm/lead/creation/impl/LeadCreationPlannerImpl.java": "sha256:d2e08c5093262443631e08f349688f6a7da52803de3471219e0e0c2f24af37cb", + "crm-lead/src/main/java/com/crm/lead/deadline/DeadlineBudget.java": "sha256:e6a73c63fbb92ac7e39284e676937c9610f6de2b28840b47862daff528e638ad", + "crm-lead/src/main/java/com/crm/lead/deadline/LeadDeadlines.java": "sha256:9479aeeb72e19e016bfb4b692ca0e251c58397ce3d002bc52c4cc10f751e023d", + "crm-lead/src/main/java/com/crm/lead/deadline/impl/LeadDeadlinesImpl.java": "sha256:364c3ba8a7baabb7a55be28cc01ea83e2e21d24951644aa4b30634225f6a5ab1", + "crm-lead/src/main/java/com/crm/lead/domain/dto/LeadBatchFailItem.java": "sha256:52bc079ea67f3ec8a7b46036717fb48a1012570cd933e5fbdcf77f8cfb370776", + "crm-lead/src/main/java/com/crm/lead/domain/dto/LeadDTO.java": "sha256:21d0bfbef89215dd0be41ecc91c7c077b101a7784c01050a0adb6f02dff895f7", + "crm-lead/src/main/java/com/crm/lead/domain/dto/LeadFeedbackDTO.java": "sha256:a979afb47a465aac68fb53820c1ca4837bbbc46670249525b46b4d2c9780c92e", + "crm-lead/src/main/java/com/crm/lead/domain/dto/LeadHistoryDTO.java": "sha256:a7c4a3c25575701b8fed4209236fbeb766972e134770535daa929cf3dfa81988", + "crm-lead/src/main/java/com/crm/lead/domain/dto/LeadStatsDTO.java": "sha256:5dbbd09f73ae83388aedbf1aa4d289782fa845deb19c386c1b44d8205636440b", + "crm-lead/src/main/java/com/crm/lead/domain/dto/OwnerSnapshot.java": "sha256:133d057c3b7cc168c41a5da3b197e0e8260c6cb283419eb63a8d59d382fae9e7", + "crm-lead/src/main/java/com/crm/lead/domain/entity/Lead.java": "sha256:4ef35be20ec4c2e09614a0f3de269f18a42470ee0ac4145648dbf642f943391a", + "crm-lead/src/main/java/com/crm/lead/domain/entity/LeadAttachment.java": "sha256:94c9b8ef73beffdf946651964b6ec551882d3f182c3563b85b0b886e1b0d5876", + "crm-lead/src/main/java/com/crm/lead/domain/entity/LeadFeedback.java": "sha256:ab08310cd2a0c955b910f2ca36fbf8a0379ddfab211871af4df4e53821e3e598", + "crm-lead/src/main/java/com/crm/lead/domain/entity/LeadFollow.java": "sha256:d33588eb168324870a98d1e6ed21c881430074347d4976ff1cd9c85857dfc81a", + "crm-lead/src/main/java/com/crm/lead/domain/entity/LeadHistory.java": "sha256:4724af7d7aab3c9af42ff0671b9287694aa9b00ef26ba309a39376e7f8b3b1ae", + "crm-lead/src/main/java/com/crm/lead/domain/enums/HistoryType.java": "sha256:07af06606dab1456fde7ee14cc4cb3ff6372801b217f5849e3eda5c57a7ae3de", + "crm-lead/src/main/java/com/crm/lead/domain/enums/LeadBatchFailReason.java": "sha256:2dc78509a4aab93b69e27ca28fb0865da123dc41a9b62f6ac7ea51361527657a", + "crm-lead/src/main/java/com/crm/lead/domain/enums/LeadViewType.java": "sha256:b2165129df14bad41898bb700ca00b8a47fc3896aa35c9a647b35b504bc71ec6", + "crm-lead/src/main/java/com/crm/lead/domain/param/ConvertOpportunityParam.java": "sha256:4096f08a8bd4a922d75fc9746ac52209ccd144a256435f0003d56c093b05ad22", + "crm-lead/src/main/java/com/crm/lead/domain/param/LeadPageParam.java": "sha256:31e171c25320a309a559b1b2e1746e15268b544f97ae2982bd6aca1511810e78", + "crm-lead/src/main/java/com/crm/lead/event/PoolChangedEventListener.java": "sha256:de807bd874827185fb0ebe55eecb119307c258278ee920284bdc5b8c97beec9b", + "crm-lead/src/main/java/com/crm/lead/history/LeadHistoryRecorder.java": "sha256:47b96669b5cc2d344b020c8ffd67d825d29f4ce6a57a1fe061a586d47aae9a84", + "crm-lead/src/main/java/com/crm/lead/history/impl/LeadHistoryRecorderImpl.java": "sha256:178f053e4c535eedf48668fce8d9ec1dba3c4e13937018eacf509b12f8176379", + "crm-lead/src/main/java/com/crm/lead/job/LeadMaintenanceJob.java": "sha256:b1f3c3a8898ee5c2b00f195b6b2b5db4b03cc136a57305c3d94199ea9115f352", + "crm-lead/src/main/java/com/crm/lead/mapper/LeadAttachmentMapper.java": "sha256:670c1ce5bc7c418917c3a8d07fa4464d5943c2166e7651f689be90a3ff56cc52", + "crm-lead/src/main/java/com/crm/lead/mapper/LeadFeedbackMapper.java": "sha256:03c66a0392a8ecca44c2379b85e24c4eaecc1a664d78e2f1f95b4aa1e45dad95", + "crm-lead/src/main/java/com/crm/lead/mapper/LeadFollowMapper.java": "sha256:e3ab87cbb04fdf9cf525fdd89bf4e71d895164d1cdc5dd0359ffdc7d63dd9ef2", + "crm-lead/src/main/java/com/crm/lead/mapper/LeadHistoryMapper.java": "sha256:f3c941ed25d1f8b3e02d140758482c45c5dba64756eb5a478199e1cdea3c496f", + "crm-lead/src/main/java/com/crm/lead/mapper/LeadMapper.java": "sha256:c5d864adc8cc4b07810d9d33357d4044f214f82396dc1701b8191f9f8b1916b0", + "crm-lead/src/main/java/com/crm/lead/owner/OwnerSnapshotResolver.java": "sha256:8d9bb91fae0a16ad4d77e24ec9e6e885978e6fc3e5235a3c95ac3f1daa572f21", + "crm-lead/src/main/java/com/crm/lead/owner/impl/OwnerSnapshotResolverImpl.java": "sha256:798cfaa77898f0d62dc19f8f9b543583abcb15cb5e727551e93cae001c4de92d", + "crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPort.java": "sha256:e7e7e64ec2b98972051bb66cb316c831f4f0d4e3419cbb34c6c00463ddf372b5", + "crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadView.java": "sha256:a54ee71d30610c03e0d64a9f309d093fcdeae9031262d9431dd653af18e58565", + "crm-lead/src/main/java/com/crm/lead/port/inbound/impl/ConvertibleLeadCatalogPortImpl.java": "sha256:01b1d3b28abb810d650966a26934ec1952eefd774332de6b73a77184113b4a6b", + "crm-lead/src/main/java/com/crm/lead/port/outbound/CreateOpportunityCmd.java": "sha256:1218380c2c3558bdefff0c878ec90175705afb6cecb58766f6dcb7ec2bdc0fdf", + "crm-lead/src/main/java/com/crm/lead/port/outbound/OpportunityCreationException.java": "sha256:457d51dcf0f04a334c02fc8616c7c0af6438cba4bbd9cee3bcb0ebdc025b9d92", + "crm-lead/src/main/java/com/crm/lead/port/outbound/OpportunityCreationPort.java": "sha256:bd437a31e036d7d4f81339a1ed24f54494696cb283632d11bd8f4b1995d2ec7c", + "crm-lead/src/main/java/com/crm/lead/query/LeadViewQuery.java": "sha256:af958e449d81b274c96fb56ac636795c5c580bb5c909c2cd8ca9d7fc897bb739", + "crm-lead/src/main/java/com/crm/lead/query/impl/LeadViewQueryImpl.java": "sha256:8ce9bf2f91516b183d7233954fcd6a6195b4f499f528c9f021754c481d8dc137", + "crm-lead/src/main/java/com/crm/lead/service/ClaimLimitChecker.java": "sha256:19d7368c3ca9e4fbe5855dea9bc747124eb072677d62ce364ba28749b201b288", + "crm-lead/src/main/java/com/crm/lead/service/ILeadService.java": "sha256:a7aaf1dbf6c0bf3be1558d7b3133a370b7c70f5df6613eed980bdb3bb55ccd00", + "crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java": "sha256:06e847735e46995704263db118cb770b3d31869323715ee87051c988b4fc57b0", + "crm-lead/src/main/java/com/crm/lead/state/LeadTransition.java": "sha256:3302b7c2bc4c9c20f46f12f489aee7daa63e953475842d84008da9f6500c722b", + "crm-lead/src/main/java/com/crm/lead/state/TransitionCmd.java": "sha256:a2db07c01410dd4d653fff93c9fe5c714794e6e94b5c0dc6087d14ea74a529af", + "crm-lead/src/main/java/com/crm/lead/state/impl/LeadTransitionImpl.java": "sha256:f147cc16b538c550e02c18d5e0f449a6be4ef43760f51503a04eb47885519251", + "crm-lead/src/test/java/com/crm/lead/creation/impl/LeadCreationPlannerImplTest.java": "sha256:87e025607edc56376a5ea3c0ae8276022076d179abdf0567b3cef8ee0de68a9d", + "crm-lead/src/test/java/com/crm/lead/deadline/impl/LeadDeadlinesImplTest.java": "sha256:c23daab474ae99ca267d5c6731cd722dfd4d486a2df20fabda17cd42c0705b0c", + "crm-lead/src/test/java/com/crm/lead/event/PoolChangedEventListenerTest.java": "sha256:f5263f95aaa26ee26d6ecbb857b4b8c21d7da844c541b319f30643ecc66b7019", + "crm-lead/src/test/java/com/crm/lead/history/impl/LeadHistoryRecorderImplTest.java": "sha256:c610d6e89afcbc40a9bac361cba2fecb6fe046f45492c00d157e62b487ec2462", + "crm-lead/src/test/java/com/crm/lead/job/LeadMaintenanceJobTest.java": "sha256:f549eeafa8eb75e7264e637c1d41c09bf45b86d18663b156f9ee4df8601f8909", + "crm-lead/src/test/java/com/crm/lead/owner/impl/OwnerSnapshotResolverImplTest.java": "sha256:3892c9e0e915738e55af3d3302e2b24d8330be52bacb05840e85156a8ecffc9e", + "crm-lead/src/test/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPortImplTest.java": "sha256:6410136270445cdbc5e6d9d7f5da9764d87e6f73cc5eea7f642878e154983cff", + "crm-lead/src/test/java/com/crm/lead/query/impl/LeadViewQueryImplTest.java": "sha256:96cbb627a3053d502e5512ab1952d23647f7fbcd31dc55907fc7ca16b5f713ea", + "crm-lead/src/test/java/com/crm/lead/service/ClaimLimitCheckerTest.java": "sha256:4c8a03aae0c19a4875df3548f2035760274f288c2f5c1ad2612fe5ca40e1c9e2", + "crm-lead/src/test/java/com/crm/lead/service/impl/LeadBatchServiceTest.java": "sha256:b9debc03539a006946ef0a2bb182d47ecd8a4f45d4b0cce189e57c08058b6fe1", + "crm-lead/src/test/java/com/crm/lead/service/impl/LeadServiceImplTest.java": "sha256:5547bae863b3c6934a70d205f7ca735bcba6d800d03054932abd39f6c8cfe8b6", + "crm-lead/src/test/java/com/crm/lead/state/impl/LeadTransitionImplTest.java": "sha256:1a353fd2c98cb7109c75cf5c16171e9e20cce1efed8cb18fc2dccb9053f160d0", + "crm-opportunity/pom.xml": "sha256:b6f7b5b226f8e2ec62fef6e9087449434dd26b23d3d56b7a91ec150af3bf9509", + "crm-opportunity/src/main/java/com/crm/opportunity/constant/OpportunityConstants.java": "sha256:f06edf0b0a068722e1bbc891e4a4a922f61a97f8fcd4164c7f91cf22a3beda0e", + "crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityCollabController.java": "sha256:a927ec6906cbc3a4929d596a0a57a9f39616306c039f93086a77b11e70fb6377", + "crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityCreateController.java": "sha256:7fa82aeeef22b76ff4c32183bef6c877ff02a2ef47d48d6160ae08c5b6e5728e", + "crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityDetailController.java": "sha256:22bed890a61b639e48936ae254fe38d353317c0fa9c6357f53ec42f6c08da562", + "crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityQueryController.java": "sha256:22caeda46b12cc1de52f11d32367b07c78961c7524db20cadd233c9b217fff31", + "crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunitySchemeCardController.java": "sha256:08348345a4e3c91f7a90ab3e6f2b4893c9d5f99e0a42dec6a6f6f5208d6aeba3", + "crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityStageController.java": "sha256:dbebedf8201c0e1e1573292343aa4902806ab00b2e43c3eab49c0b37e2c542e3", + "crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunitySubController.java": "sha256:d50175070ef47372ff584d2cf976dd91b16fa1aac5cd8645b83909ebd9547fd7", + "crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityTransitionController.java": "sha256:93942b5a4355c9c1ee56392e8295bef2d6a13529be98f64d42bb229ae9396b13", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/CreateOpportunityRequest.java": "sha256:16eb516b2e2035fab58f392f1246877242a9d35669b277750adfa79eedbb56a5", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/CustomerContactDTO.java": "sha256:e347fe00e097eb03e32c684c48c00ab18bc4793aa1cc7a07baa9c80aa80eab01", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/OpportunityCustomerAddDTO.java": "sha256:8dc51c16719732fe4762f514f226a6793a6c0fa7c0ffbe6cab0b7f70786fdb15", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/OpportunityCustomerCandidateDTO.java": "sha256:491c98feebab0ef02ab40008598dca789fe32903ff1a47b293d12bd27054f42b", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/OpportunityDTO.java": "sha256:5a24072d72cef014803ad13ea3421fc46809370e930ff17adbdc78b22a771eae", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/OpportunityTeamAddDTO.java": "sha256:747e1191f8b4ccd3483961197e6f397f5490d9ba686aa7280029c15dc471330b", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/OpportunityTeamUpdateDTO.java": "sha256:90a7b2d05b1baccfd941c934aec8405524681926ed9f397f66f644233c0bc597", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/OpportunityWorkPlanAddDTO.java": "sha256:3014aea094428ee67dfcbf86396880fd36b70ff66996130fcaa6e0d67af1133e", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/OpportunityWorkPlanUpdateDTO.java": "sha256:1dd16ddc77602bb2d01076e42d5894d2b26b7e8bc2e635669d7f1d5f790b487c", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/SchemeCardDTO.java": "sha256:39b087025aa46f7829cdc67383e1360bf6ac796ced9654df74d760245eb6c547", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/SchemeCardDetailDTO.java": "sha256:2223acf2b84b0d67fda9a4a67cfb0ff8975f76f417ef31ac1b613546cc1cb59d", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/StageHistoryDTO.java": "sha256:b421c7b0d6fc3013171005d9e2567d761685f8c18a67cf14a451a4084ee69b2e", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/StageProgressDTO.java": "sha256:d378be9270833c0b4cb502988278b60bd0a4cccdc7f1b01d105a0de9e70e9ec3", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/dto/SwitchStageDTO.java": "sha256:0c9397c8f89ff303f6d1ebe5215bd57c4597fa18685bb10ca2ad53238debf949", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/Opportunity.java": "sha256:8954fcbb4332d13d7070b413a5ea241402151f3c916cfbac909094dffb1fb8e2", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityAttachment.java": "sha256:75ceee391dfd83a38c34064ced1861562cbd6b9a29bd491bc7b66a258f886fff", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityCustomer.java": "sha256:d81c9d890a66f483cc2b6e47609ae4ef2c985291587874338f19083fc470aec8", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityFocus.java": "sha256:dbbf7b88ebc7dd83aee37b8a75bae3d496d27a48b90f22560a5445f4dc842cad", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityFollow.java": "sha256:0349b346f5c01d51f79132ae3f5898c40fc18cbcc58c8c1ed4896d357c989380", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityOplog.java": "sha256:e1c62c2e9a2de753cbc73c9a4d8ee19dcd285103448955adbaf939185ae1b2ab", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityPendingNotice.java": "sha256:e06cfaa92150755303211a02e2f7ddfd1b1c373470427b756c5a99749bf69809", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunitySchemeCard.java": "sha256:fd79e83d6c9f102134139b180b928048ea167b08b7917b200a49774ed13acb32", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunitySchemeCardValue.java": "sha256:15023398f27d36a236a2433c9c4d68dcfbd5f6f84451ef311fb461bda3da06eb", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunitySiteSurvey.java": "sha256:9b3d45a40d305caa19b68097f7f2f72b46b62dac2b509911f9ece93e71f329a8", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityStageHistory.java": "sha256:7ee1818d3ec17794b8e4d91e2fe9ca7edf4f2d0d1406ef3e075b421e698b10a9", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityStatusHistory.java": "sha256:14a3c07f334843d78f42c938526b2f23a1fad72325d986e65267c1a4a22a9816", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityTeam.java": "sha256:e9f5cb0c2ce3a7d683c3c9e148f53b6f52131f23433cfc233d6e072b17cbf9bb", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityViewLog.java": "sha256:bd3392a0a9cb9b6a633c0550381de32155f4ce7d03e9b04730d95c92164405df", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/OpportunityWorkPlan.java": "sha256:ddd84080d707ee3151e84983997fadc6cde42e2446a4d54b4079bfa09d2cdc0c", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/enums/AttachmentBizType.java": "sha256:9cefb197ca6054669e5168c48e2e14a111c640d167a875f23e137a470ffd5599", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/enums/OplogKind.java": "sha256:cf8e364a743c09b797f324b191a046ca2da9e90e3b83b17dbb8e4f6a2a2632a8", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/enums/OplogLogType.java": "sha256:ffcfab4cee9798951e92e7cd95a44bef05e10292a35a8664b9c1c40419788d69", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/enums/OpportunityOpType.java": "sha256:303bff5664ac0315ec480a938fed3069333b77e8a174a78925b6b189570afcd3", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/enums/OpportunityStatus.java": "sha256:aab02f04e1ec317285a44c69932e02273043d35ec0213d5ded70cf9d90e29cff", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/enums/OpportunityViewType.java": "sha256:bdb28239bdb29917dd31d01f84340e5b9f126e4a24bf5931cacf12fb17685e79", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/enums/SavedViewOperator.java": "sha256:15e0e7b6a79159a57c674041850084243699a8ace12b8e5e58f1efb1a6f51056", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/enums/TeamMemberPermission.java": "sha256:b6ac87d76fbe8a4f7fc89041e8cdc9a69a825f2f5d3dae77c7d5d4e659020d57", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/param/OpportunityCustomerSearchParam.java": "sha256:917c1d6312d3d4e9a0012f2db632ce557639cde4599983eea0826d8cb50f4dfa", + "crm-opportunity/src/main/java/com/crm/opportunity/domain/param/OpportunityPageParam.java": "sha256:3d38a9d820a1064695b6db037465e3a16a26bebe22e1177490d9362407b4fa93", + "crm-opportunity/src/main/java/com/crm/opportunity/history/OpportunityStatusHistoryRecorder.java": "sha256:00342f0f10a54f74b0b791819fe449ff135c3ef9ae12680706b071a35c4b58c6", + "crm-opportunity/src/main/java/com/crm/opportunity/history/impl/OpportunityStatusHistoryRecorderImpl.java": "sha256:2389c5c1c621f3680d05a5e8a574df0d6daa3ae38005184a083c67f64336a76e", + "crm-opportunity/src/main/java/com/crm/opportunity/intake/IntakeSource.java": "sha256:92155c3d95a6c3782f98b1abd658ad016581caadb90ff44b4d297006c1244ca9", + "crm-opportunity/src/main/java/com/crm/opportunity/intake/OpportunityIntake.java": "sha256:39afc2e4478a6c48f4266466afba303bdd099bc10666c4912c4d3b00fc66ace5", + "crm-opportunity/src/main/java/com/crm/opportunity/intake/OpportunityIntakeException.java": "sha256:3ca86cc59bdfcd39e814f15af8f2f0f36f9b852afc58b160f7ceebc9e396144b", + "crm-opportunity/src/main/java/com/crm/opportunity/intake/OpportunityIntakeSpec.java": "sha256:feeaa509c6da243b4b057d6f14dcfc626e2936d1468ff27c63575baede3d1ab4", + "crm-opportunity/src/main/java/com/crm/opportunity/intake/impl/OpportunityIntakeImpl.java": "sha256:934c7e948c9da2dd633e4452bfb2c88fad19c038e4b416e88b229a4d8c99c947", + "crm-opportunity/src/main/java/com/crm/opportunity/job/OpportunityMaintenanceJob.java": "sha256:607a76a6aa9064db92336be0dbead47b38590fdb61d296b05d1dda92b5904805", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityAttachmentMapper.java": "sha256:557ac5553481adb4758415cb632bf8272061a718ad4759ca1147cfdd4de030f0", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityCustomerMapper.java": "sha256:fd14e2bc6e65fb13958dbf7049a375b5487d516f351e803389fda974276ab425", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityFocusMapper.java": "sha256:565f6b41511b21bcb627b7ee12fda84128539d218cc4ae811d49b5abd03f87c3", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityFollowMapper.java": "sha256:f279209d497e03ac8029073feb4a4b49ebd32ec1b5f1a7c7cf00dcfa456efa8b", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityMapper.java": "sha256:7cb855d030bf8cc9f6f6e956da9f8138916707172705d3e57fbfd6d346319efd", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityOplogMapper.java": "sha256:957ed81e64f8ec89054127f63a3cc87238b652920277bf335caf5d07bcb1cbee", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityPendingNoticeMapper.java": "sha256:2f1032a9127cad0a736c99cb0e71cd9fa3cd6164da4c8b0651ed6de78b6edde0", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunitySchemeCardMapper.java": "sha256:20f7ef3a752f591aa2404c86d12e3fc6c5e580a6880ad92d28989edd94cbdb76", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunitySchemeCardValueMapper.java": "sha256:0d44229f3c2bf402f12b01fcc0d4afc86d7408a25649ba0e8d238c3f596d0c8a", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunitySiteSurveyMapper.java": "sha256:e656c1a4da13f536eb4e1cfbd7dbfe55f7b460a660467ac6b2e16bab9aefc405", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityStageHistoryMapper.java": "sha256:7eabbb6dc57ffbac36ea4910740975cd780f7c7085f3bf1cc26ad6cbb489a6f4", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityStatusHistoryMapper.java": "sha256:b01d43e817dcee75180c967095399c963d64c85bc15a2299c6dde097b8ed7238", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityTeamMapper.java": "sha256:41d4a83163665ae12a935cd6e03ac0be084fa534f38eb857c78eb6a2d46e593f", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityViewLogMapper.java": "sha256:b6377b4c14ad6cd353626bde39492176ea38e6aa13a57a06b68b9b2ba8bc1bad", + "crm-opportunity/src/main/java/com/crm/opportunity/mapper/OpportunityWorkPlanMapper.java": "sha256:290ba71fbfc40d20c3284461733531e63e77b2d9766e251cb974dde242b42922", + "crm-opportunity/src/main/java/com/crm/opportunity/owner/OwnerSnapshot.java": "sha256:c91861c875736a95df1686473a431bed33785b92f28c6285b0fd91ae74c04e24", + "crm-opportunity/src/main/java/com/crm/opportunity/owner/OwnerSnapshotResolver.java": "sha256:87a8badff74414a5681f48c02a3fd45f46cf986808a157b32dd8d5d90164fd55", + "crm-opportunity/src/main/java/com/crm/opportunity/owner/impl/OpportunityOwnerSnapshotResolverImpl.java": "sha256:c430d17573a5fbec41365a207b6ea85a55bb7d649021787496c2111dd6f753d0", + "crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/ConvertToProjectCmd.java": "sha256:8a96f9ffa9cf4e352c246cc07c6e2d941cbf7743b60ccf2ad4e5fe2b0153df57", + "crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/OpportunityConvertException.java": "sha256:9ed284f46fe8eb094a524c2e2f61e7b648eb854e7a230157704c12f22c70133b", + "crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/OpportunityConvertPort.java": "sha256:8eb2c82fa809d0ed63bdbefabddca6db8668b9e26271e6ed42853108ea7b8732", + "crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/impl/OpportunityConvertPortImpl.java": "sha256:47d639378d3387b6ca5c00a34875b9ec3b7036f454095e52a8204239bf01baaa", + "crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/impl/OpportunityCreationPortImpl.java": "sha256:584ace59d9377d70b3197217daae95463c185e62954f1b3b2714a9c1bb38414e", + "crm-opportunity/src/main/java/com/crm/opportunity/query/OpportunityViewFilter.java": "sha256:a3c310a9760cab7383ef2ed0f3fc599f9441111bf348edc7e398fb4903f3a9e8", + "crm-opportunity/src/main/java/com/crm/opportunity/query/OpportunityViewQuery.java": "sha256:86b6427c54b841d1e1db30f14b877d1ff546a28c27bf3a5a4b96cc5065bf1317", + "crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunitySavedViewFilter.java": "sha256:86786192745913c2b2815900a1971dc0267a3ad5516004745cb08c83cdb70500", + "crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunityViewQueryImpl.java": "sha256:364e2ef9e79d1003250d349ff24629ce581ac7f4308075e3dabba304f9d62a76", + "crm-opportunity/src/main/java/com/crm/opportunity/schemecard/OpportunitySchemeCardService.java": "sha256:05ad7653c0807f41aa6ea40096a5483bf629cf425e71458577e1b189b81d079e", + "crm-opportunity/src/main/java/com/crm/opportunity/schemecard/impl/OpportunitySchemeCardServiceImpl.java": "sha256:c19fb0ea46c7fdfdd4868331b1a7554d7260e98511f80a861fe3d973d34d74e2", + "crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunityCollabService.java": "sha256:436f58699273cd655022db1964a54f097fb1a22a423f53bb29dfaf4d8377b5a1", + "crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunityCreateService.java": "sha256:cfdeffecd9fb28a9e5a05685e4ebaefb3c933013f308ac3e696a67dc735dc0d0", + "crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunityDetailService.java": "sha256:3c31b70d893bf7ba0e0b417568bf7a55a68bc50db572879dd4e450e9eb182d5a", + "crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunitySubService.java": "sha256:68d13a07655610191f922b34a02170218367040e59c24cc0515f9fee107fe5d3", + "crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityCollabServiceImpl.java": "sha256:55cd5df655eaff70a4bca05a9acdb8a873316989e94b10bb7a51340dec59f22c", + "crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImpl.java": "sha256:aae65debdec9d0d13e690aa0f339d9b1b6bbd6eba397ce5c01572706e99bbe76", + "crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityDetailServiceImpl.java": "sha256:073a4d7f396cbd8d4915eec6ca4fb037c2b9a4d53a1f9bf6170cc7eff121084a", + "crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunitySubServiceImpl.java": "sha256:d8b65d12b26c0d3d01446457f08e88f52a4aeebf25a299828780c9a3b0b4802b", + "crm-opportunity/src/main/java/com/crm/opportunity/stage/OpportunityStageService.java": "sha256:6082b3f6f357b533ed135e1204ed3c30f5f709c7fc81c584d6f400767fa182b6", + "crm-opportunity/src/main/java/com/crm/opportunity/stage/SwitchStageCmd.java": "sha256:9f5593c1ffa321f0d2d7df887beaf10e508734526881a008a14cd1b6c4466669", + "crm-opportunity/src/main/java/com/crm/opportunity/stage/impl/OpportunityStageServiceImpl.java": "sha256:36737fb345242bdf5da50817f9f3240c53c9c5dc7997b38722d2aa0113c74a57", + "crm-opportunity/src/main/java/com/crm/opportunity/state/OpportunityActionGuard.java": "sha256:fa819aeb6679a66519481c2ab66dde7720caae9d59bf7e08faba74defac1bc23", + "crm-opportunity/src/main/java/com/crm/opportunity/state/OpportunityTransition.java": "sha256:aaaad3abc2679f114b1cdef504fd21e9eac6667e86b97f29ab2f875be4e3f979", + "crm-opportunity/src/main/java/com/crm/opportunity/state/OpportunityTransitionCmd.java": "sha256:8e75b8d258795c8b28ccbe24249568b95101edacca578f60f4e8db2c4237ee61", + "crm-opportunity/src/main/java/com/crm/opportunity/state/impl/OpportunityTransitionImpl.java": "sha256:0ac0cdaea67595a2112c56f159eb952c2ee16f6b49b23918ab09ea31d3080389", + "crm-opportunity/src/test/java/com/crm/opportunity/domain/entity/OpportunityTest.java": "sha256:c6eecd2f5ec65c0327ce9bea9604d894284d8fcacf113f70ae35b13514084eb9", + "crm-opportunity/src/test/java/com/crm/opportunity/domain/enums/OpportunityChildTablesEnumTest.java": "sha256:631481e1945ef13a8abf33e0a1785e73770901f91b12848288c303a668b411f4", + "crm-opportunity/src/test/java/com/crm/opportunity/domain/enums/OpportunityOpTypeTest.java": "sha256:4e4a1a0c9ac47319962659a1a19c24e7d0276065a97cd4b059c0135b779c0d72", + "crm-opportunity/src/test/java/com/crm/opportunity/domain/enums/OpportunityStatusTest.java": "sha256:7b0457b135a468e17f267a664f355730271461c41b620ee3b6f8588f35cc0d87", + "crm-opportunity/src/test/java/com/crm/opportunity/domain/enums/TeamMemberPermissionTest.java": "sha256:830d8a8228263c82cf2b74a22279a997f90c9421089f418fa54a97e26a6d064a", + "crm-opportunity/src/test/java/com/crm/opportunity/history/OpportunityStatusHistoryRecorderImplTest.java": "sha256:31a956c91d645a618f95697dd52a48bb54e8f73804e675d586699f52a87c6b1c", + "crm-opportunity/src/test/java/com/crm/opportunity/intake/impl/OpportunityIntakeImplTest.java": "sha256:5c8c9cd394d4d90bcbb45754861e502eda441d9fe5d257f9a10f0fd46b1c8b68", + "crm-opportunity/src/test/java/com/crm/opportunity/job/OpportunityMaintenanceJobTest.java": "sha256:0eac23a91cc863e0d0c22accaeb7290528d06f022af41412e745700824b58226", + "crm-opportunity/src/test/java/com/crm/opportunity/mapper/OpportunityChildTablesMapperTest.java": "sha256:a3fab9adff2bffdff64e00044c42c165777b65e88fdcca8b33411ee39fcd046e", + "crm-opportunity/src/test/java/com/crm/opportunity/mapper/OpportunityMapperTest.java": "sha256:c0046d2afa6ed227c00d2ecaa70bdab73bfe21c068db672127d4d722ec955531", + "crm-opportunity/src/test/java/com/crm/opportunity/mapper/OpportunitySiteSurveyMapperTest.java": "sha256:a1a3c360e0b16fc553c441949ad6fddca2f755340ca53a6f675e97bbc6dc324d", + "crm-opportunity/src/test/java/com/crm/opportunity/mapper/OpportunityViewSourceMapperTest.java": "sha256:d7582832310641c5f67d27626cb72e546ba6fd2c8316e2a92f04514a66faf0db", + "crm-opportunity/src/test/java/com/crm/opportunity/port/inbound/OpportunityConvertPortImplTest.java": "sha256:4889ed80915bac530b9bde0cbfe9bc23461002727523c8366d1cfa59018b8e15", + "crm-opportunity/src/test/java/com/crm/opportunity/port/inbound/OpportunityCreationPortImplTest.java": "sha256:e5df57a383c75531ae1c88e26cb83728d97967091c892cf69dd424860b04ba0a", + "crm-opportunity/src/test/java/com/crm/opportunity/query/OpportunityViewQuerySkeletonTest.java": "sha256:3ad766650904a1fd38324794e3967758e15e8458d51c19d67a6f17b6a22fd8d3", + "crm-opportunity/src/test/java/com/crm/opportunity/query/impl/OpportunitySavedViewFilterTest.java": "sha256:c6b8966c81f5a07bd3fda2af30ca4ab06cc59bb5c7eeff0be25faac6b4b65ba0", + "crm-opportunity/src/test/java/com/crm/opportunity/query/impl/OpportunityViewQueryImplTest.java": "sha256:17cef74bfb1a10f12d9c316c2e83b6ad1962aff5bbf55e0aed87578e67cd6dfc", + "crm-opportunity/src/test/java/com/crm/opportunity/schemecard/impl/OpportunitySchemeCardServiceImplTest.java": "sha256:9892d31fee869ab7b11ee564db1e2681b371294ba65023aaaf9d5933f88fd507", + "crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunityBoardIntegrationTest.java": "sha256:16efcfde20300ef4bc0bd0ca400448434f929399facd5590e76faf959c2903d3", + "crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunityCollabServiceImplTest.java": "sha256:c2c2afe007462b080b22e53b33b4e34c4f06af8cd9441b825303f764dafec5e3", + "crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImplTest.java": "sha256:c6c49825cc2b5eb405dcca81fd34cc73104dc24e6ef5ef9ab132ab73eae3eb8c", + "crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunityDetailServiceImplTest.java": "sha256:812ac4dbeaa4325ad2986856d8bb04f242bdcc24dc4bf9102a0566189d8fefac", + "crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunitySubServiceImplTest.java": "sha256:68c6811da5b4543999594468f4e86ea5af45c507c0c8f5f7c6eba78430e66aef", + "crm-opportunity/src/test/java/com/crm/opportunity/stage/impl/OpportunityStageServiceImplTest.java": "sha256:9bf60f8d0e11a80007a18eb097443911390b945ac5f84a6053bcf0b0bb49df3d", + "crm-opportunity/src/test/java/com/crm/opportunity/state/OpportunityActionGuardTest.java": "sha256:37cf8f014f5f21a95e4950490350f0899c11eb557b4c8468a21ecdb30ffaccf0", + "crm-opportunity/src/test/java/com/crm/opportunity/state/OpportunityTransitionCmdTest.java": "sha256:1c324adeee25b46d15da0e6e50dda0a9618486d902e98ea14bf1265bebf5db7f", + "crm-opportunity/src/test/java/com/crm/opportunity/state/impl/OpportunityTransitionImplTest.java": "sha256:011836c4d805d7e541d73c00bac999280ee9b5beb13702c05495e2b8bf3136c7", + "crm-preference/pom.xml": "sha256:3c4795bbd8111e3c978151015b03bafe3fad20ba5a10a6b5adab5de2feffc368", + "crm-preference/src/main/java/com/crm/preference/controller/ColumnPreferenceController.java": "sha256:dad97aeaebd8a6c68973a0ca428daa066c93bcc0a50572c5235128a3f420ab17", + "crm-preference/src/main/java/com/crm/preference/controller/SavedViewController.java": "sha256:fea61cf1bd4d869a0970d21504715783f147ad8f9ee2093cfbbf2912abd1820e", + "crm-preference/src/main/java/com/crm/preference/domain/dto/ColumnPreference.java": "sha256:ce36723a5454e36464c10ef3db617079fac7a4ef339a0cc35bb89962e30f0066", + "crm-preference/src/main/java/com/crm/preference/domain/dto/SavedView.java": "sha256:1f9e79a3bb61dd5b470dec6e24cf39286fb5e8c100688d5f78b06c20b9d6ab4e", + "crm-preference/src/main/java/com/crm/preference/domain/dto/SavedViewCondition.java": "sha256:6dec4875b976af07985fa2c5c307ec17d657c37260b1c3491bf7698fc003da1b", + "crm-preference/src/main/java/com/crm/preference/domain/entity/UserColumnPreference.java": "sha256:86e3c2443fea9b32f0e329c4d62c14adb90fe096df55796787e64de4ff667a71", + "crm-preference/src/main/java/com/crm/preference/domain/entity/UserSavedView.java": "sha256:49c54c3cbe6c45bd2f968c71b7db94c525e89837645db80062ceadbbd5e551b4", + "crm-preference/src/main/java/com/crm/preference/mapper/UserColumnPreferenceMapper.java": "sha256:7f6f80a40d2076164d96c0e1a5a7fc1c677bbc5ddeed51a8049031c702534873", + "crm-preference/src/main/java/com/crm/preference/mapper/UserSavedViewMapper.java": "sha256:805ed51cc8588d91f6323c630cd565404a397b4a78223d8d91495da001f650e4", + "crm-preference/src/main/java/com/crm/preference/service/ColumnPreferenceService.java": "sha256:ac6e3f47439cd2800e70987538e77f87876c09fc4305e8891fd6b17e2bd6c76d", + "crm-preference/src/main/java/com/crm/preference/service/SavedViewService.java": "sha256:9089b13e9019675582db6f2c2bb13ded8c19567ac7564147f6a9661d54863037", + "crm-preference/src/main/java/com/crm/preference/service/impl/ColumnPreferenceServiceImpl.java": "sha256:00535d095770ba0c94964035db75e28a2a5df661ea127ae7eef2ba95e0572961", + "crm-preference/src/main/java/com/crm/preference/service/impl/SavedViewServiceImpl.java": "sha256:c4ba1ac5d0bbfc75bf12a4a1f48ed741e99afafd205c2570e167c2c41ec92e04", + "crm-preference/src/test/java/com/crm/preference/service/impl/ColumnPreferenceServiceImplTest.java": "sha256:3f7b43975ae0be929ba66bf08dcfd5bb47f37fb187b665a28d4eb100bae0acb7", + "crm-preference/src/test/java/com/crm/preference/service/impl/SavedViewServiceImplTest.java": "sha256:ff10069e1c695b9114f92d472e7556ae8ad8004295ef1a4329173350de129e71", + "crm-rule/pom.xml": "sha256:73501580daca184cea7e126ba82355c70f0161fef26a16c60d68e811ff7c4ef6", + "crm-rule/src/main/java/com/crm/rule/config/OpportunityRulePermissionInitializer.java": "sha256:1178e6234a7cbbdd9cc446473452bfb9e5665b8217caf09f09aa2f76bac59834", + "crm-rule/src/main/java/com/crm/rule/config/OpportunitySchemeCardTemplatePermissionInitializer.java": "sha256:7ae7c7f89a913bc9f7370a2c73bcbfda1d1f8be1ae5153c573e318ea50f7406e", + "crm-rule/src/main/java/com/crm/rule/config/OpportunityStageTemplatePermissionInitializer.java": "sha256:711e33ca215e0f571d8024ee44238a5ccba64d4911494bb8a515829969a21abd", + "crm-rule/src/main/java/com/crm/rule/config/RegionDataInitializer.java": "sha256:739735b09f76b759be8bd9ce156ca867e17d372cf4f667e2e58dd3a2d4c18261", + "crm-rule/src/main/java/com/crm/rule/config/RulePermissionInitializer.java": "sha256:f1eb40bbf0a70a467bb45a6023ad032e230c07cc709a51c5f1d1b90e2fe358be", + "crm-rule/src/main/java/com/crm/rule/config/SchemeFieldDefInitializer.java": "sha256:f6060f84c3fb4379e1a20e41771ae5db1b53dc50455dcb801129a8d05ac52201", + "crm-rule/src/main/java/com/crm/rule/config/StageTemplateInitializer.java": "sha256:d979d5d499f029cc10f752d80a4f93416a3d1664e93f1f304720018f577571bf", + "crm-rule/src/main/java/com/crm/rule/constant/OpportunityRuleConstants.java": "sha256:60bca9f230f985c6e8e8982351b8c84c211b767bce237d7de031eca05d8c9b33", + "crm-rule/src/main/java/com/crm/rule/constant/RuleConstants.java": "sha256:a6c6cd8db948c97603dbf0767c3ca70205a388ea386f6c2f3e3c3fe40b03de60", + "crm-rule/src/main/java/com/crm/rule/controller/LeadPoolController.java": "sha256:bee1d3e441fc4001533fcb640b606ce3d1c673099da625371d524c55a1d3c73f", + "crm-rule/src/main/java/com/crm/rule/controller/OpportunityPoolRuleController.java": "sha256:f7bd644b8c1d667c6f6580cf52d2f87bd31cc0dba0e8f4968ea5a89a1dc0c4ed", + "crm-rule/src/main/java/com/crm/rule/controller/OpportunitySchemeCardTemplateController.java": "sha256:32c8320d78d141f63532e7ad7bad46489d8c0e071156d1316927da034d8cf894", + "crm-rule/src/main/java/com/crm/rule/controller/OpportunityStageTemplateController.java": "sha256:d9799efccb9e83f2ca1432dce83495f9e0c28305fbfdb037730db53b38d4a85b", + "crm-rule/src/main/java/com/crm/rule/controller/RegionController.java": "sha256:f714716c5ee9e800dbef84294c52b6484adfdf49d84684f171e4ff41c6e7dfc6", + "crm-rule/src/main/java/com/crm/rule/domain/dto/LeadPoolDTO.java": "sha256:d28081fa062762a440248004cec417512dd95964e12fd8ca68f23b718104dcb1", + "crm-rule/src/main/java/com/crm/rule/domain/dto/OpportunityPoolRuleDTO.java": "sha256:abeaa2e6912dc107442a0d1364be2651d323c12f1fdaa66baf1789be6eeb61a2", + "crm-rule/src/main/java/com/crm/rule/domain/dto/OpportunityPoolRuleVersionDTO.java": "sha256:39c3c5bbf5dba97b7067443e15c67e45aede3ec61c997ae85ebbb595b8ccd2e2", + "crm-rule/src/main/java/com/crm/rule/domain/dto/OpportunitySchemeCardTemplateDTO.java": "sha256:96af81f28c2deaf11653dca4ad2348ff86a8d7daeed9d60946bc05a7d48e0e1f", + "crm-rule/src/main/java/com/crm/rule/domain/dto/OpportunitySchemeCardTemplateVersionDTO.java": "sha256:8f8c790e21c2c298c3c08cdc1350cdd1cc5e227d5ed66980aedd2044b35e6ee2", + "crm-rule/src/main/java/com/crm/rule/domain/dto/OpportunitySchemeFieldDefDTO.java": "sha256:1983380b54df3adf5efb356ca8d3fa09c9a40c25b8722e82cd436676971208aa", + "crm-rule/src/main/java/com/crm/rule/domain/dto/OpportunityStageTemplateDTO.java": "sha256:0efe0aa6d80d359bef6dc9884498e1aa912d8c4a98086a000483058ce7231c71", + "crm-rule/src/main/java/com/crm/rule/domain/dto/OpportunityStageTemplateVersionDTO.java": "sha256:6c842fbaa27aa941efc8dde29b13ff4439812f673f95393c202b7fe8345d267a", + "crm-rule/src/main/java/com/crm/rule/domain/dto/PoolBatchFailItem.java": "sha256:510ad672eb50f51bcb863e9ffbb7b92f9256c0838d5d78aa2450881ab877e523", + "crm-rule/src/main/java/com/crm/rule/domain/dto/StageDisplay.java": "sha256:8bc572b10f16ec5d43f6339992f001b3d3e5a5d0eb9782455d64ca7ddd627c30", + "crm-rule/src/main/java/com/crm/rule/domain/dto/SysRegionDTO.java": "sha256:f9d23c44cf69d9e338dc978534278761fdec5c4d3d6cdd87d513e21173696bf1", + "crm-rule/src/main/java/com/crm/rule/domain/entity/LeadPool.java": "sha256:b9d6080a20921b8d8bf75c990461f404aaa4f46c47adc00b16d25cd6805d939e", + "crm-rule/src/main/java/com/crm/rule/domain/entity/LeadPoolMember.java": "sha256:3607a9cddab1bb2d6ceca3474f9615895d6b9aa3ff7b3915c50ed2cb0fe42160", + "crm-rule/src/main/java/com/crm/rule/domain/entity/LeadPoolProvince.java": "sha256:a0a0ac3d9ee18b0db28ae092fee408303f3d30078ec3e9695b6b3350eda1e25b", + "crm-rule/src/main/java/com/crm/rule/domain/entity/LeadPoolRegion.java": "sha256:d2e10869f8a0929d1f814ecf2e0464cf14c88d51f699c7195872c9f053038e82", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunityPoolRule.java": "sha256:33b376c587bbb224b368eb26609fec8db711b80d6c18e22a57bf4f88c1ad17df", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunityPoolRuleDept.java": "sha256:94b7fcc00e0cc34ca9ff3c035c6887bd50ddfaa17eedc86dfafceae98bf0a6c9", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunitySchemeCardField.java": "sha256:82f26abaf16d14c2f45d3ffd7b591770ec0411ee8cf2a33f17474afacbe2e017", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunitySchemeCardTemplate.java": "sha256:d2569cdc155571fc4b5be01c64347ea9767b02e2f0686f9760419e15854c81ab", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunitySchemeCardTemplateDept.java": "sha256:b568de05cb0521ba97a3849711de14067c30b671c371ca9919f65c2692928767", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunitySchemeFieldDef.java": "sha256:448af7ba2cfe886dcd620a12fe748e5852aa1af3937fc99a64e9565c4bc271a7", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunityStageNode.java": "sha256:5fa30531f5a1b5738e1788071ace59c7af4224c2ed58218477dfccf4fe3859d4", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunityStageTemplate.java": "sha256:b11ec6c1e0c7c0b29548276c14187e55f5d1e637fbf8d9ae435eea5a6756e77b", + "crm-rule/src/main/java/com/crm/rule/domain/entity/OpportunityStageTemplateDept.java": "sha256:6431232c7336146783916ff3b07a2ddeddbbb87cc533974fe2530d6635cccaba", + "crm-rule/src/main/java/com/crm/rule/domain/entity/SysRegion.java": "sha256:c404963587bc4b5bd928546bbe9602c47b64dcb4a99567a9c72e5f219193f379", + "crm-rule/src/main/java/com/crm/rule/domain/enums/PoolBatchFailReason.java": "sha256:dbf37539bb7812e29bf439a1897fcd2748bd0065fc8a8b6b7ee07b21be530624", + "crm-rule/src/main/java/com/crm/rule/domain/param/LeadPoolPageParam.java": "sha256:adf0365d8206e3c8b22172a1ec128d8ab78645c1cb725d05210aa49c58dc8f4d", + "crm-rule/src/main/java/com/crm/rule/domain/param/OpportunityPoolRulePageParam.java": "sha256:923315441d65a8320bb75e0dd5c7756df2a9aa4cf690719b9a287a27c8c7807e", + "crm-rule/src/main/java/com/crm/rule/domain/param/OpportunitySchemeCardTemplatePageParam.java": "sha256:3399d25d53400653fa300ab6e1e8c1691bedbcc6908e9c67511721a63e3b7303", + "crm-rule/src/main/java/com/crm/rule/domain/param/OpportunityStageTemplatePageParam.java": "sha256:1ce3d461c02dccb1cabf89f622092ed02c69760ba737c41ecb28424a2e4b0479", + "crm-rule/src/main/java/com/crm/rule/event/PoolChangedEvent.java": "sha256:2de9939be11fff335c9df6c0f732478f77603dc21c58e7147a8a47c9986caeb8", + "crm-rule/src/main/java/com/crm/rule/mapper/LeadPoolMapper.java": "sha256:9745e2f22c2c83b77d2412ebe7a1372aad3470d14da0f03285859941a8424ad3", + "crm-rule/src/main/java/com/crm/rule/mapper/LeadPoolMemberMapper.java": "sha256:091606d23adb43c53b677ca096821fb252fde367ee184f274f9243159f6898e2", + "crm-rule/src/main/java/com/crm/rule/mapper/LeadPoolProvinceMapper.java": "sha256:925ac43341bdda92a5d4244d6bf352ec30cdecf08d4083d535b17f5e2612df9e", + "crm-rule/src/main/java/com/crm/rule/mapper/LeadPoolRegionMapper.java": "sha256:d203583d37f51283eaf680643709c882688de3124d0db1cb12eb9c2bd7b3e23b", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunityPoolRuleDeptMapper.java": "sha256:6aa7ff4d594703482e789192c0ef10660f496f72f8bdf1a10d0073d3b6f2f346", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunityPoolRuleMapper.java": "sha256:ecb2e8fa06b0fa10bb69771c4df7b76ab2a4b120b6f8aa8b8e950f7b2879a179", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunitySchemeCardFieldMapper.java": "sha256:0990e4cebbfff2f19c303c80c2570ab0d526673048357b4692763a4e21a8ada8", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunitySchemeCardTemplateDeptMapper.java": "sha256:7eddd9d31373d79383b2bd14dca3ea7530223910cf580efb18324ceacda0e69a", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunitySchemeCardTemplateMapper.java": "sha256:1324b7e7a81b8e7c5d0b9f000c290558653c54c5058a275a893b83679fce0a02", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunitySchemeFieldDefMapper.java": "sha256:6fee929fc2f779aa306fd6ca4df60dbcdb3b09d5dc82d9192fdaec93354735fb", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunityStageNodeMapper.java": "sha256:96adb785c222c4f3b8e747fec5aa13297f12ebfa7f037da7b43254b1cb2adeb5", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunityStageTemplateDeptMapper.java": "sha256:ed36422bb4f56ca8b11b74e7b443e669383e512cc0835a039364a256ff0713c4", + "crm-rule/src/main/java/com/crm/rule/mapper/OpportunityStageTemplateMapper.java": "sha256:98deed4a4be18a5f916cc4e7b27cf56624e3c33a837ad2f563f82f8220167f63", + "crm-rule/src/main/java/com/crm/rule/mapper/SysRegionMapper.java": "sha256:dcac2b33a2df44568a6771f2db9ee98714d877f08efeb0dfd5216d716fe83991", + "crm-rule/src/main/java/com/crm/rule/port/outbound/PoolLeadOccupationPort.java": "sha256:2fb3c98ea713846d887fd492476004dceff0672577ce3f21c2010e5b947b4931", + "crm-rule/src/main/java/com/crm/rule/service/ILeadPoolService.java": "sha256:6e8b03b5f2e58395dea4257ca87e26a5f939e6bbc959721e6facd2983ea85cb4", + "crm-rule/src/main/java/com/crm/rule/service/IOpportunityPoolRuleService.java": "sha256:b3348bdd0519e2aea1e6c55ba4237dd9a92b4be1e40b770d95838d57d230f0d0", + "crm-rule/src/main/java/com/crm/rule/service/IOpportunitySchemeCardTemplateService.java": "sha256:67446206742eb35d6c2a21c355c109ce9f40ca85b20829969346e78cd1ee0b6c", + "crm-rule/src/main/java/com/crm/rule/service/IOpportunityStageTemplateService.java": "sha256:8c42dccaafe5bd445f27d88250e6b59046cdf3efc26bbdb0fb0a996cd08d5185", + "crm-rule/src/main/java/com/crm/rule/service/ISysRegionService.java": "sha256:569c320bc6cee349334b3a05b0abe5cb0cd7c0e5179a1d6a74f04da6f88ccbc8", + "crm-rule/src/main/java/com/crm/rule/service/OpportunityPoolRuleMatcher.java": "sha256:a6596fa5cfdd60c7f1083b2cee6aa2f62e0d16dfeb9c646bdf0d1ce652a203b8", + "crm-rule/src/main/java/com/crm/rule/service/PoolGeographyStore.java": "sha256:0f7fcaaa7f8e342d4d03ab9f6a30d202c492e7b4b1729f54ba04710393d75308", + "crm-rule/src/main/java/com/crm/rule/service/PoolMemberStore.java": "sha256:a82c6b3b61203848793cad8ddb9202ec4c9c998f29e98756c7b587f43cabb234", + "crm-rule/src/main/java/com/crm/rule/service/impl/LeadPoolServiceImpl.java": "sha256:d98b4abd562c8fef4dc118394b1500e46f7ced9d6f9e171b057b4628f673aa2b", + "crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityPoolRuleMatcherImpl.java": "sha256:f3ffe1282381e5aa59f1e5114ab47bcdcbe6345a320b1cd06c844a258405674d", + "crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityPoolRuleServiceImpl.java": "sha256:46a1ed1ea8093876219544cf5987be6f23c3a92db9d1bc9072fa2d65701b180a", + "crm-rule/src/main/java/com/crm/rule/service/impl/OpportunitySchemeCardTemplateServiceImpl.java": "sha256:ae0b25741753a45cdf3084c8c2b46f842c5d6f964b1e9fb9dc61cfb90000e074", + "crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityStageTemplateServiceImpl.java": "sha256:4572b0499c61019cc8e60e298996fe72ef5e44b862dc5743659ebe33912730e0", + "crm-rule/src/main/java/com/crm/rule/service/impl/PoolGeographyStoreImpl.java": "sha256:373a34a991fd4926d8f4b74931ad8229fcf9ad0d296d020b087444d172e6dcc8", + "crm-rule/src/main/java/com/crm/rule/service/impl/PoolMemberStoreImpl.java": "sha256:88aedc80813f19e47648f17bed1a3b4e7ec2ba6ad3ed50edc1b03cca4f7e1714", + "crm-rule/src/main/java/com/crm/rule/service/impl/SysRegionServiceImpl.java": "sha256:4cac672fd48073b935fa40034e7476523917ac93387f9f147ebf76dedcd98e91", + "crm-rule/src/main/java/com/crm/rule/service/impl/VersionedConfigSupport.java": "sha256:69a54997f744fd4b5a964f635b5a8f14df6790c86970af019f1afcd8dbd505b9", + "crm-rule/src/test/java/com/crm/rule/config/RegionDataInitializerTest.java": "sha256:78c3254185aec09d8879027a352fa2d01dc890a63bb151988ffa3eee3a0fe1f6", + "crm-rule/src/test/java/com/crm/rule/config/SchemeFieldDefInitializerTest.java": "sha256:26987fbd248c596267abdd2e448617c917839af1b86c65d34cdb9ef97e311be3", + "crm-rule/src/test/java/com/crm/rule/config/StageTemplateInitializerTest.java": "sha256:9acd14075c909851c8451b6b0e0758aadda57ae2ecd52468d8e2d3a4ad24f2b5", + "crm-rule/src/test/java/com/crm/rule/service/impl/LeadPoolBatchDeleteTest.java": "sha256:bbaadc0de1f0ddf50b33743ebf00b603a90bd46e8a79c8ebc565ed6dd1a8c0cd", + "crm-rule/src/test/java/com/crm/rule/service/impl/LeadPoolServiceImplTest.java": "sha256:8ebcfd7e8e1c481e8524a1d230cd8a7c8071b3693759787ebed952f0ac04542a", + "crm-rule/src/test/java/com/crm/rule/service/impl/OpportunityPoolRuleMatcherImplTest.java": "sha256:570b50da3715b3e663dc9d1f4f07fd243cfb21d9006d5ffa98cdf1b1c47dab44", + "crm-rule/src/test/java/com/crm/rule/service/impl/OpportunityPoolRuleServiceImplTest.java": "sha256:72fe94505f97865f26acbfb0d36ea89456f2951cf01e42472c36cdfb3326a52a", + "crm-rule/src/test/java/com/crm/rule/service/impl/OpportunitySchemeCardTemplateServiceImplTest.java": "sha256:ad7e8ac4e5188ecd491a6a6404aa66ed8b3128c24109f9012e684b056e4a0083", + "crm-rule/src/test/java/com/crm/rule/service/impl/OpportunityStageTemplateServiceImplTest.java": "sha256:5791337fe64fc8762c9c63a0264cf66dca9977c2d6bd235315ead6505a05cde9", + "docker-compose.yml": "sha256:6e5ea6a3d2442cddf87a8dae62c97ff43585f5c4f6f8daab14a673a94a85913c", + "pom.xml": "sha256:abe417c4b369d6a649f52b15948497c086cb2cd05371cf0b6bdb9a2a9d5c4b2c", + "tmp/china-division/package/package.json": "sha256:a8b0c3d1f5e480c51b6726a5456f4b62188b5f4a54d719075d6166f24cae9fbd" + } + }, + "version": 2 +} \ No newline at end of file diff --git a/.lithe/toolchains/requirements.json b/.lithe/toolchains/requirements.json new file mode 100644 index 0000000..beb8739 --- /dev/null +++ b/.lithe/toolchains/requirements.json @@ -0,0 +1,29 @@ +{ + "toolchains": { + "project-jdk": { + "java": null, + "minimumVersion": "17", + "preferredVendor": null, + "type": "java", + "version": null, + "wrapper": null + }, + "project-maven": { + "java": "project-jdk", + "minimumVersion": null, + "preferredVendor": null, + "type": "maven", + "version": null, + "wrapper": null + }, + "project-node": { + "java": null, + "minimumVersion": null, + "preferredVendor": null, + "type": "node", + "version": null, + "wrapper": null + } + }, + "version": 1 +} \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..969b61a --- /dev/null +++ b/.project @@ -0,0 +1,28 @@ + + + crm + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + + + 1788308974700 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/.scratch/customer-module/REVIEW-HANDOFF.md b/.scratch/customer-module/REVIEW-HANDOFF.md new file mode 100644 index 0000000..1f6fbb2 --- /dev/null +++ b/.scratch/customer-module/REVIEW-HANDOFF.md @@ -0,0 +1,206 @@ +# 客户模块(A4)后端 spec — 复核交接清单 + +> 用途:交付给一个**全新的、无本会话上下文**的会话做第二轮 grill / 独立复核。 +> 上一轮共 19 个 grill 决策(D18–D29 + D4-rev1/rev2)已全部由用户拍板。**本清单的目的不是报喜,而是把最可能被推翻的点提前摊开,供下一轮挑战。** + +--- + +## 0. 怎么读这份清单 + +- **权威交付物**:`.scratch/customer-module/map.md`(决策索引 D1–D29,自包含)+ `issues/NN-answer.md`(各票详细决议,自包含)。 +- **原型出处**:`D:\code\crm-需求梳理\lanhu-mcp\data\axure_extract_bade4454_screenshots\a4-*.txt`(蓝湖 A4 页面已本地提取文本)。 +- **对称参照**:`.scratch/opportunity-module/map.md`(已完成的商机图,本图大量对称其范式)。 +- **领域上下文**:`crm-customer/CONTEXT.md`、`crm-lead/CONTEXT.md`、`crm-opportunity/CONTEXT.md`。 +- **协作规范**:`docs/agents/issue-tracker.md`、`docs/agents/triage-labels.md`。 + +**复核建议**:先读 `map.md` 的 Decisions 区(D1–D29,约 5 分钟),再逐个挑战下面「第 4 节薄弱点」。**不要**因为 map 里写了"已拍板"就跳过——这是本清单特意要求的。 + +--- + +## 1. 本图范围(D2) + +**做**(8 大块):客户核心(实体+查重+公海+总览+我的客户+三视图+新增/编辑+导入)、联系人(CRUD+全局搜索+电话查重)、客户交割、客户规则族(超期提醒 + 查重设置)。 + +**留 seam(依赖未建)**:报备成功链路(方案卡已推送/A5 回调)→ 客户「重潜」阶段(D20 改票 B,本期潜在→重潜不触发);A5 项目模块 → 客户「已成交」阶段、「关联项目」详情页签、汇总卡「合同总额·累计回款」。 + +**划出(Not-yet / Out-of-scope)**:战略协议、联系人图谱(职务二级联动)、**客户查重列表+客户合并(A4-6)**、企查查/OCR 外部服务内部实现(只定契约 seam)、OCR 快捷新增、联系人导入。 + +--- + +## 2. 已冻结的决策(D1–D29 速览) + +| # | 决策要点 | 原型/出处 | +|---|---|---| +| D1 | spec-only,对称商机图;做完进 /implement | — | +| D2 | 作用域三档划分(见上) | — | +| D3 | 双轴建模:轴1 归属(owner 空=公海)正交 archive_status;轴2 阶段三态只前进 | a4-2-1 | +| D4-rev1 | 客户规则族只含「超期提醒 + 查重设置」;星级/阶段退回硬编码 | a7-2-3 | +| D4-rev2 | 查重设置 = crm-rule 全局单例可配置,本期只消费于新增/编辑内建查重 | a7-2-3-查重 | +| D5 | 联系人 = crm-customer 独立表 `customer_contact`,权限继承客户 | a4-4 | +| D6 | 内建查重口径(后被 D4-rev2 改为可配置) | a4-3-3 | +| D7 | 客户主表字段八区 + DataScope + 索引(无孤立单大写陷阱) | a4-3-3 | +| D8 | 超期提醒:crm-rule 单例配置 + 待发通知表 + Job 只落待发 | a7-3-3 | +| D9 | 三视图/公海/总览 = 逻辑视图,内置视图不入库 | a4-1-1/a4-2-1 | +| D10 | 分层查重 + CustomerDedupService + 企查查 seam | a4-3-3 | +| D11 | 阶段只前进 CAS(三态字段/机制保留);重潜触发本期 seam 化不实现(D20 改票 B) | a4-4 | +| D12 | 联系人 CRUD + 全局搜索;电话查重软提示(D26);来源字典(D27) | a4-4 | +| D13 | 详情 8 页签 + 跟进 append-only + 字段级 oplog;卡片占位(D28) | a4-4 | +| D14 | 建 crm-customer/CONTEXT.md;依赖方向 opp→customer | — | +| D15 | 交割两段式;直属总监(D22);完成判定(D23) | a4-7 | +| D16 | 导入三模式 + 异步 + 行级部分成功;多行默认(D24) | a4-3-2/a4-3-5 | +| D17 | 超期提醒规则表/Job 细节 | a7-3-3 | +| D17b | 保存视图复用平台,3 scope_key;enter_pool_time 可排序(D29) | a4-1-1 | +| D18 | customer_type = crm-dict 字典,18 值(含4甲方+14非甲方) | a4-3-3 弹窗 | +| D19 | 归档=删除(软删);去掉「已被合并」态;列表筛选 default 排除已归档 | a4-2-1 | +| D20 | 重潜本期**不触发**、只留 `promote` seam(等「提交并报备成功」链路,不用「已提交」凑合) | a4-4-1 | +| D21 | 相似判定 seam 化;一期最简 = MySQL ngram FULLTEXT;阈值从配置读 | a7-2-3-查重 | +| D22 | 直属销售总监 = 组织架构推导(dept.leader 向上找销售总监);不改 crm-auth | a4-7-1 | +| D23 | 交接单完成 = 自动(assignedCount==totalCount);无手动按钮 | a4-7-1 | +| D24 | 导入多行匹配同一客户默认「重复时不导入」(重复组全失败) | a4-3-5 | +| D25 | 超期提醒锚点 = 成为「有主」时刻;公海 NULL 不计时;领取时赋锚点 | a7-3-3 | +| D26 | 电话查重=**可配置软提示**(不建唯一索引);同客户内电话不重复为表单级校验 | a7-3-3-2 查重设置 | +| D27 | contact_source 字典种子 = 组会/组局/转介绍/其他 | a4-4-1 | +| D28 | 汇总卡占位一律渲染 + 显"--"(不显 0,不留白) | a4-4-1 | +| D29 | 公海「进入公海时间」列保留 + 加入排序字段池 | a4-1-1 | + +--- + +## 3. 为什么我信不过自己——复核方法论 + +用户的担忧是对的。上一轮我犯过**SOP 违规**(票 02–11 未经 grill 就标记 resolved),后来被迫全部重开重问。因此第二轮复核请遵循: + +1. **每个决策都要能找到"证据"**:要么是原型页面(a4-*.txt 的原文文字),要么是用户当时明确说过的选型(如 Q8 的 18 值、Q16 的"全部电话")。**找不到证据的就是我编的,请当误入标注出来。** +2. **每个"定稿"都可被推翻**:~~D20(重潜触发点=已提交…请重点挑战)~~ **D20 已于 20260902 复核翻案为 B(本期不触发、留 seam),见 §4.1**、D22(组织架构推导在无组织数据/多领导时的降级行为我写的是"阻断",但用户可能预期"降级给普通接收人")。 +3. **决策之间的一致性**:D8/D25(锚点)、D19/D3(归档)、D4-rev2/D6(查重口径)、D12/D26(电话唯一)、D4-rev1/D7(星级)——若两条决策互相打架,是我没对齐。 + +--- + +## 4. 我最担心的薄弱点(请下一轮重点挑战) + +### ✅ 4.1 D20 重潜触发 —— **用户拍板 B(20260902):本期不触发、只留 seam,翻案** +- 我选了「保守」口径:方案卡状态到「已提交」就触发客户重潜。 +- **风险**:原型 A4-4 的重潜定义其实写着「关联的任一方案卡**提交并报备成功**」。我因为「报备/推送链路本期不可达」而收窄成「已提交」。用户可能完全接受,也可能说"那就等报备成功再触发,本期不实现重潜"。 +- **请问**:报备链路不可达时,是「用已提交凑合触发」还是「本期干脆不实现重潜、只留 seam」? +- **第三轮 fresh 复核(20260902)**:`a4-4-1`(8-26 fresh)L359/L412 原文明文「任一方案卡**提交并报备成功**后 → 重潜客户」= 两个条件。用「已提交」单条件凑合会造出「提交了但报备没成功、客户却已重潜」的错误状态。 +- **✅ 已拍板(用户选 B,20260902)**:本期**不自动触发潜在→重潜**,客户阶段实际只有「潜在」;`CustomerStagePort.promote` seam(契约/只前进 CAS/幂等)全部保留,等报备成功链路(方案卡「已推送(3)」或 A5 回调)就绪再接入调用。**不用「已提交」凑合。** map.md D20/D11 + 票 05-answer + CONTEXT.md 已同步。 + +### ✅ 4.2 D22 直属销售总监 —— **已确认(grill Q12 拍板 A),复核补全「调动/区域调整=下拉选」分支** +- 复核发现:票 08-answer L15/L23 已把口径写全 = **离职=组织架构推导自动定唯一总监**(无/停用/多条→阻断提示管理员);**岗位调动/区域调整=在本人启用的直属总监内下拉选(不阻断)**。 +- 原 HANDOFF 4.2 段只描述了「离职=推导阻断」这**一半**,漏了下半句。**非决策遗漏,是记录不全**。修正:阻断仅离职专属;调动/区域调整为可选下拉,不阻断。 +- **✅ 已确认(20260902)**:无需翻案,仅补齐 map.md D22 描述(补充「调动/区域调整=下拉选、离职才阻断」)。不改 crm-auth、不加 `superior_user_id` 维持原判。 + +### ✅ 4.3 D4-rev1 把「星级/阶段设置」踢出客户规则族 —— 阶段 ✅A口径 / 星级 ✅已三点确认(20260826) +- **阶段**:第二轮复核拍板 **A 口径** —— 客户阶段=固定三态(潜在→重潜→已成交),只前进不回退,**不建 `a7-2-3 客户阶段设置` 页**。D4-rev1 对阶段部分**正确,保留**。 + - 依据:`a4-4-1` 详情页 3 次强调「阶段由业务结果驱动、阶段条不可点击修改、只前进不回退」,与 A 口径一致。 + - 注:`a7-2-3 阶段设置` 页与详情页内部矛盾(可增删/排序/迁移 vs 只前进不可配),已裁决按详情页(A 口径)走,设置页视为原型残留。 +- **星级**:✅ **已确认原判成立(第二轮复核,用户拍板 20260826)**。我上一轮引用「关系星级名称+描述由管理员自定义」是**基于过时原型缓存**(`a7-2-3_星级设置_.txt` 为 2026-07-20 旧快照,仍是「潜在/高潜/合作/重点/战略 + name/desc 可配」老口径)。**新原型已更新**:`a4-3-3-1_新增客户信息.txt`(2026-08-26)L91-106 显示客户星级、关系星级**均为「一星~五星」两个普通下拉字段**,无 name+desc 可配语义。**用户三点确认**:①客户星级/关系星级=两个 1~5 星字段;②D4-rev1「星级退回硬编码枚举 1~5」**确认成立**(非翻案);③星级**彻底不进客户规则族**(`a7-2-3 星级设置`页当作不实现,同阶段设置页处理)。**我上一轮「关系星级必须可配」的纠偏作废。** + +### ✅ 4.4 D26 联系人电话查重 —— **用户拍板 A(20260902):降级为可配置软提示,翻案** +- 用户拍板选了「全部电话唯一(含座机/短号)」——比我的推荐(仅手机号 11 位唯一)更强。 +- **风险**:座机/短号撞号概率实际远低于手机号;但**同一个公司多个联系人的座机可能共用一个总机号**,这会误伤。强制唯一可能让"一个公司两个联系人都填了同一个座机总机"这种正常场景无法保存。 +- **请确认**:是否真的对座机也做全局唯一?要不要豁免「座机且同客户」? +- **第三轮 fresh 铁证(20260902,重新提取 8-26 原型 HTML)——D26「电话全局唯一」被查重设置页正面推翻**:新版 `a7-3-3-2_客户管理设置_客户查重设置_.txt`(源 HTML 2026-08-26)**逐条否定电话唯一约束**: + - 「**总开关仅控制客户名称、联系电话的重复提醒**」——电话查重是「**提醒**」不是「约束」; + - 「**统一社会信用代码为系统强制规则,不受总开关影响,不提供关闭开关**」——**只有信用代码是唯一强制**; + - 通用判断规则:「统一社会信用代码精确命中时,**禁止创建客户**;客户名称或联系电话任一命中时,**提示疑似重复客户**」——电话命中只**提示**、不阻断; + - 「联系电话:**支持单独开启或关闭**;匹配方式固定为精确匹配;命中时提示疑似重复」——电话查重可被管理员整体关闭。 +- **结论**:原型口径下电话查重 = **可开关的软提示(提示疑似重复,不禁止创建)**,与 D26 主张的「**电话全局 DB 唯一索引 + 撞号即拒绝保存**」**直接冲突**。D26 是**过度设计**:把原型的软提示误升级为硬约束。 +- **座机豁免问题自动消解**:既然电话查重本来就只是"提示疑似重复、不禁止","多个联系人共用总机座机导致无法保存"的误伤场景根本不存在——因为原型从不因电话撞号阻止保存。 +- **新增页佐证**:`a4-3-3-1_新增客户信息.txt`(8-26)§3.5「联系电话存在时…按电话值查重;电话值完全相同时禁止新建」仅约束**同一表格内两行**(同一客户内联系人不能填相同电话),**不是跨客户全局唯一**。 +- **建议**:D26 从「电话全局唯一索引」**降级为「可配置的电话查重提示」**(跟随 crm-rule 客户查重设置单例的"联系电话"开关),DB 层**不建电话唯一约束**;仅保留同客户内联系人电话不重复的表单级校验。**待用户拍板改票 06 D26。** +- **✅ 已拍板(用户选 A,20260902)**:采纳建议。D26 改票:`uk_contact_phone(phone)` 唯一索引**撤销**;电话查重=可配置软提示(跟随 crm-rule 查重设置「联系电话」开关);仅保留同客户内联系人电话不重复的表单级校验。**map.md D26/D12 已同步改口径。** + +### ✅ 4.5 D18 customer_type 18 值——**已证实,非编造(第二轮复核,证据在 `a4-1-1`)** +- **原判断撤销**:第一轮我 grep 只搜 `a4-3-3-1` 新增页,漏了 `a4-1-1` 公海列表,误判"部分编造"。 +- **决定性证据**:`a4-1-1_客户公海列表.txt` L45-63 是客户类型筛选下拉的**完整展开枚举**: + `运营商 / 土建总包 / 机电总包 / 装修总包 / 教育局 / 投资(代建)公司 / 设计院 / 集成商 / 招标代理 / 咨询造价公司 / 甲方(企业用户)/ 甲方(普教)/ 甲方(中高职)/ 甲方(政府机关)/ 竞争同行 / 个人用户 / 操手 / 集成商(子客户)` = **17 具体值 + 1"全部客户类型"占位 = 18 值**。 +- 与票 02 列表逐字一致(含"投资(代建)公司""集成商(子客户)")。**消号,D18 成立**。 +- **第三轮 fresh 复核(20260902,重新提取 8-26 原型 HTML)**:新版 `a4-1-1_客户公海列表_列表视图_.txt`(源 HTML 时间戳 2026-08-26)里客户类型筛选下拉**未展开**,文本提取只捕获表格样例行的 4 个值(甲方(中高职)/甲方(企业用户)/甲方(政府机关)/甲方(普教)),**全部落在旧 18 值集合内**,无新增/删除迹象;`a4-1-1` 列表视图 data.js 仅含 `甲方(中高职)` 单元格值,18 值枚举不在页面级资源。**新原型正面印证决策架构**:`a4-3-3-1_新增客户信息.txt`(8-26)需求文档「枚举与人员数据」明确「**下拉枚举只加载后台配置的启用项**」「已停用枚举可在编辑场景回显,新增时不可选」——即客户类型 = **后台配置字典**,与 D18「crm-dict 字典分组、非硬编码」完全吻合。**结论:D18 架构决策获新证据强化;18 个具体种子值来自 7-20 枚举、新原型未反证(样例值均在集合内),种子值列表仅影响初始化脚本、不影响架构。D18 ✅ 保持。** + +### ✅ 4.6 D24 导入「重复时不导入」默认 —— 已确认(grill Q14 用户拍板 A),非缺陷 +- 我默认「重复时不导入」(重复组全失败)。但**很多导入场景用户是想"更新老客户"的**,默认不导入可能让大批行失败。 +- **请确认**:默认到底该是「不导入」还是「更新」?还是按导入方式(仅新增/仅更新)各自定? +- **✅ 已确认(grill Q14,用户拍板 A)**:文件内多行匹配同一客户,`覆盖导入`/`重复时不导入` 互斥,**默认「重复时不导入」**——重复组全部标失败不写入,用户看失败明细处理。覆盖模式作为可选项存在但不默认。 + +### ✅ 4.7 D21 ngram FULLTEXT —— 已确认可用(MySQL 8.4.8) +- 一期最简用 MySQL ngram 分词器(token_size=2)。**ngram parser 是 MySQL 5.7.6+ 功能**,且对中文分词效果一般(会切出大量无意义二元组)。 +- **请确认**:项目 MySQL 版本是否 ≥5.7.6?若偏低,ngram 不可用,得回退 LIKE + Levenshtein(或我提过的 Jaccard)。 +- **✅ 已确认(20260902)**:项目 MySQL = **8.4.8**,≥5.7.6,ngram 分词器可用,无需回退。 + +### ✅ 4.8 D17b 保存视图复用平台 —— 已确认(`user_saved_view` 支持多 scope_key) +- 我复用现有保存视图平台并假设它支持多 scope。**请核对** crm-auth / 现有平台的 `user_saved_view` 表结构和 scope 机制,确认"公海/总览/我的客户"各存一套视图不会串。 +- **✅ 已确认(20260902)**:`user_saved_view` 平台支持多 scope_key(每入口各存一套),公海/总览/我的客户各自保存不串。 + +### ✅ 4.9 D9 视图「内置视图不入库」 —— 已确认(逻辑视图,内置硬编码) +- 我写内置视图(如"我的客户-未跟进N天")硬编码不入库,用户自定义视图才入库。但这个边界(哪些算内置、哪些算自定义)我没问用户,是拍脑袋定的。 +- **请确认**:哪些内置视图是必须的?用户能不能改/删内置? +- **✅ 已确认(20260902)**:三个 workspace(公海/总览/我的客户)= 逻辑视图,靠 `@DataScope` + `owner_user_id IS NULL` + `archive_status` 过滤区分,**不建独立表**;内置视图(含常用检索)硬编码不入库,用户自定义视图才入库,边界成立。 + +--- + +## 5. 需要用户明确但**我可能漏问**的点 + +1. **公海 vs 我的客户是否允许"重复看到同一客户"**:一个客户在公海,同时又被我领了,会不会同时在两个列表出现?(我默认:有主就只在我的客户,公海只出无主,二者互斥。) + - **✅ 原型铁证已定向(20260902)**:`a4-1-1_客户公海列表_列表视图_.txt`(8-26)领取确认弹窗 L327/329 明文「领取后,该客户将**归属当前账号**,并**从客户公海移除**」;批量领取 L341-347 同理「领取后,以下客户将统一归属当前账号,**并从客户公海移除**」。 + - **结论**:**§5.1 消号**。公海与我客户**互斥**(领取=移出公海,归属有主)。D3(归属⊥归档双轴)+ D9(公海=owner IS NULL 逻辑视图)原判**正确,维持**。无需拍板。 + - **区分「关注」**:公海列表有「关注/取消关注」(个人关注状态,如 L89 取消关注 / L129 关注),关注≠领取,**不改变归属**,客户仍在公海。「我关注的客户」是检索维度,可含公海客户,但不影响互斥模型。 +2. **客户能否被多个负责人共同跟**?我默认单负责人(owner_user_id 单值)——但原型有"团队成员"页签,可能暗示多协作者。**这是竞品常见分歧点,我没问。** + - **✅ 原型铁证已定向(20260902)**:`a4-4-8_客户详情_团队成员_.txt`(8-26)需求说明 **11.1 角色与管理权限** 明文「**每个客户只允许 1 名负责人,其余成员均为协同人**(如销售协同人、方案工程师等)」「协同人可查看对应客户的信息」。团队成员页签展示角色列(负责人/协同人)+ 添加成员(搜索多选、不可重复)+ 删除成员(二次确认、仅撤销后续访问权限、不影响历史记录)。 + - **结论**:**§5.2 消号**。客户**单负责人**(owner 单值)+ **多个协同人**(团队成员表),协同人可见但非负责人。D3/D5/D7 单负责人原判**正确,维持**;「团队成员」页签 = 负责人+协同人列表,非多负责人。无需拍板。移除「多负责人」这一待确认项。 +3. **删除语义**:D19 已归档=软删,但「归档」按钮 vs「彻底删除」——有没有彻底物理删除的入口?我默认没有(只有归档)。 + - **✅ 原型证据已梳理(20260902)**:客户「删除」= **逻辑删除**(`deleted` 标志),无前端直接"删除"入口、无物理删除。依据: + - `a4-4-1_客户详情_客户信息_.txt` 操作区仅「归档」(L127)+「标记重点客户」,**无"删除"按钮**; + - `a4-4-1_客户详情_已归档客户_.txt` 提供「恢复」(L47)+ 归档条件(L325-335:有进行中商机/未完成项目/待审批流程/未履约合同/未结清款项→不允许归档;归档不填原因不审批直接执行;归档成功取消跟进提醒); + - `a4-6-3_客户合并.txt` L323「合并成功后保留主客户,将其他已选客户**标记为已合并并执行业务软删除,不做物理删除**」; + - `a4-3-3-1_新增客户信息.txt` L690 / `a4-6-1` L315 查重范围 = 「全部**未删除**客户」→ 存在 `deleted` 逻辑删除标志。 + - **建议**:D19 归档=软删(`archive_status=已归档`,可恢复原判**维持**);客户`deleted` 标志 = **系统级软删除(合并/数据治理用,非前端功能)**,前端不暴露"删除客户"入口。**三态档案(有效/已归档/已被合并)+ deleted 软删标志并存**,需确认 deleted 是否仅合并时由系统置 1(而非用户手动删)。**此项需你拍板:客户删除入口是否彻底不做?** + - **✅ 已拍板(用户选 A,20260902)**:**一期只做归档**——不提供任何前端"删除客户"入口;用户侧只有归档(软删可恢复)+ 合并(源客户系统软删,且合并本体 Out-of-scope);`deleted` 标志仅系统在合并时置 1,**不开放手动删除、无前端删除按钮**;DB 不做物理删除。**票 02-answer(archiveStatus 三值→两值去掉已被合并、deleted 补充说明)+ 票 01-answer 已同步。** +4. ~~**导入时的邮箱/电话查重**~~ **已解决(D26 翻案 20260902)**:D26 电话不再唯一,导入时跨客户电话撞号=软提示疑似重复、不阻断(列明细,跟随查重设置开关);同一文件内同客户电话相同两行=该行失败。已回填票 09-answer。 +5. **操作日志保留时长**:customer_oplog 字段级日志,有没有保留期/归档策略?我默认无限期。 + - **✅ 原型证据已定向(20260902)**:`a4-4-8_客户详情_操作日志_.txt`(8-26)**只强调审计属性、未提保留时长**——L250「日志生成后**不可修改或删除**」,L232「仅记录提交成功后的业务变更」,L188 操作类型固定枚举(新增/修改/归档/恢复/关联/取消关联/状态变更/负责人变更/添加成员/移除成员/合并/导入/导出/敏感信息查看/系统操作)+ L199 字段级「旧值→新值」。即原型把操作日志定位为 **append-only 审计日志**。 + - **结论**:保留时长**原型无要求 = 技术决策**(非规格缺口)。**已拍板(用户选 A,20260902)**:`customer_oplog` **无限期保留**——① 审计日志要求完整、不可删改(L250),保留期会截断审计链;② 字段级日志量可控(客户主记录操作频次低);③ 无清理逻辑最简、无运维负担。**票 07-answer 已补保留策略行。** + +--- + +## 7. 本轮复核结论(20260902,fresh 原型第三轮) + +**复核方式**:对 4 个 7-20 过期快照页(a7-3-3-2 查重设置 / a7-3-3-1 超期提醒 / a4-4-1 客户信息 / a4-1-1 公海列表)用本机 Lanhu Python MCP 重新提取 **8-26 fresh** 文本,逐项对照。 + +**§4 九个薄弱点 —— 全部闭环**: + +| 项 | 结论 | 拍板 | +|---|---|---| +| 4.1 D20 重潜触发 | **翻案**:原型=「提交**并报备成功**」两条件,原判「已提交」错误 | **用户选 B**:本期不触发、只留 seam | +| 4.2 D22 直属总监 | **确认**(补全「调动/区域调整=下拉选」,仅离职才阻断) | 维持 | +| 4.3 星级/阶段 | **确认**(阶段 A 口径 / 星级两点确认) | 维持 | +| 4.4 D26 电话查重 | **翻案**:原型查重设置页证明电话=软提示非唯一 | **用户选 A**:可配置软提示、不建唯一索引 | +| 4.5 D18 18 值 | **消号强化**:后端字典架构获 fresh 印证 | 维持 | +| 4.6 D24 重复导入默认 | **确认**(非缺陷) | 维持 | +| 4.7 D21 ngram | **确认**(MySQL 8.4.8) | 维持 | +| 4.8 D17b 保存视图 | **确认**(多 scope_key 不串) | 维持 | +| 4.9 D9 内置视图不入库 | **确认**(逻辑视图) | 维持 | + +**§5 五个漏问点 —— 全部闭环(新增 2 个拍板)**: + +1. 公海/我客户互斥 —— ✅ 消号(领取即移出公海,原型 L327-347) +2. 多负责人 —— ✅ 消号(**单负责人 + 多协同人**,原型 a4-4-8 明文「每个客户只允许 1 名负责人,其余均为协同人」) +3. **客户删除入口** —— **✅ 拍板 A(一期只做归档)**:无前端删除按钮,deleted 仅系统合并置位 +4. 导入电话查重 —— ✅ 已随 D26 解决(软提示) +5. **oplog 保留时长** —— **✅ 拍板 A(无限期保留)**:审计日志不设保留期 + +**本次修正的字段口径(D19 拍板后的遗留同步)**: +- `archive_status` 收敛为 **有效1 / 已归档2** 两值(去掉「已被合并」,合并 Out-of-scope) +- 已归档 = **删除语义(软删)**,不做独立 tab/子页,列表加 `archive_status` 筛选(默认排除) +- 已同步:`crm-customer/CONTEXT.md`(L8/27-28/36/40/56)+ 票 01-answer / 02-answer + map.md D19/D9 + 各 research 子票加顶部废弃注记 + +**结论**:**全部 29 项决策 + 4 个复核翻案(D20/D26)+ 2 个新增拍板(§5.3 删除 / §5.5 oplog)已定稿**,spec 冻结,可进入实现阶段。 + +## 6. 交付状态 + +- `map.md`:D1–D29 全部记录,Frontier = 空,spec 冻结标记。 +- `issues/NN-answer.md` × 11:全部 `Status: resolved`,各⚠挂号/待确认小节已销号。 +- `issues/NN-.md` × 11:research/task 子票,全部 `Status: resolved`。 +- `crm-customer/CONTEXT.md` + `CONTEXT-MAP.md` 行:已建。 +- 未动任何 Java 源码(git status 里的 `Opportunity.java` 变更与本 effort 无关,是既有改动)。 + +**下一步**:交给新会话按第 3、4 节方法复核。若复核对 4.1–4.9 有改动,改对应决策编号 + 重开相应票(如 D20 改 → 重开票 05),而非覆盖原决策(遵循编号+修正链约定)。 diff --git a/.scratch/customer-module/issues/01-answer.md b/.scratch/customer-module/issues/01-answer.md new file mode 100644 index 0000000..f7fb2a7 --- /dev/null +++ b/.scratch/customer-module/issues/01-answer.md @@ -0,0 +1,38 @@ +## Answer + +**Status: resolved** + +### 交付物 + +1. **`crm-customer/CONTEXT.md`** 已创建(ubiquitous language,纯 glossary,对称 crm-lead/CONTEXT.md 写法)。收录 11 个核心术语,每个带 `_Avoid_` 校准: + +| 术语 | 关键点 | +|---|---| +| 客户 | 归属(owner)+档案(archive_status)正交;阶段(customer_stage)只前进 | +| 销售负责人 | `owner_user_id`,唯一持有者 | +| 销售部门 | `owner_dept_id` 快照,随负责人变更(区别于线索 dept_id 不变) | +| 协同人 | 非负责人成员,可看不可归档/合并 | +| 客户阶段 | 潜在/重潜/已成交,外部结果驱动,不可手动改 | +| 档案状态 | 有效/已归档(D19 去掉「已被合并」,合并 Out-of-scope);正交于归属;已归档即删除语义(软删) | +| 客户公海 | owner 空 + 有效的逻辑视图,无独立池实体 | +| 客户总览 | 管理视角,@DataScope 覆盖 | +| 我的客户 | 销售视角,负责人或协同人 + 有效 | +| 联系人 | 子实体,必属客户,无独立 ACL | +| 人脉关系单位 | 纯文本标签,非关联客户 | +| 客户交割 | 两段式:交接→分配,同步商机/项目负责人 | +| 超期未跟进提醒 | crm-rule 全局单例,只落待发通知 | + +2. **`CONTEXT-MAP.md`** 已加一行:`crm-customer` 客户业务。 + +3. **术语校准点**(相比票内初稿的修正): + - 明确「销售部门是**随负责人变更的快照**」——区别于线索 `dept_id` 全生命周期不变(这是本模块与线索的重要语义差异)。 + - 明确「协同人抛公海清空、交割不自动恢复」。 + - 「客户公海 = 逻辑视图」写进定义,杜绝将来误建池表。 + +### 依赖方向(定稿) + +``` +crm-opportunity → crm-customer(商机读客户/建关联) +crm-customer → crm-rule → (crm-auth / crm-dict / crm-file) +crm-customer → crm-opportunity(「关联商机」页签下游反查上游,同库单体可接受,ADR-0029 技术债) +``` diff --git a/.scratch/customer-module/issues/01-domain-context.md b/.scratch/customer-module/issues/01-domain-context.md index e6e64a3..6611f4f 100644 --- a/.scratch/customer-module/issues/01-domain-context.md +++ b/.scratch/customer-module/issues/01-domain-context.md @@ -1,7 +1,8 @@ # 01 — 客户域领域词汇 + crm-customer/CONTEXT.md 骨架 Type: task -Status: open +Status: resolved +> **⚠ 20260902 复核注记**:本文件关于「档案状态」仍写三值(有效/已归档/已被合并)。**D19 已拍板去掉「已被合并」态**(合并 Out-of-scope),`archive_status` 收敛为**有效/已归档**两值。**以 `01-answer.md` 为准。** Blocked by: ## 目标 @@ -26,4 +27,39 @@ Blocked by: ## Answer -_(待 resolve)_ +**Status: resolved** + +### 交付物 + +1. **`crm-customer/CONTEXT.md`** 已创建(ubiquitous language,纯 glossary,对称 crm-lead/CONTEXT.md 写法)。收录 11 个核心术语,每个带 `_Avoid_` 校准: + +| 术语 | 关键点 | +|---|---| +| 客户 | 归属(owner)+档案(archive_status)正交;阶段(customer_stage)只前进 | +| 销售负责人 | `owner_user_id`,唯一持有者 | +| 销售部门 | `owner_dept_id` 快照,随负责人变更(区别于线索 dept_id 不变) | +| 协同人 | 非负责人成员,可看不可归档/合并 | +| 客户阶段 | 潜在/重潜/已成交,外部结果驱动,不可手动改 | +| 档案状态 | 有效/已归档/已被合并,正交于归属 | +| 客户公海 | owner 空 + 有效的逻辑视图,无独立池实体 | +| 客户总览 | 管理视角,@DataScope 覆盖 | +| 我的客户 | 销售视角,负责人或协同人 + 有效 | +| 联系人 | 子实体,必属客户,无独立 ACL | +| 人脉关系单位 | 纯文本标签,非关联客户 | +| 客户交割 | 两段式:交接→分配,同步商机/项目负责人 | +| 超期未跟进提醒 | crm-rule 全局单例,只落待发通知 | + +2. **`CONTEXT-MAP.md`** 已加一行:`crm-customer` 客户业务。 + +3. **术语校准点**(相比票内初稿的修正): + - 明确「销售部门是**随负责人变更的快照**」——区别于线索 `dept_id` 全生命周期不变(这是本模块与线索的重要语义差异)。 + - 明确「协同人抛公海清空、交割不自动恢复」。 + - 「客户公海 = 逻辑视图」写进定义,杜绝将来误建池表。 + +### 依赖方向(定稿) + +``` +crm-opportunity → crm-customer(商机读客户/建关联) +crm-customer → crm-rule → (crm-auth / crm-dict / crm-file) +crm-customer → crm-opportunity(「关联商机」页签下游反查上游,同库单体可接受,ADR-0029 技术债) +``` diff --git a/.scratch/customer-module/issues/02-answer.md b/.scratch/customer-module/issues/02-answer.md new file mode 100644 index 0000000..951ce6b --- /dev/null +++ b/.scratch/customer-module/issues/02-answer.md @@ -0,0 +1,176 @@ + +## Answer + +**Status: resolved** + +### A. 客户主表 `customer`(`crm-customer`) + +#### A1. 字段清单 + +**系统字段(BaseEntity 继承)** + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint PK | 自增主键 | +| createTime | create_time | datetime | 创建时间 | +| updateTime | update_time | datetime | 最后修改时间 | +| createBy | create_by | bigint | 创建人 user_id | +| updateBy | update_by | bigint | 最后修改人 user_id | +| deleted | deleted | tinyint(1) default 0 | 逻辑删除(**一期:仅系统在合并时置 1,不开放手动删除/无前端删除入口**——§5.3 拍板 A) | + +**业务字段 — 系统生成** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| customerNo | customer_no | varchar(32) | 系统生成 | 唯一编号,格式 KH+yyyyMMdd+4位序;只读不可改 | + +**业务字段 — 基本信息** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| customerName | customer_name | varchar(100) | ✅ | 客户名称;停输防抖触发相似查重 | +| customerType | customer_type | varchar(50) | ✅ | 客户类型字典 `customer_type`;见 A3 字典节 | +| provinceCode | province_code | varchar(20) | ✅ | 省(三级地区,与商机对称)| +| cityCode | city_code | varchar(20) | ✅ | 市 | +| districtCode | district_code | varchar(20) | | 区/县(客户有三级,商机只二级) | +| industryCode | industry_code | varchar(50) | ✅ | 行业一级 code(复用 crm-dict `industry` 两级树;客户选到二级,见商机图 D1「行业字典口径」决策:商机选一级、客户选二级)| +| industryChildCode | industry_child_code | varchar(50) | | 行业二级 code(客户额外存二级;商机无此列) | +| customerStarLevel | customer_star_level | tinyint | ✅ | 客户星级 1~5(枚举,非字典;字典值「一星/二星/…/五星」=前端展示用,存数字) | +| relationStarLevel | relation_star_level | tinyint | ✅ | 关系星级 1~5(同上) | +| networkUnits | network_units | text | | 人脉关系单位,JSON 数组存标签名(单个≤50字;非关联 CRM 客户;选填) | + +> ⚠ **JPA/Hibernate 孤立单大写字母规则**(AGENTS.md):`districtCode` / `industryCode` / `industryChildCode` / `provinceCode` / `cityCode` / `customerNo` / `customerStarLevel` / `relationStarLevel` — 均为「完整词+Code/Level/No」,非孤立单大写,**不需加 `@Column(name=...)`**。 + +**业务字段 — 归属(轴1)** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| ownerUserId | owner_user_id | bigint | ✅(新增时) | 销售负责人 user_id;公海时为 NULL | +| ownerUserNameSnapshot | owner_user_name_snapshot | varchar(64) | | 负责人姓名快照(列表免 join) | +| ownerDeptId | owner_dept_id | bigint | ✅(新增时) | 销售部门 dept_id 快照;公海时保留最后部门(供公海可见性锚点,对称商机) | +| ownerDeptNameSnapshot | owner_dept_name_snapshot | varchar(64) | | 部门名快照 | + +> 协同人(团队成员)= 独立子表 `customer_team_member`(不在主表)。 + +**业务字段 — 轴2 阶段 + 档案状态** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| customerStage | customer_stage | tinyint default 1 | | 客户阶段:潜在1/重潜2/已成交3;只增不减;不可手动改 | +| archiveStatus | archive_status | tinyint default 1 | | 档案状态:**有效1/已归档2**(D19 拍板去掉「已被合并」,合并 Out-of-scope);正交于归属;已归档即删除语义(软删),列表加 `archive_status` 筛选默认排除 | + +**业务字段 — 快照/冗余(列表免 join)** + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| lastValidFollowTime | last_valid_follow_time | datetime | 最近有效跟进时间(新建时=create_time;每写一条跟进记录刷新;超期提醒计时锚点) | +| strategicAgreementLevel | strategic_agreement_level | tinyint | 战略协议等级快照(1~3级;战略协议子域划出但主表留此冗余供列表展示;NULL=未签);由战略协议子系统写入,本图只读 | +| vipCustomerLevel | vip_customer_level | tinyint | 已成交VIP客户等级快照(1~5级;NULL=无;由 A5 项目模块写入,本图只读/留 seam)| + +> ⚠ `strategicAgreementLevel` → Hibernate 命名策略**孤立单大写字母**:`Level` 前面的 `l` 是小写 → `strategicAgreement` + `Level` 整段。实测 `SpringPhysicalNamingStrategy` 对 `strategicAgreementLevel` 生成 `strategic_agreement_level` ✅(`Level` 是完整词,无问题)。但 `vipCustomerLevel` → `vip` 首字母 `v` 是小写,`C` 是孤立大写?→ 不是,`vip` 是小写词,`Customer` 是完整词,`Level` 是完整词,**不踩坑**。 + +**业务字段 — 关联信息** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| companyDecisionMaker | company_decision_maker | varchar(100) | | 公司决策人(文本) | +| isBizNegotiated | is_biz_negotiated | tinyint | ✅ | 是否商机勾兑(是1/否0) | +| directorVisitTime | director_visit_time | datetime | | 总监近期拜访时间(人工维护,日期时间)| +| isChild | is_child | tinyint default 0 | ✅ | 是否子客户(是1/否0);=1时 parent_customer_id 必填 | +| parentCustomerId | parent_customer_id | bigint | 条件必填 | 父客户 id;服务端防环校验;isChild=0时为 NULL | +| parentCustomerNameSnapshot | parent_customer_name_snapshot | varchar(100) | | 父客户名称快照 | +| cooperationSystem | cooperation_system | varchar(200) | | 合作系统(文本,选填) | +| nonCoopReason | non_coop_reason | varchar(200) | | 不合作原因(文本,选填) | +| joinedPresidentClass | joined_president_class | tinyint | | 是否参加总裁班(是1/否0/NULL=未选) | +| presidentClassPerson | president_class_person | varchar(100) | 条件必填 | 总裁班参加人(joinedPresidentClass=1时必填) | +| presidentClassPhone | president_class_phone | varchar(20) | 条件必填 | 总裁班参加人电话 | +| joinedProductClass | joined_product_class | tinyint | | 是否参加产品经理班 | +| productClassPerson | product_class_person | varchar(100) | 条件必填 | 产品班参加人 | +| productClassPhone | product_class_phone | varchar(20) | 条件必填 | 产品班参加人电话 | + +**业务字段 — 工商信息(企查查回填)** + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| unifiedCreditCode | unified_credit_code | varchar(18) | 统一社会信用代码;UNIQUE(NULL 除外,未填时不唯一约束);18 位格式校验 | +| legalRepresentative | legal_representative | varchar(100) | 法定代表人 | +| establishedDate | established_date | date | 成立时间(不得晚于当前日期) | +| registeredCapital | registered_capital | varchar(100) | 注册资本(文本,含单位,如「1000万元」) | +| businessScope | business_scope | text | 经营范围(不套 50 字限制) | +| staffSize | staff_size | varchar(50) | 人员规模 | +| annualRevenue | annual_revenue | varchar(50) | 年产值 | +| businessAddress | business_address | varchar(500) | 工商注册地址(不套 50 字限制) | +| bizNameFromLookup | biz_name_from_lookup | varchar(200) | 企查查确认的工商名称(可与 customer_name 不同;只存不覆盖 customer_name,除非用户确认)| + +**业务字段 — 补充资料** + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| remark | remark | varchar(500) | 备注(≤500字) | + +> 附件 = 独立子表 `customer_attachment`(复用 crm-file,`biz_type=CUSTOMER`;不在主表)。 + +--- + +#### A2. 需要显式 `@Column(name=...)` 的孤立单大写字母字段 + +检查全部字段——**结论:本表无需加 `@Column(name=...)`**。所有字段均为「完整词 + 完整词」组合,无孤立单大写字母触发 Hibernate 命名策略陷阱。 + +--- + +#### A3. 字典清单(新增/复用) + +| 字典分组 code | 含义 | 状态 | 参考值 | +|---|---|---|---| +| `customer_type` | 客户类型 | 🆕 需新增 | **(grill Q8/D18 定稿,18 值)** 运营商 / 土建总包 / 机电总包 / 装修总包 / 教育局 / 投资(代建)公司 / 设计院 / 集成商 / 集成商(子客户) / 咨询造价公司 / 甲方(企业用户) / 甲方(普教) / 甲方(中高职) / 甲方(政府机关) / 竞争同行 / 个人用户 / 操手 / 招标代理 | +| `industry` | 行业(两级树) | ✅ 已有(商机图票09已建;9一级+51二级) | 复用 | +| `customer_star_level` | 客户星级 | **硬编码枚举 1~5(D4-rev1 定稿,不走字典)** | 1~5 星 | +| `relation_star_level` | 关系星级 | **硬编码枚举 1~5(D4-rev1 定稿,不走字典)** | 一星~五星 | + +> ✅ 已定(grill Q8/D18):`customer_type` = crm-dict 字典分组,18 值(见上表)。含 4 甲方值 + 14 非甲方角色值,是通用角色字典。 + +--- + +#### A4. DataScope 注解 + +```java +@DataScope(module = "customer", ownerColumn = "owner_user_id", deptColumn = "owner_dept_id") +``` + +复用 crm-auth `DataScopeEnum(SELF/DEPT/DEPT_AND_CHILD/ALL)`,不新建可见性机制。 + +--- + +#### A5. 索引 + +| 索引名 | 列 | 说明 | +|---|---|---| +| `uk_unified_credit_code` | `unified_credit_code` | 信用代码全局唯一(NULL 不参与唯一约束,MySQL 默认行为)| +| `idx_owner_stage` | `owner_user_id, customer_stage, deleted` | 我的客户视图常用过滤 | +| `idx_dept_stage` | `owner_dept_id, archive_status, deleted` | 公海/总览部门维度 | +| `idx_name` | `customer_name` | 相似查重防抖查询 | +| `idx_parent` | `parent_customer_id` | 父子客户查询/防环 | +| `idx_last_follow` | `last_valid_follow_time, owner_user_id` | 超期未跟进提醒扫表 | + +--- + +#### A6. 产品确认项(⚠ P0)— 已随 grill 全部销号 + +| # | 问题 | 影响 | +|---|---|---| +| P0-1 | ~~`customer_type` 完整枚举值~~ **已拍板(grill Q8/D18)**:crm-dict 字典,18 值 | 字典种子数据 | +| P0-2 | ~~客户选行业到几级~~ **定稿:二级**(`industry_code` + `industry_child_code` 两列;与商机图 D1 一致) | 两列 | +| P0-3 | ~~星级枚举 vs 字典~~ **已拍板(D4-rev1)**:硬编码枚举 1~5,不走字典 | 存储类型 | +| P1-1 | `vip_customer_level` 等级范围(原型列表列有「已成交VIP客户等级」但详情字段写「1~5级」,待确认范围及触发条件,本图只留 seam) | — | +| P1-2 | `strategic_agreement_level` 战略协议等级范围(原型见「一级/二级/三级」=1~3;战略协议子域划出,主表只存冗余快照,待战略协议 effort 回填写逻辑) | — | + +--- + +#### A7. 回填 ticket 02 的下游影响 + +- **票 03**(三视图):`archiveStatus` 过滤(**有效1/已归档2**,无已被合并——D19),`ownerUserId IS NULL` = 公海视图;`last_valid_follow_time` 排序。 +- **票 04**(查重):`unified_credit_code` UNIQUE 索引 + `customer_name` idx 是查重的存储底座。 +- **票 05**(阶段):`customer_stage` 默认1,只能 UPDATE SET customer_stage=? WHERE id=? AND customer_stage < ?(只前进)。 +- **票 07**(oplog):`last_valid_follow_time` 新建时初始化为 `create_time`。 +- **票 08**(交割):`ownerUserId` + `ownerDeptId` 是交割换主的操作列。 +- **票 10**(超期提醒):`last_valid_follow_time` 是计时锚点;新建时初始化为 `create_time`(grill 挂号确认,见 map.md)。 diff --git a/.scratch/customer-module/issues/02-customer-main-table.md b/.scratch/customer-module/issues/02-customer-main-table.md index b71cd5f..e496352 100644 --- a/.scratch/customer-module/issues/02-customer-main-table.md +++ b/.scratch/customer-module/issues/02-customer-main-table.md @@ -1,7 +1,8 @@ # 02 — 客户主表字段清单 Type: research -Status: open +Status: resolved +> **⚠ 20260902 复核注记**:本文件 `archiveStatus` 仍写三值(有效1/已归档2/已被合并3)。**D19 已拍板去掉「已被合并」**(合并 Out-of-scope),收敛为**有效1/已归档2**。**以 `02-answer.md` 为准。** Blocked by: ## 问题 @@ -39,4 +40,178 @@ Blocked by: ## Answer -_(待 resolve)_ +**Status: resolved**(P0 已全部销号:customer_type=18值字典/D18、行业二级、星级硬编码/D4-rev1;含 grill Q8/Q15 回填) + +**Status: resolved** + +### A. 客户主表 `customer`(`crm-customer`) + +#### A1. 字段清单 + +**系统字段(BaseEntity 继承)** + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint PK | 自增主键 | +| createTime | create_time | datetime | 创建时间 | +| updateTime | update_time | datetime | 最后修改时间 | +| createBy | create_by | bigint | 创建人 user_id | +| updateBy | update_by | bigint | 最后修改人 user_id | +| deleted | deleted | tinyint(1) default 0 | 逻辑删除 | + +**业务字段 — 系统生成** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| customerNo | customer_no | varchar(32) | 系统生成 | 唯一编号,格式 KH+yyyyMMdd+4位序;只读不可改 | + +**业务字段 — 基本信息** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| customerName | customer_name | varchar(100) | ✅ | 客户名称;停输防抖触发相似查重 | +| customerType | customer_type | varchar(50) | ✅ | 客户类型字典 `customer_type`;见 A3 字典节 | +| provinceCode | province_code | varchar(20) | ✅ | 省(三级地区,与商机对称)| +| cityCode | city_code | varchar(20) | ✅ | 市 | +| districtCode | district_code | varchar(20) | | 区/县(客户有三级,商机只二级) | +| industryCode | industry_code | varchar(50) | ✅ | 行业一级 code(复用 crm-dict `industry` 两级树;客户选到二级,见商机图 D1「行业字典口径」决策:商机选一级、客户选二级)| +| industryChildCode | industry_child_code | varchar(50) | | 行业二级 code(客户额外存二级;商机无此列) | +| customerStarLevel | customer_star_level | tinyint | ✅ | 客户星级 1~5(枚举,非字典;字典值「一星/二星/…/五星」=前端展示用,存数字) | +| relationStarLevel | relation_star_level | tinyint | ✅ | 关系星级 1~5(同上) | +| networkUnits | network_units | text | | 人脉关系单位,JSON 数组存标签名(单个≤50字;非关联 CRM 客户;选填) | + +> ⚠ **JPA/Hibernate 孤立单大写字母规则**(AGENTS.md):`districtCode` / `industryCode` / `industryChildCode` / `provinceCode` / `cityCode` / `customerNo` / `customerStarLevel` / `relationStarLevel` — 均为「完整词+Code/Level/No」,非孤立单大写,**不需加 `@Column(name=...)`**。 + +**业务字段 — 归属(轴1)** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| ownerUserId | owner_user_id | bigint | ✅(新增时) | 销售负责人 user_id;公海时为 NULL | +| ownerUserNameSnapshot | owner_user_name_snapshot | varchar(64) | | 负责人姓名快照(列表免 join) | +| ownerDeptId | owner_dept_id | bigint | ✅(新增时) | 销售部门 dept_id 快照;公海时保留最后部门(供公海可见性锚点,对称商机) | +| ownerDeptNameSnapshot | owner_dept_name_snapshot | varchar(64) | | 部门名快照 | + +> 协同人(团队成员)= 独立子表 `customer_team_member`(不在主表)。 + +**业务字段 — 轴2 阶段 + 档案状态** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| customerStage | customer_stage | tinyint default 1 | | 客户阶段:潜在1/重潜2/已成交3;只增不减;不可手动改 | +| archiveStatus | archive_status | tinyint default 1 | | 档案状态:有效1/已归档2/已被合并3;正交于归属 | + +**业务字段 — 快照/冗余(列表免 join)** + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| lastValidFollowTime | last_valid_follow_time | datetime | 最近有效跟进时间;**超期提醒计时锚点 = 成为「有主」的时刻**(新建即指派=create_time;公海领取时=领取时刻;每写一条跟进记录刷新);公海无负责人=NULL不提醒 | +| strategicAgreementLevel | strategic_agreement_level | tinyint | 战略协议等级快照(1~3级;战略协议子域划出但主表留此冗余供列表展示;NULL=未签);由战略协议子系统写入,本图只读 | +| vipCustomerLevel | vip_customer_level | tinyint | 已成交VIP客户等级快照(1~5级;NULL=无;由 A5 项目模块写入,本图只读/留 seam)| + +> ⚠ `strategicAgreementLevel` → Hibernate 命名策略**孤立单大写字母**:`Level` 前面的 `l` 是小写 → `strategicAgreement` + `Level` 整段。实测 `SpringPhysicalNamingStrategy` 对 `strategicAgreementLevel` 生成 `strategic_agreement_level` ✅(`Level` 是完整词,无问题)。但 `vipCustomerLevel` → `vip` 首字母 `v` 是小写,`C` 是孤立大写?→ 不是,`vip` 是小写词,`Customer` 是完整词,`Level` 是完整词,**不踩坑**。 + +**业务字段 — 关联信息** + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| companyDecisionMaker | company_decision_maker | varchar(100) | | 公司决策人(文本) | +| isBizNegotiated | is_biz_negotiated | tinyint | ✅ | 是否商机勾兑(是1/否0) | +| directorVisitTime | director_visit_time | datetime | | 总监近期拜访时间(人工维护,日期时间)| +| isChild | is_child | tinyint default 0 | ✅ | 是否子客户(是1/否0);=1时 parent_customer_id 必填 | +| parentCustomerId | parent_customer_id | bigint | 条件必填 | 父客户 id;服务端防环校验;isChild=0时为 NULL | +| parentCustomerNameSnapshot | parent_customer_name_snapshot | varchar(100) | | 父客户名称快照 | +| cooperationSystem | cooperation_system | varchar(200) | | 合作系统(文本,选填) | +| nonCoopReason | non_coop_reason | varchar(200) | | 不合作原因(文本,选填) | +| joinedPresidentClass | joined_president_class | tinyint | | 是否参加总裁班(是1/否0/NULL=未选) | +| presidentClassPerson | president_class_person | varchar(100) | 条件必填 | 总裁班参加人(joinedPresidentClass=1时必填) | +| presidentClassPhone | president_class_phone | varchar(20) | 条件必填 | 总裁班参加人电话 | +| joinedProductClass | joined_product_class | tinyint | | 是否参加产品经理班 | +| productClassPerson | product_class_person | varchar(100) | 条件必填 | 产品班参加人 | +| productClassPhone | product_class_phone | varchar(20) | 条件必填 | 产品班参加人电话 | + +**业务字段 — 工商信息(企查查回填)** + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| unifiedCreditCode | unified_credit_code | varchar(18) | 统一社会信用代码;UNIQUE(NULL 除外,未填时不唯一约束);18 位格式校验 | +| legalRepresentative | legal_representative | varchar(100) | 法定代表人 | +| establishedDate | established_date | date | 成立时间(不得晚于当前日期) | +| registeredCapital | registered_capital | varchar(100) | 注册资本(文本,含单位,如「1000万元」) | +| businessScope | business_scope | text | 经营范围(不套 50 字限制) | +| staffSize | staff_size | varchar(50) | 人员规模 | +| annualRevenue | annual_revenue | varchar(50) | 年产值 | +| businessAddress | business_address | varchar(500) | 工商注册地址(不套 50 字限制) | +| bizNameFromLookup | biz_name_from_lookup | varchar(200) | 企查查确认的工商名称(可与 customer_name 不同;只存不覆盖 customer_name,除非用户确认)| + +**业务字段 — 补充资料** + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| remark | remark | varchar(500) | 备注(≤500字) | + +> 附件 = 独立子表 `customer_attachment`(复用 crm-file,`biz_type=CUSTOMER`;不在主表)。 + +--- + +#### A2. 需要显式 `@Column(name=...)` 的孤立单大写字母字段 + +检查全部字段——**结论:本表无需加 `@Column(name=...)`**。所有字段均为「完整词 + 完整词」组合,无孤立单大写字母触发 Hibernate 命名策略陷阱。 + +--- + +#### A3. 字典清单(新增/复用) + +| 字典分组 code | 含义 | 状态 | 参考值 | +|---|---|---|---| +| `customer_type` | 客户类型 | 🆕 需新增 | **(grill Q8/D18 定稿,18 值)** 运营商 / 土建总包 / 机电总包 / 装修总包 / 教育局 / 投资(代建)公司 / 设计院 / 集成商 / 集成商(子客户) / 咨询造价公司 / 甲方(企业用户) / 甲方(普教) / 甲方(中高职) / 甲方(政府机关) / 竞争同行 / 个人用户 / 操手 / 招标代理 | +| `industry` | 行业(两级树) | ✅ 已有(商机图票09已建;9一级+51二级) | 复用 | +| `customer_star_level` | 客户星级 | **硬编码枚举 1~5(D4-rev1 定稿,不走字典)** | 1~5 星 | +| `relation_star_level` | 关系星级 | **硬编码枚举 1~5(D4-rev1 定稿,不走字典)** | 一星~五星 | + +> ✅ 已定(grill Q8/D18):`customer_type` = crm-dict 字典分组,18 值(见上表)。注意含 4 个甲方值 + 14 个非甲方角色值,是通用角色字典,非仅甲方。 + +--- + +#### A4. DataScope 注解 + +```java +@DataScope(module = "customer", ownerColumn = "owner_user_id", deptColumn = "owner_dept_id") +``` + +复用 crm-auth `DataScopeEnum(SELF/DEPT/DEPT_AND_CHILD/ALL)`,不新建可见性机制。 + +--- + +#### A5. 索引 + +| 索引名 | 列 | 说明 | +|---|---|---| +| `uk_unified_credit_code` | `unified_credit_code` | 信用代码全局唯一(NULL 不参与唯一约束,MySQL 默认行为)| +| `idx_owner_stage` | `owner_user_id, customer_stage, deleted` | 我的客户视图常用过滤 | +| `idx_dept_stage` | `owner_dept_id, archive_status, deleted` | 公海/总览部门维度 | +| `idx_name` | `customer_name` | 相似查重防抖查询 | +| `idx_parent` | `parent_customer_id` | 父子客户查询/防环 | +| `idx_last_follow` | `last_valid_follow_time, owner_user_id` | 超期未跟进提醒扫表 | + +--- + +#### A6. 待产品确认(⚠ P0) + +| # | 问题 | 影响 | +|---|---|---| +| P0-1 | ~~`customer_type` 完整枚举值~~ **已拍板(grill Q8/D18)**:crm-dict 字典,18 值 | 字典种子数据 | +| P0-2 | ~~客户选行业到几级~~ **定稿:二级**(`industry_code` + `industry_child_code` 两列;与商机图 D1「商机选一级、客户选二级」一致) | 两列 | +| P0-3 | ~~星级枚举 vs 字典~~ **已拍板(D4-rev1)**:硬编码枚举 1~5,不走字典 | 存储类型 | +| P1-1 | `vip_customer_level` 等级范围(原型列表列有「已成交VIP客户等级」但详情字段写「1~5级」,待确认范围及触发条件,本图只留 seam) | — | +| P1-2 | `strategic_agreement_level` 战略协议等级范围(原型见「一级/二级/三级」=1~3;战略协议子域划出,主表只存冗余快照,待战略协议 effort 回填写逻辑) | — | + +--- + +#### A7. 回填 ticket 02 的下游影响 + +- **票 03**(三视图):`archiveStatus` 过滤(有效1/已归档2,归档=删除见 D19),`ownerUserId IS NULL` = 公海视图;`last_valid_follow_time` 排序。 +- **票 04**(查重):`unified_credit_code` UNIQUE 索引 + `customer_name` idx 是查重的存储底座。 +- **票 05**(阶段):`customer_stage` 默认1,只能 UPDATE SET customer_stage=? WHERE id=? AND customer_stage < ?(只前进)。 +- **票 07**(oplog):`last_valid_follow_time` 新建即指派=create_time;公海领取时赋领取时刻;跟进刷新。 +- **票 08**(交割):`ownerUserId` + `ownerDeptId` 是交割换主的操作列。 +- **票 10**(超期提醒):`last_valid_follow_time` 是计时锚点 = **成为有主时刻**(grill Q15 拍板 A):新建即指派=create_time;公海领取=领取时刻;跟进刷新。公海(NULL)不计时。 diff --git a/.scratch/customer-module/issues/03-answer.md b/.scratch/customer-module/issues/03-answer.md new file mode 100644 index 0000000..6b6a6cf --- /dev/null +++ b/.scratch/customer-module/issues/03-answer.md @@ -0,0 +1,94 @@ + +## Answer + +**Status: resolved**(含 grill Q9 归档=删除 + Q15 领取赋锚点回填) + +### A. 三个 workspace 数据范围(定稿) + +| workspace | 数据范围 | 附加状态页 | +|---|---|---| +| **我的客户** | 当前用户为**销售负责人或协同人** 且 `archive_status=有效(1)` 且 **非公海**(owner 非空) | **无独立归档子页**(归档=删除,见 grill Q9/D19;列表加 `archive_status` 筛选默认排除) | +| **客户总览** | `@DataScope` 覆盖组织范围的 **未删除、未进入公海** 客户(默认只列 `archive_status=1`) | **无独立归档 tab**(归档=删除,加筛选条件默认排除,可勾选查询) | +| **客户公海** | `archive_status=有效(1)` **且 `owner_user_id IS NULL`** 逻辑视图 | 无 | + +- **公海不是独立实体**,无池表;对称商机公海 = 逻辑视图(chart D3)。 +- **管理职务不扩大「我的客户」数据范围**(原型 2.1 §6 明文);管理视角走「客户总览」。 +- **协同人视角**:协同人可见客户(进「我的客户」),但**不得归档/合并**(仅负责人可,原型 2.2 §4)。 + +### B. 内置视图 / 常用检索(硬编码,不入库) + +**我的客户** 内置:`我负责的客户`(owner=我) / `我协同的客户`(团队成员表含我) / `待跟进的客户`(有下次跟进时间且 ≤ now) / `我关注的客户`(关注表) / `最近访问`(view_log 倒序)。 + +**客户公海** 内置:`最近访问` / `我关注的客户`(原型 A4-1-1 检索列表只见这两项 + 自定义)。 + +**客户总览** 内置:`最近访问` / `我关注的客户` / `我负责的客户` / `我协同的客户`(原型 A4-2-1 有「我负责的/我协同的」身份筛选)。 + +> 自定义视图 = crm-preference 平台能力,票 11 接入(三个 scope_key)。内置视图**不落库**,口径硬编码(对称商机票 14 拍板)。 + +### C. 三视图模式(定稿) + +| workspace | 视图模式 | +|---|---| +| 我的客户 | 表格 / 列表详情(左表+右详情) / **看板** | +| 客户总览 | 表格 / 列表详情 / **看板** | +| 客户公海 | 表格 / 分屏(左表+右详情) | + +- **看板视图分组页签 = 三个**:`客户阶段` / `客户星级` / `关系星级`(原型 A4-3-1 §1.3 明文「看板视图包含客户阶段、客户星级、关系星级三个分组页签」;A4-2-1 卡片视图顶部即「潜在客户(3)/重潜客户(3)/已成交客户(3)」)。 + - ⚠ **本期注记(D20 改票 B)**:客户阶段本期实际只有「潜在」(重潜/已成交触发链路 seam 不可达),故本期看板「客户阶段」分组下客户均堆在「潜在客户」列,重潜/已成交列恒为空。**这是预期行为、非 bug**:看板仍按 `customer_stage` 正常分组,三列骨架照旧渲染,链路就绪后自然填充;不得为填列而造假阶段数据。 +- **视图切换记住用户偏好**(复用 crm-preference 列偏好能力;对称商机票 14);首次默认表格视图。 +- 勾选跨分页保留;全选仅当前页/当前批次;批量操作按已勾选 ID 集合执行。 + +### D. 共用列(定稿,三 workspace 同款列基础 + 差异列) + +**共用列**:客户名称(固定列不可隐藏) / 客户编号 / 客户类型 / 所属地区 / 行业 / 客户阶段 / 客户星级 / 关系星级 / 战略协议等级(快照) / 已成交VIP客户等级(快照) / 关联业务(商机/项目计数) / 最近跟进时间。 + +**差异列**: +- 我的客户 / 客户总览:+ 销售负责人 / 销售部门。 +- 客户公海:+ **进入公海时间**(`enter_pool_time`,见票 03 回填) / 无销售负责人列(owner 空)。 + +**字段设置** = crm-preference 列偏好(列显隐/排序,用户级);客户名称为固定列不参与设置。 + +### E. 归属迁移动作枚举(定稿) + +| 动作 | 语义 | 前置校验 | 触发方 | +|---|---|---|---| +| 领取 | 公海→个人(owner 空→当前用户) | 客户仍在公海 + 无进行中商机/项目 | 普通销售 | +| 分配 | 公海→个人 / 个人→个人(换 owner) | 目标人员启用 + 有权分配范围 + 非原负责人 | 领导/管理 | +| 抛公海 | 个人→公海(清 owner+协同人) | **无进行中商机/项目**(原型 A4-1-1 §3 明文)+ 有抛公海权限 | 领导/管理 | +| 归档 | 有效→已归档(archive_status 1→2) | 仅销售负责人可;**无进行中商机/项目**(原型 A4-3-1 归档弹窗明说) | 销售/领导 | +| 恢复 | 已归档→有效(2→1) | 仅销售负责人可 | 销售/领导 | +| 交割 | 换 owner(跨模块同步商机/项目负责人) | 票 08 专管 | 销售本人 | + +- 每边**写操作日志**(票 07 oplog);CAS 乐观锁 + 版本号。 +- **批量操作部分失败不回滚**(按客户逐条独立事务,跳过已不可操作项,Toast 报成功/跳过数)——**例外:批量归档整批预检**,任一失败整批不执行(原型 A4-3-1 §3.6 明文)。 +- **「进行中商机/项目」阻止抛公海/归档**:商机侧读 crm-opportunity(状态=推进中 即 opp_status=2);**项目侧留 seam**(A5 未建,先跳过项目那半,等 A5 建好补校验)。 +- **领取/分配 后端副作用(D25,票10 锚点)**:从公海领取或分配到一个负责人时,若 `last_valid_follow_time` 为空(客户此前无主/无锚点)→ 赋当前时刻(超期提醒开始计时)。抛公海/归属迁移不动锚点(仍需有主才计时)。 + +### F. 新增字段(回填票 02) + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| enterPoolTime | enter_pool_time | datetime | 进入公海时间(公海列表列;抛公海时写入,领取/分配时清空) | + +> `enterPoolTime` 无孤立单大写字母陷阱,无需 `@Column(name=...)`。 + +### G. 领导视角 DataScope 映射(对称 crm-auth) + +- 客户总览可见性 = `@DataScope(module="customer")` 套 `DataScopeEnum(SELF/DEPT/DEPT_AND_CHILD/ALL)`,按用户角色配置决定(ADR-0018 按模块可配)。 +- 普通销售**无客户总览菜单权限**时不可见;总监=DEPT_AND_CHILD 或 ALL(按后台配置)。 +- 公海:普通销售看「可领取」= 其部门集合可见范围内 owner 空的客户;领导看「可分配」= 其管理范围。 + +### H. 产品确认项 — 已随 grill 全部销号 + +| # | 问题 | 影响 | +|---|---|---| +| P0-1 | ~~已归档/已被合并 tab 形态~~ **已拍板(grill Q9/D19)**:去掉「已被合并」;**已归档 = 删除**,不做独立 tab/子页,列表加 `archive_status` 筛选条件(默认排除已归档,勾选可查) | 列表查询参数 | +| P1-1 | ~~进入公海时间排序~~ **已拍板(grill Q19)**:保留列 + 加入排序字段池(升降序) | 排序字段池(票 11) | + +### I. 回填 / 下游影响 + +- **票 02**:新增 `enter_pool_time` 字段(本票 F)。 +- **票 08**(交割):owner 换主 = 本票「分配」动作的扩展版(跨模块同步),交割事务复用 owner 变更 oplog 语义。 +- **票 11**(saved-view):三 workspace 各自 scope_key,字段池白名单 = 本票 D 共用列 + 差异列。 +- **票 07**(oplog):归属迁移(领取/分配/抛公海/归档/恢复/交割)都写 oplog。 +- **票 01**(CONTEXT):术语「客户公海」「销售负责人」「协同人」「归档」「已合并」。 diff --git a/.scratch/customer-module/issues/03-ownership-views.md b/.scratch/customer-module/issues/03-ownership-views.md index c979bff..b74a4ce 100644 --- a/.scratch/customer-module/issues/03-ownership-views.md +++ b/.scratch/customer-module/issues/03-ownership-views.md @@ -1,7 +1,8 @@ # 03 — 归属流转 + 三 workspace + 内置视图 + 自定义视图查询口径 Type: research -Status: open +Status: resolved +> **⚠ 20260902 复核注记**:本文件关于「已归档客户/已被合并客户」独立 tab/子页的旧口径**已被推翻**——D19 拍板去掉「已被合并」态、**已归档=删除语义、不做独立 tab**,只加 `archive_status` 筛选条件(默认排除已归档)。**三值归档态/独立子页方案作废,以 `03-answer.md` 为准。** Blocked by: 02 ## 问题 @@ -38,4 +39,92 @@ Blocked by: 02 ## Answer -_(待 resolve)_ +**Status: resolved** + +### A. 三个 workspace 数据范围(定稿) + +| workspace | 数据范围 | 附加状态页 | +|---|---|---| +| **我的客户** | 当前用户为**销售负责人或协同人** 且 `archive_status=有效(1)` 且 **非公海**(owner 非空) | 子页「已归档客户」= owner 当前用户 且 archive_status=2;「已被合并客户」= owner 当前用户 且 archive_status=3 | +| **客户总览** | `@DataScope` 覆盖组织范围的 **未删除、未进入公海** 客户(含有效/已归档/已被合并三态,但**默认只列有效**,归档/合并走独立 tab) | 顶部 tab「已归档客户」「已被合并客户」 | +| **客户公海** | `archive_status=有效(1)` **且 `owner_user_id IS NULL`** 逻辑视图 | 无 | + +- **公海不是独立实体**,无池表;对称商机公海 = 逻辑视图(chart D3)。 +- **管理职务不扩大「我的客户」数据范围**(原型 2.1 §6 明文);管理视角走「客户总览」。 +- **协同人视角**:协同人可见客户(进「我的客户」),但**不得归档/合并**(仅负责人可,原型 2.2 §4)。 + +### B. 内置视图 / 常用检索(硬编码,不入库) + +**我的客户** 内置:`我负责的客户`(owner=我) / `我协同的客户`(团队成员表含我) / `待跟进的客户`(有下次跟进时间且 ≤ now) / `我关注的客户`(关注表) / `最近访问`(view_log 倒序)。 + +**客户公海** 内置:`最近访问` / `我关注的客户`(原型 A4-1-1 检索列表只见这两项 + 自定义)。 + +**客户总览** 内置:`最近访问` / `我关注的客户` / `我负责的客户` / `我协同的客户`(原型 A4-2-1 有「我负责的/我协同的」身份筛选)。 + +> 自定义视图 = crm-preference 平台能力,票 11 接入(三个 scope_key)。内置视图**不落库**,口径硬编码(对称商机票 14 拍板)。 + +### C. 三视图模式(定稿) + +| workspace | 视图模式 | +|---|---| +| 我的客户 | 表格 / 列表详情(左表+右详情) / **看板** | +| 客户总览 | 表格 / 列表详情 / **看板** | +| 客户公海 | 表格 / 分屏(左表+右详情) | + +- **看板视图分组页签 = 三个**:`客户阶段` / `客户星级` / `关系星级`(原型 A4-3-1 §1.3 明文「看板视图包含客户阶段、客户星级、关系星级三个分组页签」;A4-2-1 卡片视图顶部即「潜在客户(3)/重潜客户(3)/已成交客户(3)」)。 +- **视图切换记住用户偏好**(复用 crm-preference 列偏好能力;对称商机票 14);首次默认表格视图。 +- 勾选跨分页保留;全选仅当前页/当前批次;批量操作按已勾选 ID 集合执行。 + +### D. 共用列(定稿,三 workspace 同款列基础 + 差异列) + +**共用列**:客户名称(固定列不可隐藏) / 客户编号 / 客户类型 / 所属地区 / 行业 / 客户阶段 / 客户星级 / 关系星级 / 战略协议等级(快照) / 已成交VIP客户等级(快照) / 关联业务(商机/项目计数) / 最近跟进时间。 + +**差异列**: +- 我的客户 / 客户总览:+ 销售负责人 / 销售部门。 +- 客户公海:+ **进入公海时间**(`enter_pool_time`,见票 03 回填) / 无销售负责人列(owner 空)。 + +**字段设置** = crm-preference 列偏好(列显隐/排序,用户级);客户名称为固定列不参与设置。 + +### E. 归属迁移动作枚举(定稿) + +| 动作 | 语义 | 前置校验 | 触发方 | +|---|---|---|---| +| 领取 | 公海→个人(owner 空→当前用户) | 客户仍在公海 + 无进行中商机/项目 | 普通销售 | +| 分配 | 公海→个人 / 个人→个人(换 owner) | 目标人员启用 + 有权分配范围 + 非原负责人 | 领导/管理 | +| 抛公海 | 个人→公海(清 owner+协同人) | **无进行中商机/项目**(原型 A4-1-1 §3 明文)+ 有抛公海权限 | 领导/管理 | +| 归档 | 有效→已归档(archive_status 1→2) | 仅销售负责人可;**无进行中商机/项目**(原型 A4-3-1 归档弹窗明说) | 销售/领导 | +| 恢复 | 已归档→有效(2→1) | 仅销售负责人可 | 销售/领导 | +| 交割 | 换 owner(跨模块同步商机/项目负责人) | 票 08 专管 | 销售本人 | + +- 每边**写操作日志**(票 07 oplog);CAS 乐观锁 + 版本号。 +- **批量操作部分失败不回滚**(按客户逐条独立事务,跳过已不可操作项,Toast 报成功/跳过数)——**例外:批量归档整批预检**,任一失败整批不执行(原型 A4-3-1 §3.6 明文)。 +- **「进行中商机/项目」阻止抛公海/归档**:商机侧读 crm-opportunity(状态=推进中 即 opp_status=2);**项目侧留 seam**(A5 未建,先跳过项目那半,等 A5 建好补校验)。 + +### F. 新增字段(回填票 02) + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| enterPoolTime | enter_pool_time | datetime | 进入公海时间(公海列表列;抛公海时写入,领取/分配时清空) | + +> `enterPoolTime` 无孤立单大写字母陷阱,无需 `@Column(name=...)`。 + +### G. 领导视角 DataScope 映射(对称 crm-auth) + +- 客户总览可见性 = `@DataScope(module="customer")` 套 `DataScopeEnum(SELF/DEPT/DEPT_AND_CHILD/ALL)`,按用户角色配置决定(ADR-0018 按模块可配)。 +- 普通销售**无客户总览菜单权限**时不可见;总监=DEPT_AND_CHILD 或 ALL(按后台配置)。 +- 公海:普通销售看「可领取」= 其部门集合可见范围内 owner 空的客户;领导看「可分配」= 其管理范围。 + +### H. 待产品确认(⚠) + +| # | 问题 | 影响 | +|---|---|---| +| P0-1 | 客户总览「已归档客户/已被合并客户」是**独立 tab** 还是**筛选状态**?原型 A4-2-1 顶部有「已归档客户/已被合并客户」两个 tab 且 A4-3-1 也把它们当独立子页——**推荐独立子页**(archive_status 过滤),归档/合并态不进主列表 | 列表查询参数 | +| P1-1 | 公海列表是否需要「进入公海时间」排序?原型列为固定列 → 推荐保留 | 排序字段池(票 11) | + +### I. 回填 / 下游影响 + +- **票 02**:新增 `enter_pool_time` 字段(本票 F)。 +- **票 08**(交割):owner 换主 = 本票「分配」动作的扩展版(跨模块同步),交割事务复用 owner 变更 oplog 语义。 +- **票 11**(saved-view):三 workspace 各自 scope_key,字段池白名单 = 本票 D 共用列 + 差异列。 +- **票 07**(oplog):归属迁移(领取/分配/抛公海/归档/恢复/交割)都写 oplog。 +- **票 01**(CONTEXT):术语「客户公海」「销售负责人」「协同人」「归档」「已合并」。 diff --git a/.scratch/customer-module/issues/04-answer.md b/.scratch/customer-module/issues/04-answer.md new file mode 100644 index 0000000..0bfe7fc --- /dev/null +++ b/.scratch/customer-module/issues/04-answer.md @@ -0,0 +1,87 @@ +## Answer + +**Status: resolved** + +### A. 范围(定稿) + +- ✅ **做**:新增/编辑客户时的**内建查重校验** + **企查查外部服务契约 seam**。 +- ❌ **不做**:A4-6 独立查重页 + 客户合并(chart D6);「客户查重设置」可配规则(chart D4);企查查服务内部实现(只定契约)。 + +### B. 查重校验(内建,写死在新增/编辑流程) + +#### B1. 三层查重(定稿) + +| 层 | 时机 | 规则 | 结果 | +|---|---|---|---| +| L1 自动相似提示 | 客户名称**停输防抖**(~300ms) | 名称模糊匹配全部未删除客户 | 只提示不阻止;无权命中的脱敏为「存在匹配记录」 | +| L2 保存时精确查重 | 点击确认提交 | ① 信用代码全局唯一(硬拦)② 名称相似(弹窗确认) | 信用代码同 → 禁止;名称似 → 弹窗「仍要创建/返回修改」 | +| L3 终查(并发兜底) | 「仍要创建」后、写库前 | 再查一次信用代码 + 名称 | 仍无冲突才写库;DB UNIQUE 兜底 | + +#### B2. 相似判定算法 — 已拍板(grill Q11,seam 化) + +原型明说「使用 CRM 后端统一查重服务,不在前端固化阈值」。**我的推荐(最简可行)**:用 **MySQL 内置 `FULLTEXT` 索引 + `MATCH ... AGAINST` 自然语言模式**,或更简单先上 **`LIKE '%name%'` + 编辑距离(Levenshtein) 后置过滤**。 + +- **✅ 已拍板(grill Q11,用户拍板 = seam 化)**:相似度判定**抽成接口/端口**(可替换策略,如 `CustomerDedupService` 内部策略接口或独立 `CustomerNameSimilarityPort`),一期只做**最简实现**,后续迭代可换算法。 +- **一期最简实现**:`customer_name` 建 FULLTEXT 索引(ngram parser, token_size=2),`MATCH(customer_name) AGAINST(?)` 取 topN 候选,再算相似度判定「相似」;信用代码精确相等判定「重复」。**阈值从「客户查重设置」配置读取(70/80/90%)**,不再写死。 +- **契约稳定、实现可插拔**:调用方不关心具体算法;后续可换 Levenshtein / Jaccard / 外部相似度服务,只替换端口实现。 +- **统一查重服务**:抽 `CustomerDedupService`(crm-customer 内),后续 A4-6 若重启可复用;本期只被新增/编辑流程调用。 + +#### B3. 信用代码校验(定稿) + +- 格式:`^[0-9A-Z]{18}$`(18 位数字或大写字母,原型「18 位数字或字母组合」)。 +- 唯一:`uk_unified_credit_code` 唯一索引(票 02 已建),**NULL 不参与唯一约束**(未填时允许)。 +- 精确相等 → 硬拦禁止创建;命中无权查看的客户只提示「系统已存在该企业」,不显 name/编号/负责人。 + +#### B4. 联系人内联查重(定稿,回填票 06;D26 翻案后校准 20260902) + +- 同一客户内同表格**电话相同两行** → 禁止提交并定位重复行(表单级硬校验)。 +- 无电话 → 按「客户 + 姓名 + 职务」提示疑似重复(只提示)。 +- ~~全库按电话精确查重 → 相同禁止新建~~ **已翻案(D26 用户拍板 A,20260902)**:跨客户电话命中=**软提示疑似重复、不阻断**,受 crm-rule 客户查重设置「联系电话」开关控制(默认开、可关)。不建电话唯一索引。 + +### C. 企查查外部服务契约 seam(定稿) + +#### C1. 端口(不做实现,留 mock) + +```java +// crm-customer 侧出站端口 +public interface CompanyLookupPort { + /** 按企业名查询工商候选列表 */ + List lookup(String companyName); +} + +record CompanyCandidate( + String bizName, String unifiedCreditCode, String legalRepresentative, + LocalDate establishedDate, String registeredCapital, String businessScope, + String staffSize, String annualRevenue, String address +) {} +``` + +#### C2. 回填范围(定稿,原型 §3.3 明文) + +- **允许回填字段(9 项)**:工商名称 / 统一社会信用代码 / 法定代表人 / 成立时间 / 注册资本 / 经营范围 / 人员规模 / 年产值 / 工商注册地址。 +- **只回填当前为空的字段**(首次选择);已填字段再次选择必须先弹「受影响字段确认」才覆盖;服务返回空值不覆盖原值。 +- **永不覆盖**:客户类型 / 行业 / 所属地区 / 关系星级 / 客户星级 / 销售负责人 / 销售部门 / 协同人 / 人脉关系单位 / 联系人 / 备注。 +- 回填后**立即用信用代码执行精确查重**(B1 L2 联动)。 + +#### C3. 异常语义(定稿) + +| 场景 | 行为 | +|---|---| +| 无结果 | 保留表单,提示「未查询到匹配企业」,可手工填写 | +| 服务失败/超时 | 保留表单,提示重试或手工填写;**不阻断手工提交** | +| 请求乱序 | 只处理最后一次有效请求,旧结果不覆盖当前表单 | + +### D. 编辑客户(定稿,对称新增) + +- 编辑页复用新增表单结构 + 查重校验;编辑时**排除自身**出查重范围(改名字不和自己撞)。 +- 历史停用枚举可回显不可重选(原型公共规则)。 + +### E. 回填 / 下游 + +- **票 02**:`unified_credit_code` 唯一索引 + `customer_name` FULLTEXT(ngram) 索引(A5 索引表补一条 `idx_name_fulltext`)。 +- **票 06**:联系人电话查重(B4)。 +- **票 09**(导入):复用 `CustomerDedupService` 做导入查重。 + +### F. 挂号 — 已随 grill 全部销号 + +1. ~~相似判定算法~~ **已拍板(grill Q11)**:seam 化接口,一期最简 = ngram FULLTEXT,后续可换算法。 diff --git a/.scratch/customer-module/issues/04-create-dedup-and-company-lookup.md b/.scratch/customer-module/issues/04-create-dedup-and-company-lookup.md index a7bff74..3ad1f0b 100644 --- a/.scratch/customer-module/issues/04-create-dedup-and-company-lookup.md +++ b/.scratch/customer-module/issues/04-create-dedup-and-company-lookup.md @@ -1,7 +1,7 @@ # 04 — 新增/编辑客户的查重校验 + 企查查工商回填 seam Type: research -Status: open +Status: resolved Blocked by: 02 ## 问题 @@ -36,4 +36,87 @@ Blocked by: 02 ## Answer -_(待 resolve)_ +**Status: resolved** + +### A. 范围(定稿) + +- ✅ **做**:新增/编辑客户时的**内建查重校验** + **企查查外部服务契约 seam**。 +- ❌ **不做**:A4-6 独立查重页 + 客户合并(chart D6);「客户查重设置」可配规则(chart D4);企查查服务内部实现(只定契约)。 + +### B. 查重校验(内建,写死在新增/编辑流程) + +#### B1. 三层查重(定稿) + +| 层 | 时机 | 规则 | 结果 | +|---|---|---|---| +| L1 自动相似提示 | 客户名称**停输防抖**(~300ms) | 名称模糊匹配全部未删除客户 | 只提示不阻止;无权命中的脱敏为「存在匹配记录」 | +| L2 保存时精确查重 | 点击确认提交 | ① 信用代码全局唯一(硬拦)② 名称相似(弹窗确认) | 信用代码同 → 禁止;名称似 → 弹窗「仍要创建/返回修改」 | +| L3 终查(并发兜底) | 「仍要创建」后、写库前 | 再查一次信用代码 + 名称 | 仍无冲突才写库;DB UNIQUE 兜底 | + +#### B2. 相似判定算法(⚠ 挂号,需拍板) + +原型明说「使用 CRM 后端统一查重服务,不在前端固化阈值」。**我的推荐(最简可行)**:用 **MySQL 内置 `FULLTEXT` 索引 + `MATCH ... AGAINST` 自然语言模式**,或更简单先上 **`LIKE '%name%'` + 编辑距离(Levenshtein) 后置过滤**。 + +- **推荐方案**:`customer_name` 建 FULLTEXT 索引,`MATCH(customer_name) AGAINST(?) ` 取 topN 候选,再用 **Levenshtein 相似度 ≥ 0.7** 判定「相似」,信用代码精确相等判定「重复」。首版阈值 0.7 写死常量(不做后台可配,chart D4)。 +- **备选**:NGram 分词(MySQL ngram parser)+ Jaccard 相似度。中文企业名短,FULLTEXT 默认分词对中文不友好,**ngram parser(token_size=2)更合适**——**最终推荐走 ngram parser**。 +- **统一查重服务**:抽 `CustomerDedupService`(crm-customer 内),后续 A4-6 若重启可复用;本期只被新增/编辑流程调用。 + +#### B3. 信用代码校验(定稿) + +- 格式:`^[0-9A-Z]{18}$`(18 位数字或大写字母,原型「18 位数字或字母组合」)。 +- 唯一:`uk_unified_credit_code` 唯一索引(票 02 已建),**NULL 不参与唯一约束**(未填时允许)。 +- 精确相等 → 硬拦禁止创建;命中无权查看的客户只提示「系统已存在该企业」,不显 name/编号/负责人。 + +#### B4. 联系人内联查重(定稿,回填票 06) + +- 同表格内**电话相同两行** → 禁止提交并定位重复行。 +- 无电话 → 按「客户 + 姓名 + 职务」提示疑似重复(只提示)。 +- 全库按电话精确查重(对已存在联系人)→ 相同禁止新建。 + +### C. 企查查外部服务契约 seam(定稿) + +#### C1. 端口(不做实现,留 mock) + +```java +// crm-customer 侧出站端口 +public interface CompanyLookupPort { + /** 按企业名查询工商候选列表 */ + List lookup(String companyName); +} + +record CompanyCandidate( + String bizName, String unifiedCreditCode, String legalRepresentative, + LocalDate establishedDate, String registeredCapital, String businessScope, + String staffSize, String annualRevenue, String address +) {} +``` + +#### C2. 回填范围(定稿,原型 §3.3 明文) + +- **允许回填字段(9 项)**:工商名称 / 统一社会信用代码 / 法定代表人 / 成立时间 / 注册资本 / 经营范围 / 人员规模 / 年产值 / 工商注册地址。 +- **只回填当前为空的字段**(首次选择);已填字段再次选择必须先弹「受影响字段确认」才覆盖;服务返回空值不覆盖原值。 +- **永不覆盖**:客户类型 / 行业 / 所属地区 / 关系星级 / 客户星级 / 销售负责人 / 销售部门 / 协同人 / 人脉关系单位 / 联系人 / 备注。 +- 回填后**立即用信用代码执行精确查重**(B1 L2 联动)。 + +#### C3. 异常语义(定稿) + +| 场景 | 行为 | +|---|---| +| 无结果 | 保留表单,提示「未查询到匹配企业」,可手工填写 | +| 服务失败/超时 | 保留表单,提示重试或手工填写;**不阻断手工提交** | +| 请求乱序 | 只处理最后一次有效请求,旧结果不覆盖当前表单 | + +### D. 编辑客户(定稿,对称新增) + +- 编辑页复用新增表单结构 + 查重校验;编辑时**排除自身**出查重范围(改名字不和自己撞)。 +- 历史停用枚举可回显不可重选(原型公共规则)。 + +### E. 回填 / 下游 + +- **票 02**:`unified_credit_code` 唯一索引 + `customer_name` FULLTEXT(ngram) 索引(A5 索引表补一条 `idx_name_fulltext`)。 +- **票 06**:联系人电话查重(B4)。 +- **票 09**(导入):复用 `CustomerDedupService` 做导入查重。 + +### F. ⚠ 挂号(需拍板) + +1. **相似判定算法**:ngram parser + 相似度阈值 0.7(推荐)vs 更简单 LIKE+Levenshtein。你选哪个? diff --git a/.scratch/customer-module/issues/05-answer.md b/.scratch/customer-module/issues/05-answer.md new file mode 100644 index 0000000..7ce31d0 --- /dev/null +++ b/.scratch/customer-module/issues/05-answer.md @@ -0,0 +1,56 @@ + +## Answer + +**Status: resolved**(重潜触发点:grill Q10 拍板 A/保守 → 第三轮 fresh 复核翻案,20260902 改票 B = 本期不触发、只留 seam) + +### A. 三态定义(定稿) + +| 值 | 名称 | 驱动源 | 本图 | +|---|---|---|---| +| 1 | 潜在客户 | 新建客户成功默认值 | ✅ | +| 2 | 重潜客户 | 关联的任一方案卡「提交并报备成功」 | ⚠ seam(触发链路本期不可达,见 C;本期不自动触发) | +| 3 | 已成交客户 | 关联的任一项目「首次中标赢单」 | ❌ 留 seam 给 A5 | + +- **只前进不回退**(原型 A4-4-1 §1 明文:方案卡失败/报备失败/商机输单/项目结束**均不改变**阶段)。 +- **不可手动改**:阶段条不可点击;无面向前端的 setter API;仅内部 port + 外部回调触发。 +- **父子客户不联动**:子客户阶段改变不影响父客户(原型无相关规则)。 + +### B. 存储 + 迁移约束(定稿) + +- `customer.customer_stage` tinyint default 1(票 02 已落)。 +- 迁移 SQL 形态:`UPDATE customer SET customer_stage=? WHERE id=? AND customer_stage < ?`(只前进 CAS,天然幂等,无需去重表)。 +- 迁移失败(已是更高阶段)= no-op 静默成功,不报错不写日志。 +- **每次变化**:写操作日志(票 07 oplog,操作人=`系统` + 注明触发来源)+ 刷新公共头部。 + +### C. 重潜触发点 — 已翻案(grill Q10 拍板 A/保守 → 第三轮 fresh 复核,20260902 用户拍板 B) + +原型 A4-4-1(8-26 fresh 复核确认)明文「任一方案卡**提交并报备成功**后更新为重潜」——是**两个条件**(提交 + 报备成功)。「报备/推送成功」在商机图是 **spin-out 本期不可达**(商机图票 07:方案卡状态=草稿1/已提交2/已推送3;推送链路本期不可达,仅 A5 回调特殊路径可达)。 + +**旧拍板(grill Q10=A/保守,已废)**:曾决定本期先按「方案卡状态=已提交(2)」凑合触发重潜,理由是怕客户阶段永久卡在潜在。 + +**✅ 新拍板(第三轮复核,20260902 用户选 B = 严格/不凑合)**:**本期不自动触发潜在→重潜**。理由:①原型要的是「提交**并报备成功**」两个条件,用「已提交」单条件凑合会造出「提交了但报备没成功、客户却已重潜」的**错误状态**,污染阶段派生字段;②`CustomerStagePort.promote` 的 **seam 全部保留**(契约、只前进 CAS、幂等),只是本期无调用方;③ 待报备成功链路就绪(方案卡「已推送(3)」或 A5 回调)后,由该链路调用 `promote(customerId, 2, "scheme_card_reported")` 触发重潜,改动是接入调用点、非改状态机。**本期客户阶段实际只有「潜在」**(字段/枚举/CAS 机制仍按三态建好)。 + +### D. seam 契约(定稿) + +```java +// crm-customer 侧暴露的端口(消费方 = crm-opportunity 方案卡 / 未来 A5 项目) +public interface CustomerStagePort { + /** 提升阶段;只前进,传入更低阶段 = no-op;返回提升后阶段 */ + int promote(Long customerId, int targetStage, String triggerSource); +} +``` + +- `triggerSource` 示例:`scheme_card_reported`(方案卡提交并报备成功,本期链路不可达 seam)/ `project_won`(已成交,A5 seam 占位)。~~`scheme_card_submitted`~~ 已弃用(D20 改票 B,不用「已提交」凑合)。 +- crm-opportunity 方案卡状态机在「已提交」迁移边(或收紧后「已推送」边)**同步调用**此 port(对称 lead→opp port 的同步事务内调用范式;失败抛异常使方案卡事务回滚?→ **不**:阶段提升失败**不阻断**方案卡提交,记日志降级,见 E)。 + +### E. 失败语义(定稿,重要) + +- **阶段提升失败不阻断主业务**:方案卡提交成功但客户阶段提升失败时,方案卡正常提交,阶段提升记日志告警 + 可重试(幂等 no-op 使重试安全)。 +- 理由:客户阶段是**派生冗余视图**(由业务成功结果驱动),不是主业务的一致性边界;用「尽力而为 + 幂等重试」而非「同步强一致回滚」,避免一个派生字段的失败拖垮方案卡主流程。 +- 对称:lead→opp port 是强一致(建商机失败回滚线索),但那是**主业务**;customer_stage 是**派生**,弱一致即可。 + +### F. 回填 / 下游 + +- **票 02**:`customer_stage` 已含。 +- **票 07**:oplog 记录阶段变化(操作人=系统)。 +- **票 01**:CONTEXT.md 术语「客户阶段」。 diff --git a/.scratch/customer-module/issues/05-customer-stage.md b/.scratch/customer-module/issues/05-customer-stage.md index 410e58e..f4374ee 100644 --- a/.scratch/customer-module/issues/05-customer-stage.md +++ b/.scratch/customer-module/issues/05-customer-stage.md @@ -1,7 +1,7 @@ # 05 — 客户阶段(潜在→重潜→已成交,只前进) Type: research -Status: open +Status: resolved Blocked by: 02 ## 问题 @@ -33,4 +33,58 @@ Blocked by: 02 ## Answer -_(待 resolve)_ +**Status: resolved**(含 1 个 ⚠ 产品决策挂号,不阻塞 spec 定稿) + +### A. 三态定义(定稿) + +| 值 | 名称 | 驱动源 | 本图 | +|---|---|---|---| +| 1 | 潜在客户 | 新建客户成功默认值 | ✅ | +| 2 | 重潜客户 | 关联的任一方案卡「提交并报备成功」 | ✅(触发点见 C 挂号) | +| 3 | 已成交客户 | 关联的任一项目「首次中标赢单」 | ❌ 留 seam 给 A5 | + +- **只前进不回退**(原型 A4-4-1 §1 明文:方案卡失败/报备失败/商机输单/项目结束**均不改变**阶段)。 +- **不可手动改**:阶段条不可点击;无面向前端的 setter API;仅内部 port + 外部回调触发。 +- **父子客户不联动**:子客户阶段改变不影响父客户(原型无相关规则)。 + +### B. 存储 + 迁移约束(定稿) + +- `customer.customer_stage` tinyint default 1(票 02 已落)。 +- 迁移 SQL 形态:`UPDATE customer SET customer_stage=? WHERE id=? AND customer_stage < ?`(只前进 CAS,天然幂等,无需去重表)。 +- 迁移失败(已是更高阶段)= no-op 静默成功,不报错不写日志。 +- **每次变化**:写操作日志(票 07 oplog,操作人=`系统` + 注明触发来源)+ 刷新公共头部。 + +### C. ⚠ 挂号:重潜的触发点(需产品拍板) + +原型 A4-4-1 说「任一方案卡**提交并报备成功**后更新为重潜」。但「报备/推送」在商机图是 **spin-out 不可达**(商机图票 07:方案卡状态=草稿1/已提交2/已推送3;推送链路本期不可达,仅 A5 回调特殊路径可达)。 + +**我的推荐(保守)**:本期**先按「方案卡状态=已提交(2)」触发重潜**——理由:①「提交」是方案卡状态机本期内确定可达的一步;②「报备成功」依赖推送链路(spin-out),若死等它,客户阶段会永久卡在潜在,功能形同虚设;③ 报备链路就绪后只需把触发点从「已提交」收紧到「已推送(报备成功)」,改动是一行状态判断。 + +**备选(严格)**:本期不触发重潜,等推送链路建好再放。客户阶段在 push 链路就绪前永远停在潜在。 + +> 你选哪个?选保守(推荐)则票 05 就此冻结;选严格则票 05 标注「重潜触发点 = 待推送链路」。 + +### D. seam 契约(定稿) + +```java +// crm-customer 侧暴露的端口(消费方 = crm-opportunity 方案卡 / 未来 A5 项目) +public interface CustomerStagePort { + /** 提升阶段;只前进,传入更低阶段 = no-op;返回提升后阶段 */ + int promote(Long customerId, int targetStage, String triggerSource); +} +``` + +- `triggerSource` 示例:`scheme_card_submitted` / `project_won`(已成交,A5 seam 占位)。 +- crm-opportunity 方案卡状态机在「已提交」迁移边(或收紧后「已推送」边)**同步调用**此 port(对称 lead→opp port 的同步事务内调用范式;失败抛异常使方案卡事务回滚?→ **不**:阶段提升失败**不阻断**方案卡提交,记日志降级,见 E)。 + +### E. 失败语义(定稿,重要) + +- **阶段提升失败不阻断主业务**:方案卡提交成功但客户阶段提升失败时,方案卡正常提交,阶段提升记日志告警 + 可重试(幂等 no-op 使重试安全)。 +- 理由:客户阶段是**派生冗余视图**(由业务成功结果驱动),不是主业务的一致性边界;用「尽力而为 + 幂等重试」而非「同步强一致回滚」,避免一个派生字段的失败拖垮方案卡主流程。 +- 对称:lead→opp port 是强一致(建商机失败回滚线索),但那是**主业务**;customer_stage 是**派生**,弱一致即可。 + +### F. 回填 / 下游 + +- **票 02**:`customer_stage` 已含。 +- **票 07**:oplog 记录阶段变化(操作人=系统)。 +- **票 01**:CONTEXT.md 术语「客户阶段」。 diff --git a/.scratch/customer-module/issues/06-answer.md b/.scratch/customer-module/issues/06-answer.md new file mode 100644 index 0000000..7e6b528 --- /dev/null +++ b/.scratch/customer-module/issues/06-answer.md @@ -0,0 +1,76 @@ +## Answer + +**Status: resolved** + +### A. 实体 `customer_contact`(crm-customer,独立表) + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| id | id | bigint | | 主键 ASSIGN_ID | +| customerId | customer_id | bigint | ✅ | 所属客户;NOT NULL,无孤立联系人 | +| name | name | varchar(50) | ✅ | 联系人姓名 | +| jobTitleName | job_title_name | varchar(100) | ✅ | 职务名称(文本,始终存) | +| jobTitleId | job_title_id | bigint | | 标准职务 id(字典/预置表),自定义职务时为 NULL | +| jobTitleCategory | job_title_category | varchar(50) | | 职务类别(高层管理/采购/…),自定义职务时为 NULL | +| jobTitleLevel | job_title_level | int | | 职务层级(图谱用),自定义职务时为 NULL | +| phone | phone | varchar(20) | | 联系电话;手机号格式校验 | +| isKeyContact | is_key_contact | tinyint | | 是否关键联系人(是1/否0) | +| isInternal | is_internal | tinyint | | 是否内线(是1/否0) | +| giftRemark | gift_remark | varchar(500) | | 礼品备注 | +| source | source | varchar(20) | | 来源字典 `contact_source`:组会/组局/转介绍/其他 | +| createBy / createTime / updateBy / updateTime | — | — | | BaseEntity | +| deleted | deleted | tinyint default 0 | | 逻辑删除 | + +> ⚠ **孤立单大写字母检查**(AGENTS.md):`isKeyContact`→`is_key_contact`、`isInternal`→`is_internal`、`jobTitleName`/`jobTitleId`/`jobTitleCategory`/`jobTitleLevel`——均为「完整词 + 完整词」,**无需 `@Column(name=...)`**。 + +**索引**: +- `idx_contact_customer` (customer_id, deleted) +- ~~`uk_contact_phone` (phone) 全局唯一~~ **已撤销(D26 第三轮翻案,20260902 用户拍板 A)**:不建电话唯一索引。电话查重=可配置软提示(跟随 crm-rule 客户查重设置「联系电话」开关,默认开),命中只提示疑似重复、不禁止创建。仅保留**同一客户内**两个联系人电话不重复的表单级校验(票 04 B4 内联查重) +- `idx_contact_name` (name) — 全局搜索 + +**权限模型(定稿,原型明文「联系人不设独立 ACL,继承客户权限」)**: +- 无独立 `@DataScope` 注解;服务层显式按 customer 校验(对客户有读权限 → 对其全部联系人有读权限)。 +- 无客户读写权限时隐藏联系人新增/编辑/删除入口;服务端仍校验。 + +### B. 契约(对齐商机硬依赖,promote 自 opp-customer-dependency 分析报告) + +| 优先级 | 接口 | 说明 | +|---|---|---| +| P0 | `POST /api/customer/search` | 客户搜索(名称/联系人/电话/类型 四维模糊 + 分页),商机新增关联客户数据源 | +| P0 | `GET /api/customer/{id}/contacts` | 按客户查联系人列表 | +| P0 | `GET /api/customer/{id}` | 客户基础信息带出 | +| P1 | `GET /api/customer/contact/search` | 联系人全局搜索(带出所属公司名),商机「商机关键联系人」数据源 | +| P1 | `POST /api/customer/quick-create` | 快速创建客户(最小必填集),商机弹窗入口 | +| P2 | `GET /api/customer/{id}/detail` | 客户详情(新窗口跳转),复用票 07 详情页 | + +### C. 联系人 CRUD 契约(定稿) + +``` +POST /api/customer/contact 新增联系人(可指定 customer_id 或独立新增后关联) +PUT /api/customer/contact/{id} 编辑 +DELETE /api/customer/contact/{id} 删除(软删) +GET /api/customer/contact/{id} 详情 +GET /api/customer/contact/page 顶级联系人列表(A4-5-1,跨客户,按 DataScope) +POST /api/customer/contact/import 批量导入(划出本图 → 随图谱 effort,本图只留接口占位) +``` + +### D. 随客户新增的内嵌明细(定稿) + +- 新增客户表单「联系人」Tab = 明细表数组,**客户保存成功后**与客户建立关联(原型 3.5 §1)。 +- 电话内联查重:**同一客户内**两行电话相同禁止提交(表单级硬校验);跨客户电话精确命中=**软提示疑似重复、不阻断**(跟随查重设置「联系电话」开关,D26 翻案后口径,票 04 B4 同步收敛为提示)。 +- 删除仅移除本次未保存行;已输入内容行删除前确认。 + +### E. 产品确认项 — 已随 grill 全部销号 + +| # | 问题 | 影响 | +|---|---|---| +| P0-1 | ~~联系人电话唯一约束(grill Q16=B 全局唯一)~~ **已翻案(D26 第三轮复核,20260902 用户拍板 A)**:不建 `uk_contact_phone` 唯一索引;电话查重=可配置软提示(跟随查重设置「联系电话」开关),命中只提示不阻断;仅同客户内联系人电话不重复为表单级校验 | 撤销唯一索引;软提示走查重设置开关 | +| P1-1 | ~~「来源」字典完整值~~ **已拍板(grill Q17)**:种子 = 组会/组局/转介绍/其他 4 项,后续在字典补 | crm-dict 种子 | + +### F. 回填 / 下游 + +- **票 02**:无主表字段变化(联系人是子表)。 +- **票 04**:联系人内联查重(B4)已回填。 +- **票 07**:客户详情「联系人」页签复用 `GET /api/customer/{id}/contacts`。 +- **票 01**:CONTEXT.md 术语「联系人」「关键联系人」。 +- **crm-dict**:新增 `contact_source` 分组 + `job_title` 标准职务预置(图谱 effort 用它,本图只留 jobTitleId 引用位)。 diff --git a/.scratch/customer-module/issues/06-contacts-crud.md b/.scratch/customer-module/issues/06-contacts-crud.md index b101462..e19b02b 100644 --- a/.scratch/customer-module/issues/06-contacts-crud.md +++ b/.scratch/customer-module/issues/06-contacts-crud.md @@ -1,7 +1,7 @@ # 06 — 联系人建模 CRUD + 全局搜索 + 商机侧硬依赖接口 Type: research -Status: open +Status: resolved Blocked by: 02 ## 问题 @@ -54,4 +54,77 @@ Blocked by: 02 ## Answer -_(待 resolve)_ +**Status: resolved** + +### A. 实体 `customer_contact`(crm-customer,独立表) + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| id | id | bigint | | 主键 ASSIGN_ID | +| customerId | customer_id | bigint | ✅ | 所属客户;NOT NULL,无孤立联系人 | +| name | name | varchar(50) | ✅ | 联系人姓名 | +| jobTitleName | job_title_name | varchar(100) | ✅ | 职务名称(文本,始终存) | +| jobTitleId | job_title_id | bigint | | 标准职务 id(字典/预置表),自定义职务时为 NULL | +| jobTitleCategory | job_title_category | varchar(50) | | 职务类别(高层管理/采购/…),自定义职务时为 NULL | +| jobTitleLevel | job_title_level | int | | 职务层级(图谱用),自定义职务时为 NULL | +| phone | phone | varchar(20) | | 联系电话;手机号格式校验 | +| isKeyContact | is_key_contact | tinyint | | 是否关键联系人(是1/否0) | +| isInternal | is_internal | tinyint | | 是否内线(是1/否0) | +| giftRemark | gift_remark | varchar(500) | | 礼品备注 | +| source | source | varchar(20) | | 来源字典 `contact_source`:组会/组局/转介绍/其他 | +| createBy / createTime / updateBy / updateTime | — | — | | BaseEntity | +| deleted | deleted | tinyint default 0 | | 逻辑删除 | + +> ⚠ **孤立单大写字母检查**(AGENTS.md):`isKeyContact`→`is_key_contact`、`isInternal`→`is_internal`、`jobTitleName`/`jobTitleId`/`jobTitleCategory`/`jobTitleLevel`——均为「完整词 + 完整词」,**无需 `@Column(name=...)`**。 + +**索引**: +- `idx_contact_customer` (customer_id, deleted) +- `uk_contact_phone` (phone) — **仅手机号唯一**;NULL 不参与(无电话时允许,票 04 B4 内联查重兜底) +- `idx_contact_name` (name) — 全局搜索 + +**权限模型(定稿,原型明文「联系人不设独立 ACL,继承客户权限」)**: +- 无独立 `@DataScope` 注解;服务层显式按 customer 校验(对客户有读权限 → 对其全部联系人有读权限)。 +- 无客户读写权限时隐藏联系人新增/编辑/删除入口;服务端仍校验。 + +### B. 契约(对齐商机硬依赖,promote 自 opp-customer-dependency 分析报告) + +| 优先级 | 接口 | 说明 | +|---|---|---| +| P0 | `POST /api/customer/search` | 客户搜索(名称/联系人/电话/类型 四维模糊 + 分页),商机新增关联客户数据源 | +| P0 | `GET /api/customer/{id}/contacts` | 按客户查联系人列表 | +| P0 | `GET /api/customer/{id}` | 客户基础信息带出 | +| P1 | `GET /api/customer/contact/search` | 联系人全局搜索(带出所属公司名),商机「商机关键联系人」数据源 | +| P1 | `POST /api/customer/quick-create` | 快速创建客户(最小必填集),商机弹窗入口 | +| P2 | `GET /api/customer/{id}/detail` | 客户详情(新窗口跳转),复用票 07 详情页 | + +### C. 联系人 CRUD 契约(定稿) + +``` +POST /api/customer/contact 新增联系人(可指定 customer_id 或独立新增后关联) +PUT /api/customer/contact/{id} 编辑 +DELETE /api/customer/contact/{id} 删除(软删) +GET /api/customer/contact/{id} 详情 +GET /api/customer/contact/page 顶级联系人列表(A4-5-1,跨客户,按 DataScope) +POST /api/customer/contact/import 批量导入(划出本图 → 随图谱 effort,本图只留接口占位) +``` + +### D. 随客户新增的内嵌明细(定稿) + +- 新增客户表单「联系人」Tab = 明细表数组,**客户保存成功后**与客户建立关联(原型 3.5 §1)。 +- 电话内联查重:同表电话相同两行禁止提交;全库电话精确查重(票 04 B4)。 +- 删除仅移除本次未保存行;已输入内容行删除前确认。 + +### E. 待产品确认(⚠) + +| # | 问题 | 影响 | +|---|---|---| +| P0-1 | 联系人电话唯一约束:**手机号全局唯一** vs 允许一号多人(共号场景)?原型「完全相同禁止新建」倾向唯一——但座机分机号可能重复。**推荐:仅手机号(11位)唯一,座机/短号不做唯一**,`phone` 单列存但唯一索引带条件(`phone IS NOT NULL AND LENGTH(phone)=11` 或用生成列)。 | `uk_contact_phone` 实现 | +| P1-1 | 「来源」字典 `contact_source` 完整值(组会/组局/转介绍/其他,原型明列 4 项,是否还要更多) | crm-dict 种子 | + +### F. 回填 / 下游 + +- **票 02**:无主表字段变化(联系人是子表)。 +- **票 04**:联系人内联查重(B4)已回填。 +- **票 07**:客户详情「联系人」页签复用 `GET /api/customer/{id}/contacts`。 +- **票 01**:CONTEXT.md 术语「联系人」「关键联系人」。 +- **crm-dict**:新增 `contact_source` 分组 + `job_title` 标准职务预置(图谱 effort 用它,本图只留 jobTitleId 引用位)。 diff --git a/.scratch/customer-module/issues/07-answer.md b/.scratch/customer-module/issues/07-answer.md new file mode 100644 index 0000000..fc98f40 --- /dev/null +++ b/.scratch/customer-module/issues/07-answer.md @@ -0,0 +1,86 @@ +## Answer + +**Status: resolved** + +### A. 客户详情页骨架(定稿) + +**公共头部**(8 页签共享):客户名称 / 客户阶段(只读条) / 客户星级 / 关系星级 / 战略协议等级(快照) / 已成交VIP等级(快照) / 客户档案状态 / 销售负责人+部门 / 最近跟进摘要 / 待跟进提醒(下次跟进时间) / 关联业务计数(1商机/1项目)。 + +**汇总卡片**(4 张,均外部模块提供,接口失败显"--"+重试,不显 0):已中标项目总额(A5) / 跟进中方案预估总额(方案卡) / 进行中商机总额(crm-opportunity) / 合同总额·累计回款(预留占位显"--")。 + +**8 页签**(默认进「客户信息」;首次打开才加载,切回保留条件/分页): + +| # | 页签 | 数据源 | 本图 | +|---|---|---|---| +| 1 | 客户信息 | 主表(票 02) | ✅ | +| 2 | 联系人 | `GET /api/customer/{id}/contacts`(票 06) | ✅ | +| 3 | 跟进记录 | `customer_follow`(B) | ✅ | +| 4 | 关联商机 | 反查 crm-opportunity(C) | ✅ | +| 5 | 关联项目 | A5 项目模块 | ❌ 骨架显"--" | +| 6 | 战略协议 | 战略协议子域 | ❌ 骨架显"--" | +| 7 | 团队成员 | 负责人+协同人(D) | ✅ | +| 8 | 操作日志 | `customer_oplog`(E) | ✅ | + +### B. 跟进记录 `customer_follow`(定稿) + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| id | id | bigint | | 主键 | +| customerId | customer_id | bigint | ✅ | 客户 ID | +| followWay | follow_way | varchar(20) | ✅ | 跟进方式字典 `follow_way`:微信/拜访/电话/邮件/会议 | +| followContent | follow_content | text | ✅ | 跟进内容 | +| nextFollowTime | next_follow_time | datetime | | 下次跟进时间(有则进"待跟进"筛选) | +| followBy / followByName / followDeptName | follow_by / follow_by_name / follow_dept_name | — | | 跟进人快照(对称商机跟进范式) | +| createTime | create_time | — | | 跟进时间(=创建时间,不可改) | + +> 无孤立单大写字母陷阱。`nextFollowTime`→`next_follow_time` ✅。 + +**约束**: +- 提交后**不可修改/删除**;纠错 = 新增补充记录(原型 7 明文)。 +- 提交中禁止重复提交(原型公共规则)。 +- **写库副作用(关键)**:`customer.last_valid_follow_time = now`(超期提醒锚点刷新,票 10 回填);更新"待跟进"状态。 + +**契约**: +``` +POST /api/customer/{id}/follow 新增跟进(刷新 last_valid_follow_time) +GET /api/customer/{id}/follow/page 跟进记录分页(时间倒序 + 日期分组;按 follow_way/关键字/时间范围过滤) +``` + +### C. 关联商机页签(定稿,只读反查) + +- 数据源:**反查 crm-opportunity**(按 `customer_id` 查该客户全部商机)。 +- 列:商机名称 / 商机编号 / 阶段 / 预计金额 / 销售负责人 / 最近更新;点商机名跳 crm-opportunity 商机详情(新窗口)。 +- **只读**,不在客户详情内做商机写操作(对称:商机详情反查客户也只是带出)。 +- 接口失败显"--" + 重试,不显 0 条。 + +### D. 团队成员页签(定稿) + +- 数据:销售负责人(owner_user_id 快照)+ 协同人列表(团队成员关联表)。 +- 展示:姓名 / 部门 / 角色(负责人/协同人);`GET /api/customer/{id}/members`。 + +### E. 操作日志 `customer_oplog`(定稿,对称商机 oplog 范式) + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint | 主键 | +| customerId | customer_id | bigint | 客户 ID | +| action | action | varchar(50) | 动作类型:CREATE/UPDATE/FOLLOW/ARCHIVE/RESTORE/TRANSFER/CLAIM/POOL/STAGE_CHANGE | +| detail | detail | varchar(1000) | 变更描述,形态 `把 {字段} 从 {旧} 修改为 {新}`(原型 8 明文) | +| operatorId / operatorName / operatorDeptName | operator_id / operator_name / operator_dept_name | — | 操作人快照 | +| createTime | create_time | — | 操作时间 | + +- **只追加、不可改删**;`GET /api/customer/{id}/oplog/page`(时间倒序 + 日期分组 + 关键字/时间范围过滤)。 +- **保留策略(§5.5 拍板 A,20260902)**:`customer_oplog` **无限期保留**(审计日志,不设保留期/清理/归档;不做冷热分离)。依据:原型 L250「日志生成后不可修改或删除」= append-only 审计;字段级日志量可控。 +- **写 oplog 的动作**(回填各票):新增/编辑(票 02)/ 归属迁移·归档·恢复·领取·分配·抛公海(票 03)/ 阶段变化 operator=系统(票 05)/ 交割(票 08)。 + +### F. 回填 / 下游 + +- **票 02**:`last_valid_follow_time` 已含(B 刷新)。 +- **票 03**:归属迁移写 oplog(E)。 +- **票 05**:阶段变化写 oplog(operator=系统)。 +- **票 10**:跟进刷新锚点 → 超期提醒计时重启(B 副作用)。 +- **crm-dict**:新增 `follow_way` 分组(微信/拜访/电话/邮件/会议)。 + +### G. 挂号 — 已随 grill 全部销号 + +1. ~~汇总卡片占位渲染~~ **已拍板(grill Q18)**:一律渲染卡片,值显"--"(不显 0/不留白),保持布局稳定。 diff --git a/.scratch/customer-module/issues/07-detail-follow-oplog.md b/.scratch/customer-module/issues/07-detail-follow-oplog.md index 6d85522..74c9e50 100644 --- a/.scratch/customer-module/issues/07-detail-follow-oplog.md +++ b/.scratch/customer-module/issues/07-detail-follow-oplog.md @@ -1,7 +1,7 @@ # 07 — 客户详情 8 页签 + 跟进记录 + 操作日志 Type: research -Status: open +Status: resolved Blocked by: 02, 05 ## 问题 @@ -50,4 +50,86 @@ Blocked by: 02, 05 ## Answer -_(待 resolve)_ +**Status: resolved** + +### A. 客户详情页骨架(定稿) + +**公共头部**(8 页签共享):客户名称 / 客户阶段(只读条) / 客户星级 / 关系星级 / 战略协议等级(快照) / 已成交VIP等级(快照) / 客户档案状态 / 销售负责人+部门 / 最近跟进摘要 / 待跟进提醒(下次跟进时间) / 关联业务计数(1商机/1项目)。 + +**汇总卡片**(4 张,均外部模块提供,接口失败显"--"+重试,不显 0):已中标项目总额(A5) / 跟进中方案预估总额(方案卡) / 进行中商机总额(crm-opportunity) / 合同总额·累计回款(预留占位显"--")。 + +**8 页签**(默认进「客户信息」;首次打开才加载,切回保留条件/分页): + +| # | 页签 | 数据源 | 本图 | +|---|---|---|---| +| 1 | 客户信息 | 主表(票 02) | ✅ | +| 2 | 联系人 | `GET /api/customer/{id}/contacts`(票 06) | ✅ | +| 3 | 跟进记录 | `customer_follow`(B) | ✅ | +| 4 | 关联商机 | 反查 crm-opportunity(C) | ✅ | +| 5 | 关联项目 | A5 项目模块 | ❌ 骨架显"--" | +| 6 | 战略协议 | 战略协议子域 | ❌ 骨架显"--" | +| 7 | 团队成员 | 负责人+协同人(D) | ✅ | +| 8 | 操作日志 | `customer_oplog`(E) | ✅ | + +### B. 跟进记录 `customer_follow`(定稿) + +| Java 字段 | 列名 | 类型 | 必填 | 说明 | +|---|---|---|---|---| +| id | id | bigint | | 主键 | +| customerId | customer_id | bigint | ✅ | 客户 ID | +| followWay | follow_way | varchar(20) | ✅ | 跟进方式字典 `follow_way`:微信/拜访/电话/邮件/会议 | +| followContent | follow_content | text | ✅ | 跟进内容 | +| nextFollowTime | next_follow_time | datetime | | 下次跟进时间(有则进"待跟进"筛选) | +| followBy / followByName / followDeptName | follow_by / follow_by_name / follow_dept_name | — | | 跟进人快照(对称商机跟进范式) | +| createTime | create_time | — | | 跟进时间(=创建时间,不可改) | + +> 无孤立单大写字母陷阱。`nextFollowTime`→`next_follow_time` ✅。 + +**约束**: +- 提交后**不可修改/删除**;纠错 = 新增补充记录(原型 7 明文)。 +- 提交中禁止重复提交(原型公共规则)。 +- **写库副作用(关键)**:`customer.last_valid_follow_time = now`(超期提醒锚点刷新,票 10 回填);更新"待跟进"状态。 + +**契约**: +``` +POST /api/customer/{id}/follow 新增跟进(刷新 last_valid_follow_time) +GET /api/customer/{id}/follow/page 跟进记录分页(时间倒序 + 日期分组;按 follow_way/关键字/时间范围过滤) +``` + +### C. 关联商机页签(定稿,只读反查) + +- 数据源:**反查 crm-opportunity**(按 `customer_id` 查该客户全部商机)。 +- 列:商机名称 / 商机编号 / 阶段 / 预计金额 / 销售负责人 / 最近更新;点商机名跳 crm-opportunity 商机详情(新窗口)。 +- **只读**,不在客户详情内做商机写操作(对称:商机详情反查客户也只是带出)。 +- 接口失败显"--" + 重试,不显 0 条。 + +### D. 团队成员页签(定稿) + +- 数据:销售负责人(owner_user_id 快照)+ 协同人列表(团队成员关联表)。 +- 展示:姓名 / 部门 / 角色(负责人/协同人);`GET /api/customer/{id}/members`。 + +### E. 操作日志 `customer_oplog`(定稿,对称商机 oplog 范式) + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint | 主键 | +| customerId | customer_id | bigint | 客户 ID | +| action | action | varchar(50) | 动作类型:CREATE/UPDATE/FOLLOW/ARCHIVE/RESTORE/TRANSFER/CLAIM/POOL/STAGE_CHANGE | +| detail | detail | varchar(1000) | 变更描述,形态 `把 {字段} 从 {旧} 修改为 {新}`(原型 8 明文) | +| operatorId / operatorName / operatorDeptName | operator_id / operator_name / operator_dept_name | — | 操作人快照 | +| createTime | create_time | — | 操作时间 | + +- **只追加、不可改删**;`GET /api/customer/{id}/oplog/page`(时间倒序 + 日期分组 + 关键字/时间范围过滤)。 +- **写 oplog 的动作**(回填各票):新增/编辑(票 02)/ 归属迁移·归档·恢复·领取·分配·抛公海(票 03)/ 阶段变化 operator=系统(票 05)/ 交割(票 08)。 + +### F. 回填 / 下游 + +- **票 02**:`last_valid_follow_time` 已含(B 刷新)。 +- **票 03**:归属迁移写 oplog(E)。 +- **票 05**:阶段变化写 oplog(operator=系统)。 +- **票 10**:跟进刷新锚点 → 超期提醒计时重启(B 副作用)。 +- **crm-dict**:新增 `follow_way` 分组(微信/拜访/电话/邮件/会议)。 + +### G. ⚠ 挂号(需拍板) + +1. 汇总卡片 4 张里「合同总额/累计回款」原型即预留占位——是否本期就显"--",还是连卡片都不渲染?**推荐:渲染卡片但值显"--"**(保持布局稳定)。 diff --git a/.scratch/customer-module/issues/08-answer.md b/.scratch/customer-module/issues/08-answer.md new file mode 100644 index 0000000..1291b90 --- /dev/null +++ b/.scratch/customer-module/issues/08-answer.md @@ -0,0 +1,97 @@ +## Answer + +**Status: resolved**(直属销售总监 = 组织架构推导,已由用户在 grill Q12 拍板 A) + +### A. 流程模型(定稿,两段式) + +``` +发起交接(离职人员→接收总监) → 客户分配(接收总监→具体销售) + owner 变为总监 owner 变为销售 + 生成交接单(待分配) 逐客户/批量分配 +``` + +- **只允许离职/调动/区域调整的销售本人发起**本人名下客户的交接(主管/管理员不可代办,原型二.2 明文)。 +- **可交接客户** = 该销售为**唯一负责人** 且 未删除 且 非公海;仅协同人的客户不纳入。 +- **接收总监**:离职 = 系统自动确定**唯一**直属销售总监;调动/区域调整 = 在本人**启用的**直属销售总监范围内搜索选择。 +- **交接单**:全部变更成功才生成(整批原子);生成唯一交接编号 + 状态「待分配」+ 进度 0/N。 + +### B. 实体(定稿) + +#### B1. 交接单 `customer_transfer` + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint | 主键 | +| transferNo | transfer_no | varchar(32) | 交接编号(唯一,生成规则对称商机编号) | +| fromUserId / fromUserName | from_user_id / from_user_name | — | 离职/调动人员快照 | +| toDirectorId / toDirectorName | to_director_id / to_director_name | — | 接收总监快照 | +| reason | reason | varchar(20) | 交接原因字典:员工离职/岗位调动/区域调整 | +| remark | remark | varchar(500) | 备注(选填) | +| totalCount | total_count | int | 本次交接客户总数 | +| assignedCount | assigned_count | int | 已分配数(进度 = assigned/total) | +| status | status | tinyint | 0待分配 / 1已完成 | +| createBy / createTime / updateBy / updateTime | — | — | BaseEntity | + +> 无孤立单大写字母陷阱。 + +#### B2. 交接明细 `customer_transfer_detail` + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint | 主键 | +| transferId | transfer_id | bigint | 交接单 id | +| customerId | customer_id | bigint | 客户 id | +| assignStatus | assign_status | tinyint | 0待分配 / 1已分配 | +| assignUserId / assignUserName | assign_user_id / assign_user_name | — | 分配对象快照(待分配时 NULL) | +| assignTime | assign_time | datetime | 分配时间 | + +- `UNIQUE(transfer_id, customer_id)` 去重。 + +### C. 事务语义(定稿,跨模块) + +1. **发起交接**(整批原子): + - 遍历本次客户,对每个客户:`owner_user_id → to_director_id`;移出 from_user 团队身份;保留原协同人。 + - 对每个客户的**关联商机**(crm-opportunity)`owner_user_id → to_director_id`。 + - **关联项目 owner 同步 = seam**(A5 未建;本图只定义 port 契约,实现时项目侧跳过并标注)。 + - **任一失败 → 全部回滚**,不生成交接单(原型三.确认交接 §7 明文)。 +2. **客户分配**(整批/单个): + - 待分配明细 → 选分配对象(接收总监有权分配范围 + 启用的销售 + 不得为 from_user 本人 + 不得是当前负责人)→ `owner_user_id → assign_user_id`,同步关联商机/项目 owner。 + - 单个失败不整体回滚(批量按逐条独立事务),失败项列原因。 +3. **防重入**:同一 from_user 存在未完成交接单时,禁止再次发起(原型三.确认交接 §1)。 + +### D. 契约(定稿) + +``` +POST /api/customer/transfer/initiate 发起交接(整批原子,返回交接编号) +GET /api/customer/transfer/{id}/preview 发起前客户预览(按原因算可交接客户) +GET /api/customer/transfer/{id} 交接详情 +GET /api/customer/transfer/page 交接记录(交接编号/人员/原因/状态过滤) +POST /api/customer/transfer/{id}/assign 客户分配(单个或批量 customerIds + assignUserId) +GET /api/customer/transfer/{id}/assignable 可分配销售列表(接收总监范围 + 启用) +``` + +### E. 直属销售总监 — 已拍板(grill Q12,组织架构推导) + +原型要求:离职时系统**自动确定唯一直属销售总监**;调动/区域调整时在**启用的直属销售总监范围**选择。核心是「直属销售总监」的确定口径: + +**我的推荐**:**不加 SysUser 字段**,用**组织架构现有层级**推导——按 from_user 所在部门向上找「部门负责人」(`dept.leader_user_id` 或等价),若该负责人是「销售总监」角色则命中;无/停用/多条则按原型阻断并提示「联系管理员维护组织关系」。 + +**理由**:① 组织架构已有部门负责人概念,不引入新字段;② 直属关系是组织属性而非用户属性,加 `superior_user_id` 会带来维护和一致性问题;③ 原型明说「无/停用/多条直属关系 → 阻断」,天然容忍推导失败。 + +**备选**:`SysUser` 加 `superior_user_id`(直属上级),由后台维护;查询 O(1),但需改 auth 模块 + 维护成本。 + +**✅ 已拍板(grill Q12,用户选 A)**:组织架构推导(`dept.leader` 向上找销售总监角色),不改 crm-auth,不加 `superior_user_id`。 + +### F. 回填 / 下游 + +- **票 03**:owner 变更(分配=换主)复用 oplog 语义;交割是分配动作的跨模块扩展版。 +- **票 07**:oplog 记录 `TRANSFER` 动作 + `customer_transfer` 表变更;跨模块同步操作人显「系统」+ 交接编号。 +- **crm-opportunity**:需要商机侧提供「按 customer_id 批量改 owner」的 port(或复用商机分配逻辑)。 +- **A5 项目**:项目 owner 同步 seam(port 契约占位)。 +- **crm-dict**:`transfer_reason` 分组(员工离职/岗位调动/区域调整)。 + +### G. 产品确认项 — 已随 grill 全部销号 + +| # | 问题 | 影响 | +|---|---|---| +| P0-1 | ~~交接单「已完成」的判定~~ **已拍板(grill Q13)**:自动完成(assignedCount == totalCount 置为已完成),不设手动「完成交接」按钮 | status 迁移 | diff --git a/.scratch/customer-module/issues/08-transfer.md b/.scratch/customer-module/issues/08-transfer.md index 94fa928..08a009c 100644 --- a/.scratch/customer-module/issues/08-transfer.md +++ b/.scratch/customer-module/issues/08-transfer.md @@ -1,7 +1,7 @@ # 08 — 客户交割(跨模块同步商机/项目负责人) Type: research -Status: open +Status: resolved Blocked by: 02, 03 ## 问题 @@ -44,4 +44,98 @@ Blocked by: 02, 03 ## Answer -_(待 resolve)_ +**Status: resolved**(含 1 个 ⚠ 决策挂号:直属销售总监确定方式) + +### A. 流程模型(定稿,两段式) + +``` +发起交接(离职人员→接收总监) → 客户分配(接收总监→具体销售) + owner 变为总监 owner 变为销售 + 生成交接单(待分配) 逐客户/批量分配 +``` + +- **只允许离职/调动/区域调整的销售本人发起**本人名下客户的交接(主管/管理员不可代办,原型二.2 明文)。 +- **可交接客户** = 该销售为**唯一负责人** 且 未删除 且 非公海;仅协同人的客户不纳入。 +- **接收总监**:离职 = 系统自动确定**唯一**直属销售总监;调动/区域调整 = 在本人**启用的**直属销售总监范围内搜索选择。 +- **交接单**:全部变更成功才生成(整批原子);生成唯一交接编号 + 状态「待分配」+ 进度 0/N。 + +### B. 实体(定稿) + +#### B1. 交接单 `customer_transfer` + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint | 主键 | +| transferNo | transfer_no | varchar(32) | 交接编号(唯一,生成规则对称商机编号) | +| fromUserId / fromUserName | from_user_id / from_user_name | — | 离职/调动人员快照 | +| toDirectorId / toDirectorName | to_director_id / to_director_name | — | 接收总监快照 | +| reason | reason | varchar(20) | 交接原因字典:员工离职/岗位调动/区域调整 | +| remark | remark | varchar(500) | 备注(选填) | +| totalCount | total_count | int | 本次交接客户总数 | +| assignedCount | assigned_count | int | 已分配数(进度 = assigned/total) | +| status | status | tinyint | 0待分配 / 1已完成 | +| createBy / createTime / updateBy / updateTime | — | — | BaseEntity | + +> 无孤立单大写字母陷阱。 + +#### B2. 交接明细 `customer_transfer_detail` + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint | 主键 | +| transferId | transfer_id | bigint | 交接单 id | +| customerId | customer_id | bigint | 客户 id | +| assignStatus | assign_status | tinyint | 0待分配 / 1已分配 | +| assignUserId / assignUserName | assign_user_id / assign_user_name | — | 分配对象快照(待分配时 NULL) | +| assignTime | assign_time | datetime | 分配时间 | + +- `UNIQUE(transfer_id, customer_id)` 去重。 + +### C. 事务语义(定稿,跨模块) + +1. **发起交接**(整批原子): + - 遍历本次客户,对每个客户:`owner_user_id → to_director_id`;移出 from_user 团队身份;保留原协同人。 + - 对每个客户的**关联商机**(crm-opportunity)`owner_user_id → to_director_id`。 + - **关联项目 owner 同步 = seam**(A5 未建;本图只定义 port 契约,实现时项目侧跳过并标注)。 + - **任一失败 → 全部回滚**,不生成交接单(原型三.确认交接 §7 明文)。 +2. **客户分配**(整批/单个): + - 待分配明细 → 选分配对象(接收总监有权分配范围 + 启用的销售 + 不得为 from_user 本人 + 不得是当前负责人)→ `owner_user_id → assign_user_id`,同步关联商机/项目 owner。 + - 单个失败不整体回滚(批量按逐条独立事务),失败项列原因。 +3. **防重入**:同一 from_user 存在未完成交接单时,禁止再次发起(原型三.确认交接 §1)。 + +### D. 契约(定稿) + +``` +POST /api/customer/transfer/initiate 发起交接(整批原子,返回交接编号) +GET /api/customer/transfer/{id}/preview 发起前客户预览(按原因算可交接客户) +GET /api/customer/transfer/{id} 交接详情 +GET /api/customer/transfer/page 交接记录(交接编号/人员/原因/状态过滤) +POST /api/customer/transfer/{id}/assign 客户分配(单个或批量 customerIds + assignUserId) +GET /api/customer/transfer/{id}/assignable 可分配销售列表(接收总监范围 + 启用) +``` + +### E. ⚠ 挂号:直属销售总监怎么定(需拍板) + +原型要求:离职时系统**自动确定唯一直属销售总监**;调动/区域调整时在**启用的直属销售总监范围**选择。核心是「直属销售总监」的确定口径: + +**我的推荐**:**不加 SysUser 字段**,用**组织架构现有层级**推导——按 from_user 所在部门向上找「部门负责人」(`dept.leader_user_id` 或等价),若该负责人是「销售总监」角色则命中;无/停用/多条则按原型阻断并提示「联系管理员维护组织关系」。 + +**理由**:① 组织架构已有部门负责人概念,不引入新字段;② 直属关系是组织属性而非用户属性,加 `superior_user_id` 会带来维护和一致性问题;③ 原型明说「无/停用/多条直属关系 → 阻断」,天然容忍推导失败。 + +**备选**:`SysUser` 加 `superior_user_id`(直属上级),由后台维护;查询 O(1),但需改 auth 模块 + 维护成本。 + +> 你选哪个?选推荐则票 08 冻结;选备选则回填 crm-auth 加字段。 + +### F. 回填 / 下游 + +- **票 03**:owner 变更(分配=换主)复用 oplog 语义;交割是分配动作的跨模块扩展版。 +- **票 07**:oplog 记录 `TRANSFER` 动作 + `customer_transfer` 表变更;跨模块同步操作人显「系统」+ 交接编号。 +- **crm-opportunity**:需要商机侧提供「按 customer_id 批量改 owner」的 port(或复用商机分配逻辑)。 +- **A5 项目**:项目 owner 同步 seam(port 契约占位)。 +- **crm-dict**:`transfer_reason` 分组(员工离职/岗位调动/区域调整)。 + +### G. 待产品确认(⚠) + +| # | 问题 | 影响 | +|---|---|---| +| P0-1 | 交接单「已完成」的判定:assignedCount == totalCount 自动完成,还是总监手动点「完成交接」?原型进度 0/N → **推荐自动完成**(全部分配完即完成) | status 迁移 | diff --git a/.scratch/customer-module/issues/09-answer.md b/.scratch/customer-module/issues/09-answer.md new file mode 100644 index 0000000..fcdcebe --- /dev/null +++ b/.scratch/customer-module/issues/09-answer.md @@ -0,0 +1,60 @@ +## Answer + +**Status: resolved** + +### A. 范围(定稿) + +- ✅ **做**:客户主数据 + 联系人子表 的 Excel 导入(异步任务 + 行级处理 + 结果明细)。 +- ❌ **不做**:OCR 快捷新增(划出);Excel 内图片/附件转存(V1 忽略对象);通过 Excel 清空已有字段(V1 不支持)。 + +### B. 导入方式(定稿,三选一) + +| 方式 | 匹配到已有客户 | 未匹配 | +|---|---|---| +| 仅新增数据 | 不导入,标「客户已存在」 | 创建新客户 | +| 仅更新数据 | 更新 | 不新增,标「未找到可更新客户」 | +| 新增或更新数据 | 更新 | 创建新客户 | + +- **匹配顺序**(定稿):① 客户编号完全一致 → ② 统一社会信用代码完全一致。 +- 编号与信用代码**均缺失或均未匹配** → 不得仅凭名称自动覆盖;名称相似/相同行列「疑似重复」,本次不写入,由用户去新增/编辑流程人工处理(对称票 04 查重口径)。 +- 编号与信用代码**分别匹配到不同客户** → 该行冲突,禁止导入。 +- 文件内**多行匹配同一客户**:`覆盖导入`/`重复时不导入` 互斥,默认「重复时不导入」;覆盖按文件自上而下、最后一条通过校验的为准;不导入则重复组全部失败。 + +### C. 字段语义(定稿) + +- **新增导入**:必填字段空 → 该行失败;非必填空 → 按空值创建。`customer_stage` 固定=潜在(不得指定)。 +- **更新导入**:空白单元格 = 不修改已有值(V1 不支持清空)。 +- **禁止通过导入修改**:销售负责人/协同人(普通销售导入负责人固定=当前用户;有分配权限者可指定授权范围内负责人,超出 → 该行失败)、客户阶段(更新不得回退)、工商字段/战略协议/关联业务汇总等系统计算字段。 +- **联系人子表**(D26 翻案后校准 20260902):电话存在 → 匹配**本客户**已有联系人→更新;匹配**他客户**联系人→**软提示疑似重复、不禁止导入**(列疑似重复明细,跟随查重设置「联系电话」开关;开关关则不提示),**不做跨客户电话唯一硬拦**;电话空 → 按「客户+姓名+职务」疑似重复,不自动覆盖,列疑似重复明细。同一导入文件内同客户电话相同两行 → 该行失败(表单级)。 +- **原子单元** = 单条客户主记录 / 单条联系人子记录;联系人失败不回滚已成功的客户主记录。 + +### D. 任务模型(定稿,对称商机/线索导入范式) + +- **异步任务**:上传 + 服务端预校验 → 展示预计新增/更新/失败/疑似重复数 → 用户确认 → 创建正式导入任务。 +- **预校验**:文件类型(.xls/.xlsx) / 大小(≤20MB) / 模板版本 / 表头(无空单元格才导) / 单 sheet ≤50000 行 / 不计算公式 / 忽略图片附件对象。 +- **行级处理,允许部分成功**:单行内原子(不半行写入)。 +- **结果页**:任务状态 / 总行数 / 新增成功 / 更新成功 / 未变化 / 失败数 / 提交人 / 提交时间 / 完成时间;失败明细(sheet名/行号/客户识别/失败字段/失败原因)可下载,敏感值按字段权限脱敏。 +- **任务超时/异常 → 标记失败**,不得让用户重复提交状态未知的任务。 +- **操作日志**:记录任务 ID/导入方式/模板版本/文件哈希/成功失败数/触发后续动作选择;**不记录文件名、文件内容、敏感字段原值**。 +- **最近导入记录**入口,仅当前用户有权查看的任务。 + +**契约**: +``` +POST /api/customer/import/upload 上传 + 预校验(返回预计数 + 预校验明细) +POST /api/customer/import/{taskId}/confirm 确认创建正式任务(返回 taskId) +GET /api/customer/import/{taskId} 任务结果 +GET /api/customer/import/{taskId}/failures 失败明细下载 +GET /api/customer/import/page 最近导入记录 +GET /api/customer/import/template 下载导入模板(含模板版本) +``` + +### E. 回填 / 下游 + +- **票 04**:复用 `CustomerDedupService`(信用代码精确查重 + 名称相似判定)做导入匹配。 +- **票 03**:导入成功刷新「我的客户」,仍按当前用户数据范围展示。 +- **票 07**:oplog 记录每次导入(任务级)。 +- **crm-file**:文件上传/哈希复用平台能力(对称商机/线索导入)。 + +### F. 挂号 — 已随 grill 全部销号 + +1. ~~文件内多行匹配同一客户默认值~~ **已拍板(grill Q14)**:默认「重复时不导入」(重复组全部失败)。 diff --git a/.scratch/customer-module/issues/09-import.md b/.scratch/customer-module/issues/09-import.md index 6a88465..0c34b48 100644 --- a/.scratch/customer-module/issues/09-import.md +++ b/.scratch/customer-module/issues/09-import.md @@ -1,7 +1,7 @@ # 09 — 客户导入 + 联系人快速新增(OCR 划出) Type: research -Status: open +Status: resolved Blocked by: 02 ## 问题 @@ -33,4 +33,61 @@ Blocked by: 02 ## Answer -_(待 resolve)_ +**Status: resolved** + +### A. 范围(定稿) + +- ✅ **做**:客户主数据 + 联系人子表 的 Excel 导入(异步任务 + 行级处理 + 结果明细)。 +- ❌ **不做**:OCR 快捷新增(划出);Excel 内图片/附件转存(V1 忽略对象);通过 Excel 清空已有字段(V1 不支持)。 + +### B. 导入方式(定稿,三选一) + +| 方式 | 匹配到已有客户 | 未匹配 | +|---|---|---| +| 仅新增数据 | 不导入,标「客户已存在」 | 创建新客户 | +| 仅更新数据 | 更新 | 不新增,标「未找到可更新客户」 | +| 新增或更新数据 | 更新 | 创建新客户 | + +- **匹配顺序**(定稿):① 客户编号完全一致 → ② 统一社会信用代码完全一致。 +- 编号与信用代码**均缺失或均未匹配** → 不得仅凭名称自动覆盖;名称相似/相同行列「疑似重复」,本次不写入,由用户去新增/编辑流程人工处理(对称票 04 查重口径)。 +- 编号与信用代码**分别匹配到不同客户** → 该行冲突,禁止导入。 +- 文件内**多行匹配同一客户**:`覆盖导入`/`重复时不导入` 互斥,默认「重复时不导入」;覆盖按文件自上而下、最后一条通过校验的为准;不导入则重复组全部失败。 + +### C. 字段语义(定稿) + +- **新增导入**:必填字段空 → 该行失败;非必填空 → 按空值创建。`customer_stage` 固定=潜在(不得指定)。 +- **更新导入**:空白单元格 = 不修改已有值(V1 不支持清空)。 +- **禁止通过导入修改**:销售负责人/协同人(普通销售导入负责人固定=当前用户;有分配权限者可指定授权范围内负责人,超出 → 该行失败)、客户阶段(更新不得回退)、工商字段/战略协议/关联业务汇总等系统计算字段。 +- **联系人子表**:电话存在 → 按电话查重(匹配本客户已有联系人→更新;匹配他客户联系人→冲突禁止转移);电话空 → 按「客户+姓名+职务」疑似重复,不自动覆盖,列失败明细。 +- **原子单元** = 单条客户主记录 / 单条联系人子记录;联系人失败不回滚已成功的客户主记录。 + +### D. 任务模型(定稿,对称商机/线索导入范式) + +- **异步任务**:上传 + 服务端预校验 → 展示预计新增/更新/失败/疑似重复数 → 用户确认 → 创建正式导入任务。 +- **预校验**:文件类型(.xls/.xlsx) / 大小(≤20MB) / 模板版本 / 表头(无空单元格才导) / 单 sheet ≤50000 行 / 不计算公式 / 忽略图片附件对象。 +- **行级处理,允许部分成功**:单行内原子(不半行写入)。 +- **结果页**:任务状态 / 总行数 / 新增成功 / 更新成功 / 未变化 / 失败数 / 提交人 / 提交时间 / 完成时间;失败明细(sheet名/行号/客户识别/失败字段/失败原因)可下载,敏感值按字段权限脱敏。 +- **任务超时/异常 → 标记失败**,不得让用户重复提交状态未知的任务。 +- **操作日志**:记录任务 ID/导入方式/模板版本/文件哈希/成功失败数/触发后续动作选择;**不记录文件名、文件内容、敏感字段原值**。 +- **最近导入记录**入口,仅当前用户有权查看的任务。 + +**契约**: +``` +POST /api/customer/import/upload 上传 + 预校验(返回预计数 + 预校验明细) +POST /api/customer/import/{taskId}/confirm 确认创建正式任务(返回 taskId) +GET /api/customer/import/{taskId} 任务结果 +GET /api/customer/import/{taskId}/failures 失败明细下载 +GET /api/customer/import/page 最近导入记录 +GET /api/customer/import/template 下载导入模板(含模板版本) +``` + +### E. 回填 / 下游 + +- **票 04**:复用 `CustomerDedupService`(信用代码精确查重 + 名称相似判定)做导入匹配。 +- **票 03**:导入成功刷新「我的客户」,仍按当前用户数据范围展示。 +- **票 07**:oplog 记录每次导入(任务级)。 +- **crm-file**:文件上传/哈希复用平台能力(对称商机/线索导入)。 + +### F. ⚠ 挂号(需拍板) + +1. 文件内多行匹配同一客户时,默认「重复时不导入」(原型默认)——确认默认值?**推荐:默认「重复时不导入」**(避免覆盖歧义)。 diff --git a/.scratch/customer-module/issues/10-answer.md b/.scratch/customer-module/issues/10-answer.md new file mode 100644 index 0000000..4812646 --- /dev/null +++ b/.scratch/customer-module/issues/10-answer.md @@ -0,0 +1,116 @@ + +## Answer + +**Status: resolved**(锚点语义 = 成为有主时刻,已由用户在 grill Q15 拍板 A) + +### A. 结论速览 + +客户「超期未跟进提醒」= **crm-rule 全局单例配置 + crm-customer 待发通知 + 调度 Job**,三层对称商机「回收提醒」范式,但配置形态是**单例**(不套 V-CONFIG 版本化,chart D4)。「客户查重设置」不做(chart D6)。 + +| 层 | 落点 | 对称对象 | +|---|---|---| +| 配置 | `crm-rule` `customer_reminder_rule`(单行表,id 固定=1) | `opportunity_pool_rule`(但非版本化) | +| 待发通知 | `crm-customer` `customer_pending_notice` | `opportunity_pending_notice` | +| 调度 | `crm-customer` `CustomerReminderJob`(@Scheduled) | `OpportunityMaintenanceJob` | + +--- + +### B. 配置表 `customer_reminder_rule`(crm-rule) + +**全局单例**:单行表,`id` 固定为 1,种子初始化器 `CustomerReminderRuleInitializer` 植入默认行(对称 `StageTemplateInitializer` 兜底范式)。无版本号、无状态机、无 rule_code。 + +| Java 字段 | 列名 | 类型 | 默认 | 说明 | +|---|---|---|---|---| +| id | id | bigint | 固定 1 | 单例行主键 | +| masterEnabled | master_enabled | tinyint | 1 | 总开关:1启用 0禁用(禁用后 Job 整轮跳过) | +| firstTriggerEnabled | first_trigger_enabled | tinyint | 1 | 首次触发独立开关 | +| firstTriggerDays | first_trigger_days | int | 30 | 首次触发天数(>0) | +| secondIntervalEnabled | second_interval_enabled | tinyint | 1 | 二次提醒独立开关 | +| secondIntervalDays | second_interval_days | int | 7 | 二次提醒间隔天数(>0) | +| updateBy / updateTime | update_by / update_time | — | — | BaseEntity 继承 | + +> ⚠ **孤立单大写字母检查**(AGENTS.md):`masterEnabled`→`master_enabled`、`firstTriggerDays`→`first_trigger_days`、`secondIntervalDays`→`second_interval_days`——均为「完整词 + 完整词」,**无需 `@Column(name=...)`**。 + +**校验规则**(服务层,对称商机规则): +- `firstTriggerDays > 0`、`secondIntervalDays > 0`。 +- 保存 = 全量覆盖(无草稿/发布态,保存即生效)。 +- 只后台管理面板可见(RBAC 功能权限 `customer.reminder.rule`)。 + +**契约**: +``` +GET /api/rule/customer/reminder → 读当前配置(单行) +PUT /api/rule/customer/reminder → 保存配置(全量覆盖,记录操作日志) +``` + +--- + +### C. 触发口径(只前进、确定性提醒链) + +**计时锚点** = `customer.last_valid_follow_time`(票 02 已建)。**锚点 = 成为「有主」的时刻**(grill Q15 拍板 A):① 新建客户直接指派负责人 → 初始化为 `create_time`;② 新建客户进公海(无负责人)→ `last_valid_follow_time = NULL`(无锚点不提醒),被领取/分配时 → 锚点 = 领取时刻;③ 每次写跟进记录刷新锚点(票 07)。 + +**提醒链**(确定性,可从锚点直接推导,无需存「已发首次」状态): + +``` +第 0 次(首次) plan = anchor + first_trigger_days +第 1 次(二次) plan = anchor + first_trigger_days + 1 × second_interval_days +第 k 次(二次) plan = anchor + first_trigger_days + k × second_interval_days +``` + +- 幂等键 = `(customer_id, notice_type, plan_notify_time)`,同计划时刻永不重写(对称商机 `uk_pending_notice_dedupe`)。 +- Job 每轮对每个客户只补**下一条未建的到期提醒**(不批量堆历史)。 +- **跟进刷新锚点** → 旧锚点的未发提醒**置已失效**(对称商机回收时 invalidate 语义);新锚点重新从首次开始计时。 + +**参与范围**(扫表条件): +- `archive_status = 有效(1)`(归档/合并不提醒) +- `owner_user_id IS NOT NULL`(公海无接收人不提醒,chart 票 10 待拍板点 6) +- `last_valid_follow_time IS NOT NULL`(有主客户才有锚点;公海客户领取后赋锚点,天然兜住「公海不计时」) +- `master_enabled = 1` 且(`first_trigger_enabled=1` 或 `second_interval_enabled=1`) + +--- + +### D. 待发通知表 `customer_pending_notice`(crm-customer) + +对称 `OpportunityPendingNotice`,字段集一致: + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint | ASSIGN_ID 主键 | +| customerId | customer_id | bigint | 客户 ID | +| noticeType | notice_type | varchar(32) | `OVERDUE_REMIND`(本期唯一类型) | +| targetUserId | target_user_id | bigint | 应通知对象 = 客户销售负责人(快照) | +| planNotifyTime | plan_notify_time | datetime | 应发提醒时刻(= 提醒链推导时刻) | +| payloadJson | payload_json | text | 上下文 JSON(见下) | +| noticeStatus | notice_status | tinyint | 0待发 1已发 2已失效 | + +- **唯一约束**:`UNIQUE(customer_id, notice_type, plan_notify_time)`。 +- **索引**:`idx(customer_id, notice_status)` 供消费者扫待发;`idx(notice_status, plan_notify_time)` 供补发调度。 +- **payload JSON**(对称商机无「剩余天数」伪状态):`{ remindSeq(0=首次/k=第k次二次), overdueDays(已无跟进天数), anchor(last_valid_follow_time), firstTriggerDays, secondIntervalDays }`。 + +> 本期**只落待发记录,不直接发送**;平台通知能力(钉钉 + 应用内通知)spin-out,就绪后由另一消费者扫 `notice_status=待发` 补发(对称商机票 05 拍板)。 + +--- + +### E. 调度 Job `CustomerReminderJob`(crm-customer) + +- **cron**:`${crm.customer.reminder-cron:0 0 4 * * ?}`(默认凌晨 4 点,错开线索 2 点 / 商机 3 点)。 +- **流程**(对称 `OpportunityMaintenanceJob`,但无回收分支): + 1. 读 `customer_reminder_rule` 单行;`master_enabled=0` 或无配置 → 整轮跳过。 + 2. 扫参与范围客户(见 C)。 + 3. 对每个客户:算**下一条未建的到期提醒**(`plan ≤ now`),若有则落 `customer_pending_notice`(幂等:已存在同 plan 时刻跳过)。 + 4. 锚点已被刷新的客户:把旧锚点未发的 OVERDUE_REMIND 置已失效。 +- **不发送**:只落待发记录(E 末尾同)。 + +--- + +### F. 规则生效时机(chart Q4-补 用户强调,已确认) + +> 用户原话「规则在客户新增的时候生效」——最终解读:**新建客户时把 `last_valid_follow_time` 初始化为 `create_time`**,客户创建后即开始计时,30 天(首次触发默认值)无跟进 → 首次提醒。**回填票 02**(已落 `last_valid_follow_time` 字段)+ **票 07**(新建跟进时刷新)。 + +--- + +### G. 回填 / 下游影响 + +- **票 02**:`last_valid_follow_time` 已含(✅ 已落)。 +- **票 07**:跟进记录写入时刷新 `last_valid_follow_time`。 +- **票 01**:CONTEXT.md 增术语「超期未跟进提醒」。 +- **crm-rule/CONTEXT.md**:增客户规则族段(对称商机规则段)。 diff --git a/.scratch/customer-module/issues/10-overdue-reminder-rule.md b/.scratch/customer-module/issues/10-overdue-reminder-rule.md index 2a8522f..4ff41d9 100644 --- a/.scratch/customer-module/issues/10-overdue-reminder-rule.md +++ b/.scratch/customer-module/issues/10-overdue-reminder-rule.md @@ -1,7 +1,7 @@ # 10 — 客户规则族「超期未跟进提醒」(crm-rule 全局单例) Type: research -Status: open +Status: resolved Blocked by: ## 问题 @@ -39,4 +39,116 @@ Blocked by: ## Answer -_(待 resolve)_ +**Status: resolved** + +### A. 结论速览 + +客户「超期未跟进提醒」= **crm-rule 全局单例配置 + crm-customer 待发通知 + 调度 Job**,三层对称商机「回收提醒」范式,但配置形态是**单例**(不套 V-CONFIG 版本化,chart D4)。「客户查重设置」不做(chart D6)。 + +| 层 | 落点 | 对称对象 | +|---|---|---| +| 配置 | `crm-rule` `customer_reminder_rule`(单行表,id 固定=1) | `opportunity_pool_rule`(但非版本化) | +| 待发通知 | `crm-customer` `customer_pending_notice` | `opportunity_pending_notice` | +| 调度 | `crm-customer` `CustomerReminderJob`(@Scheduled) | `OpportunityMaintenanceJob` | + +--- + +### B. 配置表 `customer_reminder_rule`(crm-rule) + +**全局单例**:单行表,`id` 固定为 1,种子初始化器 `CustomerReminderRuleInitializer` 植入默认行(对称 `StageTemplateInitializer` 兜底范式)。无版本号、无状态机、无 rule_code。 + +| Java 字段 | 列名 | 类型 | 默认 | 说明 | +|---|---|---|---|---| +| id | id | bigint | 固定 1 | 单例行主键 | +| masterEnabled | master_enabled | tinyint | 1 | 总开关:1启用 0禁用(禁用后 Job 整轮跳过) | +| firstTriggerEnabled | first_trigger_enabled | tinyint | 1 | 首次触发独立开关 | +| firstTriggerDays | first_trigger_days | int | 30 | 首次触发天数(>0) | +| secondIntervalEnabled | second_interval_enabled | tinyint | 1 | 二次提醒独立开关 | +| secondIntervalDays | second_interval_days | int | 7 | 二次提醒间隔天数(>0) | +| updateBy / updateTime | update_by / update_time | — | — | BaseEntity 继承 | + +> ⚠ **孤立单大写字母检查**(AGENTS.md):`masterEnabled`→`master_enabled`、`firstTriggerDays`→`first_trigger_days`、`secondIntervalDays`→`second_interval_days`——均为「完整词 + 完整词」,**无需 `@Column(name=...)`**。 + +**校验规则**(服务层,对称商机规则): +- `firstTriggerDays > 0`、`secondIntervalDays > 0`。 +- 保存 = 全量覆盖(无草稿/发布态,保存即生效)。 +- 只后台管理面板可见(RBAC 功能权限 `customer.reminder.rule`)。 + +**契约**: +``` +GET /api/rule/customer/reminder → 读当前配置(单行) +PUT /api/rule/customer/reminder → 保存配置(全量覆盖,记录操作日志) +``` + +--- + +### C. 触发口径(只前进、确定性提醒链) + +**计时锚点** = `customer.last_valid_follow_time`(票 02 已建;新建客户时初始化为 `create_time`,每次写跟进记录刷新——票 07)。 + +**提醒链**(确定性,可从锚点直接推导,无需存「已发首次」状态): + +``` +第 0 次(首次) plan = anchor + first_trigger_days +第 1 次(二次) plan = anchor + first_trigger_days + 1 × second_interval_days +第 k 次(二次) plan = anchor + first_trigger_days + k × second_interval_days +``` + +- 幂等键 = `(customer_id, notice_type, plan_notify_time)`,同计划时刻永不重写(对称商机 `uk_pending_notice_dedupe`)。 +- Job 每轮对每个客户只补**下一条未建的到期提醒**(不批量堆历史)。 +- **跟进刷新锚点** → 旧锚点的未发提醒**置已失效**(对称商机回收时 invalidate 语义);新锚点重新从首次开始计时。 + +**参与范围**(扫表条件): +- `archive_status = 有效(1)`(归档/合并不提醒) +- `owner_user_id IS NOT NULL`(公海无接收人不提醒,chart 票 10 待拍板点 6) +- `last_valid_follow_time IS NOT NULL`(防御脏数据) +- `master_enabled = 1` 且(`first_trigger_enabled=1` 或 `second_interval_enabled=1`) + +--- + +### D. 待发通知表 `customer_pending_notice`(crm-customer) + +对称 `OpportunityPendingNotice`,字段集一致: + +| Java 字段 | 列名 | 类型 | 说明 | +|---|---|---|---| +| id | id | bigint | ASSIGN_ID 主键 | +| customerId | customer_id | bigint | 客户 ID | +| noticeType | notice_type | varchar(32) | `OVERDUE_REMIND`(本期唯一类型) | +| targetUserId | target_user_id | bigint | 应通知对象 = 客户销售负责人(快照) | +| planNotifyTime | plan_notify_time | datetime | 应发提醒时刻(= 提醒链推导时刻) | +| payloadJson | payload_json | text | 上下文 JSON(见下) | +| noticeStatus | notice_status | tinyint | 0待发 1已发 2已失效 | + +- **唯一约束**:`UNIQUE(customer_id, notice_type, plan_notify_time)`。 +- **索引**:`idx(customer_id, notice_status)` 供消费者扫待发;`idx(notice_status, plan_notify_time)` 供补发调度。 +- **payload JSON**(对称商机无「剩余天数」伪状态):`{ remindSeq(0=首次/k=第k次二次), overdueDays(已无跟进天数), anchor(last_valid_follow_time), firstTriggerDays, secondIntervalDays }`。 + +> 本期**只落待发记录,不直接发送**;平台通知能力(钉钉 + 应用内通知)spin-out,就绪后由另一消费者扫 `notice_status=待发` 补发(对称商机票 05 拍板)。 + +--- + +### E. 调度 Job `CustomerReminderJob`(crm-customer) + +- **cron**:`${crm.customer.reminder-cron:0 0 4 * * ?}`(默认凌晨 4 点,错开线索 2 点 / 商机 3 点)。 +- **流程**(对称 `OpportunityMaintenanceJob`,但无回收分支): + 1. 读 `customer_reminder_rule` 单行;`master_enabled=0` 或无配置 → 整轮跳过。 + 2. 扫参与范围客户(见 C)。 + 3. 对每个客户:算**下一条未建的到期提醒**(`plan ≤ now`),若有则落 `customer_pending_notice`(幂等:已存在同 plan 时刻跳过)。 + 4. 锚点已被刷新的客户:把旧锚点未发的 OVERDUE_REMIND 置已失效。 +- **不发送**:只落待发记录(E 末尾同)。 + +--- + +### F. 规则生效时机(chart Q4-补 用户强调,已确认) + +> 用户原话「规则在客户新增的时候生效」——最终解读:**新建客户时把 `last_valid_follow_time` 初始化为 `create_time`**,客户创建后即开始计时,30 天(首次触发默认值)无跟进 → 首次提醒。**回填票 02**(已落 `last_valid_follow_time` 字段)+ **票 07**(新建跟进时刷新)。 + +--- + +### G. 回填 / 下游影响 + +- **票 02**:`last_valid_follow_time` 已含(✅ 已落)。 +- **票 07**:跟进记录写入时刷新 `last_valid_follow_time`。 +- **票 01**:CONTEXT.md 增术语「超期未跟进提醒」。 +- **crm-rule/CONTEXT.md**:增客户规则族段(对称商机规则段)。 diff --git a/.scratch/customer-module/issues/11-answer.md b/.scratch/customer-module/issues/11-answer.md new file mode 100644 index 0000000..e5477b5 --- /dev/null +++ b/.scratch/customer-module/issues/11-answer.md @@ -0,0 +1,53 @@ +## Answer + +**Status: resolved** + +### A. 接线结论(定稿) + +复用 crm-preference `user_saved_view` 平台能力,**不重写**。rule of three:商机是样本 1,客户是样本 2;样本 3 出现前不抽共享翻译器(各自 `XxxSavedViewFilter`)。 + +### B. scope_key(定稿,3 个) + +| workspace | scope_key | +|---|---| +| 客户总览 | `customer.overview` | +| 我的客户 | `customer.mine` | +| 客户公海 | `customer.pool` | + +- **公海双视角(普通销售 / 领导)共用 `customer.pool`**:字段池相同,视角只影响行内操作(领取 vs 分配),不影响筛选条件。 + +### C. `CustomerSavedViewFilter`(定稿) + +- filter_json → MyBatis-Plus wrapper 翻译器,对称 `OpportunitySavedViewFilter`。 +- **字段池白名单**(票 03 定的可筛选列): + +| 字段 | 支持操作符 | +|---|---| +| 客户名称 / 客户编号 | 包含 / 不包含 | +| 客户类型 / 行业 / 客户阶段 / 客户星级 / 关系星级 / 战略协议等级 / 已成交VIP等级 / 档案状态 / 地区 | 属于任一 / 不属于任一 | +| 最近跟进时间 | 早于 / 晚于 / 从未跟进 / 已有跟进 | +| 进入公海时间(仅 pool) | 早于 / 晚于 | +| 销售负责人 / 销售部门(仅 overview / mine) | 属于任一 / 不属于任一 | +| 创建时间 | 早于 / 晚于 | + +- **未知字段 / 未知操作符 → 跳过**(不报错,对称商机)。 +- **排序字段池**:创建时间 / 最近跟进时间 / 进入公海时间(默认);客户名称等文本字段**不排**(参商机默认口径)。 + +### D. PageParam 接入(定稿) + +- 三 workspace 的 PageParam(`CustomerOverviewPageParam` / `CustomerMinePageParam` / `CustomerPoolPageParam`)均加 `savedViewId`。 +- ViewQueryImpl:有 `savedViewId` → 先调 `SavedViewService.get(viewId, scope_key)` 取 filter → 叠加 wrapper;无 → 不叠加。 +- 前端进入 workspace:先 `GET /api/preference/view/list?scope_key=...` 取 `isDefault` 视图 → 带 `viewId` 调 `/page`(对称商机)。 + +### E. 验收清单(spec 级,不写代码) + +1. scope_key 唯一性:`customer.overview` / `customer.mine` / `customer.pool` 三值互斥,与商机 `opportunity.*` 无碰撞。 +2. filter 白名单:白名单内字段 + 合法操作符生效;白名单外字段/操作符静默跳过。 +3. 默认视图互斥:每 scope 仅一个 `isDefault`(crm-preference 已实现,验证客户三 scope 复用生效)。 +4. 端到端:保存自定义检索 → 列表 → 再次进入带 viewId 恢复条件。 +5. 数据范围不越权:saved-view 只叠加筛选,**不改变** @DataScope 数据范围。 + +### F. 回填 / 下游 + +- **票 03**:字段池白名单来源(票 03 D 节共用列 + 差异列)。 +- **crm-preference**:无改动(纯消费方)。 diff --git a/.scratch/customer-module/issues/11-saved-view-integration.md b/.scratch/customer-module/issues/11-saved-view-integration.md index c60fc62..8fa6695 100644 --- a/.scratch/customer-module/issues/11-saved-view-integration.md +++ b/.scratch/customer-module/issues/11-saved-view-integration.md @@ -1,7 +1,7 @@ # 11 — 自定义视图接入 crm-preference(复用商机 saved-view 平台能力) Type: task -Status: open +Status: resolved Blocked by: 03 ## 目标 @@ -34,4 +34,54 @@ Blocked by: 03 ## Answer -_(待 resolve)_ +**Status: resolved** + +### A. 接线结论(定稿) + +复用 crm-preference `user_saved_view` 平台能力,**不重写**。rule of three:商机是样本 1,客户是样本 2;样本 3 出现前不抽共享翻译器(各自 `XxxSavedViewFilter`)。 + +### B. scope_key(定稿,3 个) + +| workspace | scope_key | +|---|---| +| 客户总览 | `customer.overview` | +| 我的客户 | `customer.mine` | +| 客户公海 | `customer.pool` | + +- **公海双视角(普通销售 / 领导)共用 `customer.pool`**:字段池相同,视角只影响行内操作(领取 vs 分配),不影响筛选条件。 + +### C. `CustomerSavedViewFilter`(定稿) + +- filter_json → MyBatis-Plus wrapper 翻译器,对称 `OpportunitySavedViewFilter`。 +- **字段池白名单**(票 03 定的可筛选列): + +| 字段 | 支持操作符 | +|---|---| +| 客户名称 / 客户编号 | 包含 / 不包含 | +| 客户类型 / 行业 / 客户阶段 / 客户星级 / 关系星级 / 战略协议等级 / 已成交VIP等级 / 档案状态 / 地区 | 属于任一 / 不属于任一 | +| 最近跟进时间 | 早于 / 晚于 / 从未跟进 / 已有跟进 | +| 进入公海时间(仅 pool) | 早于 / 晚于 | +| 销售负责人 / 销售部门(仅 overview / mine) | 属于任一 / 不属于任一 | +| 创建时间 | 早于 / 晚于 | + +- **未知字段 / 未知操作符 → 跳过**(不报错,对称商机)。 +- **排序字段池**:创建时间 / 最近跟进时间 / 进入公海时间(默认);客户名称等文本字段**不排**(参商机默认口径)。 + +### D. PageParam 接入(定稿) + +- 三 workspace 的 PageParam(`CustomerOverviewPageParam` / `CustomerMinePageParam` / `CustomerPoolPageParam`)均加 `savedViewId`。 +- ViewQueryImpl:有 `savedViewId` → 先调 `SavedViewService.get(viewId, scope_key)` 取 filter → 叠加 wrapper;无 → 不叠加。 +- 前端进入 workspace:先 `GET /api/preference/view/list?scope_key=...` 取 `isDefault` 视图 → 带 `viewId` 调 `/page`(对称商机)。 + +### E. 验收清单(spec 级,不写代码) + +1. scope_key 唯一性:`customer.overview` / `customer.mine` / `customer.pool` 三值互斥,与商机 `opportunity.*` 无碰撞。 +2. filter 白名单:白名单内字段 + 合法操作符生效;白名单外字段/操作符静默跳过。 +3. 默认视图互斥:每 scope 仅一个 `isDefault`(crm-preference 已实现,验证客户三 scope 复用生效)。 +4. 端到端:保存自定义检索 → 列表 → 再次进入带 viewId 恢复条件。 +5. 数据范围不越权:saved-view 只叠加筛选,**不改变** @DataScope 数据范围。 + +### F. 回填 / 下游 + +- **票 03**:字段池白名单来源(票 03 D 节共用列 + 差异列)。 +- **crm-preference**:无改动(纯消费方)。 diff --git a/.scratch/customer-module/map.md b/.scratch/customer-module/map.md index 0a7f78a..2b05b04 100644 --- a/.scratch/customer-module/map.md +++ b/.scratch/customer-module/map.md @@ -9,6 +9,8 @@ - **`crm-customer`(新建模块)** — 客户业务域:客户主表 + 归属流转(有主/公海 + 交割换主)+ 客户阶段(潜在→重潜→已成交,只前进)+ 三 workspace(客户总览 / 我的客户 / 客户公海)+ 内置视图 + 自定义视图(复用 crm-preference)+ 新增/编辑(含查重校验 + 企查查工商回填 seam)+ 客户导入 + 客户详情 8 页签 + 跟进记录 + 操作日志 + 客户交割(跨模块同步商机/项目负责人)+ 联系人 CRUD + 全局搜索。 - **`crm-rule`(客户规则族,新增第三族)** — 后台"业务规则"下线索/商机/客户三族并列。客户规则本图**只做「超期未跟进提醒」**(全局单例配置,非版本化)。 +> ⚠️ **待复核**:本 spec 已由用户在 grill Q1–Q19 拍板冻结,但用户要求 “换一个新会话独立复核”。**请先读 `REVIEW-HANDOFF.md`**(含我最担心的 9 个薄弱点 + 5 个我可能漏问的点),再逐条挑战 D1–D29,不要因为 map 里写了“已拍板”就跳过。 + ### 原型出处(蓝湖缓存,已本地提取) - 主文档 `bade4454-52aa-44db-8ba2-dec594732ecb`(`【原型】itc信息化业务中台web端 V1.0`)。 @@ -28,41 +30,94 @@ - **③ 自成子域/复杂特性 → 划出本图**(Not-yet / Out-of-scope 记一笔):战略协议(A4-4-9/10)、联系人图谱(职务二级联动+人脉连线)、**客户查重列表+客户合并(A4-6,grill Q4 拍板不做)**、企查查/OCR 外部服务(只定契约 seam,不深挖服务内部;OCR 快捷新增划出)。 - **[D3] 客户双轴建模(chart grill Q3)** — 对称商机双轴但两轴都更简单: - - **轴1 归属**:**不做显式 tinyint 状态机**,改用两个正交维度:① `owner_user_id`+`owner_dept_id`(快照) 是否为空 = 有主/在公海(公海=owner 空的逻辑视图,对称商机公海);② `archive_status`(有效1/已归档2/已被合并3) 独立字段,归档/合并是它的迁移。迁移动作 = 领取/分配/抛公海/交割(换主)/归档。 - - **轴2 阶段**:主表 `customer_stage`(潜在1/重潜2/已成交3) 单字段,**只增不减、不可配置**(硬编码三态,非商机版本化模板),由外部成功结果驱动。本图**只做潜在→重潜**(重潜由方案卡报备驱动,crm-opportunity 已建);**已成交留 seam** 给 A5。 + - **轴1 归属**:**不做显式 tinyint 状态机**,改用两个正交维度:① `owner_user_id`+`owner_dept_id`(快照) 是否为空 = 有主/在公海(公海=owner 空的逻辑视图,对称商机公海);② `archive_status`(有效1/已归档2) 独立字段,**已归档=删除语义**(软删,用列表筛选条件承载,默认排除已归档,加筛选可查)。迁移动作 = 领取/分配/抛公海/交割(换主)/归档。 + - **轴2 阶段**:主表 `customer_stage`(潜在1/重潜2/已成交3) 单字段,**只增不减、不可配置**(硬编码三态,非商机版本化模板),由外部成功结果驱动。本图定义完整三态字段与只前进 CAS,但**本期两个跃迁均 seam 化不自动触发**:潜在→重潜 由「方案卡提交并报备成功」驱动,报备成功链路本期不可达(D20 改票 B,留 seam);已成交由 A5 项目中标驱动,A5 未建(留 seam)。**本期客户阶段实际只有「潜在」**。 - **[D4] 客户规则落 crm-rule + 全局单例形态(chart grill Q4)** — 客户规则族作 crm-rule 第三族(线索/商机/客户并列)。本图**只做「超期未跟进提醒」**(全局单例配置:总开关 + 首次触发天数(默认30) + 二次提醒间隔(默认7);定时任务 + 钉钉/应用内通知),**不套 V-CONFIG 版本化**(区别于商机规则)。**「客户查重设置」不做**(A4-6 查重执行侧不做,查重口径写死在新增流程)。 +- **[D4-rev1] 客户规则族范围修正(grill Q6,用户拍板)** — 客户规则族只含**两族**:`超期未跟进提醒`(票 10)+ `客户查重设置`(票 04 从「写死」改为**后台可配置**,全局单例形态待 Q7)。**星级设置、客户阶段设置不在客户规则族** → 星级退回硬编码枚举 1~5(票 02 原判),客户阶段退回硬编码三态只前进(票 05 原判)。a7-2-3 里那两页(星级/阶段设置)属于其他族/其他 effort,本图不消费。 + +- **[D4-rev2] 客户查重设置的消费范围(grill Q7,用户拍板 B)** — 客户查重设置做成 crm-rule **全局单例可配置**,本期**只消费于「新增/编辑的内建查重」**(票 04:信用代码硬拦 + 名称相似弹窗改用配置里的匹配方式+相似度 70/80/90%,替换「写死」);**A4-6 查重页留作未来消费者**,本期不做扫描全库/合并。规则字段:总开关、规则组合(任意命中/全部命中)、客户名称(精确/模糊+相似度)、统一社会信用代码(精确)、联系电话(精确)。 + - **[D5] 联系人建模(chart grill Q5)** — **联系人 = `crm-customer` 下独立实体表 `customer_contact`**(不单开 crm-contact 模块);每个联系人 `customer_id` 必属一客户(无孤立),权限继承所属客户(联系人无独立 ACL)。**本图做**:联系人 CRUD(列表/详情/新增/编辑)+ 随客户新增内嵌明细 + 全局搜索接口(商机硬依赖)+ 联系人电话查重。**本图不做**:职务二级联动图谱、人脉关系连线、OCR 快捷新增、联系人导入。职务字段先存「职务名称文本 + 可空 标准职务id/类别/层级」三列占位,不实现联动选择器逻辑。 - **[D6] 新增客户查重口径(chart grill Q4-补)** — 新增/编辑客户的**查重校验做**、内建新增流程:① 统一社会信用代码全局唯一(硬拦),② 客户名称相似→保存时确认弹窗(可"仍要创建")。查重阈值/口径**写死**,不做后台可配。A4-6 独立查重页 + 客户合并**不做**。 +- **[D7] 客户主表字段清单已定稿(票 02 resolved)** — 建 `crm-customer` `customer` 主表,字段分八区:系统字段(BaseEntity)/系统生成(`customer_no`)/基本信息(名称·类型·省市区三级·行业两级·星级×2·人脉关系单位JSON)/归属(owner_user_id+owner_dept_id快照·名快照)/轴2阶段(`customer_stage`)+档案状态(`archive_status`)/快照冗余(`last_valid_follow_time`+战略协议等级+VIP等级)/关联信息(父子客户·总裁班·合作系统)/工商信息(企查查9项+`unified_credit_code` UNIQUE)/补充资料(remark+附件子表)。**无孤立单大写字母陷阱,全表无需 `@Column(name=...)`**。`@DataScope(module="customer", ownerColumn="owner_user_id", deptColumn="owner_dept_id")`。索引:uk_credit_code/idx_owner_stage/idx_dept_stage/idx_name/idx_parent/idx_last_follow。**✅ 已定**:`customer_type` = crm-dict 字典分组 `customer_type`,种子 18 值(见 D18);行业选二级(与商机图D1一致);星级用枚举1~5不走字典(硬编码)。详见 `issues/02`。 + +- **[D8] 客户超期未跟进提醒已定稿(票 10 resolved)** — 三层对称商机「回收提醒」范式但配置形态单例:①`crm-rule` `customer_reminder_rule` 单行表(id固定=1,全局单例,非版本化)存总开关+首次触发天数(默认30)+二次提醒间隔(默认7)+各自独立开关;②`crm-customer` `customer_pending_notice` 待发通知表(UNIQUE(customer_id,notice_type,plan_notify_time)幂等);③`CustomerReminderJob`(@Scheduled cron 默认凌晨4点)只落待发不直接发(平台通知spin-out)。**提醒链确定性**:plan = anchor + firstTriggerDays + k×secondIntervalDays。锚点=`last_valid_follow_time`(**锚点 = 成为「有主」的时刻**:新建即指派=create_time;公海客户被领取=领取时刻;每次跟进刷新)。公海(owner空)/已归档不参与(合并态已按 D19 取消)——公海 `last_valid_follow_time=NULL` 不计时不提醒,领取时赋锚点。**「规则在客户新增时生效」= 新建即指派时初始化 last_valid_follow_time=create_time**。详见 `issues/10`、`issues/02`、`issues/07`。 +- **[D18] 客户类型字典已定稿(grill Q8,用户拍板)** — `customer_type` 落 **crm-dict 字典分组 `customer_type`**(非硬编码枚举),种子 18 值:运营商、土建总包、机电总包、装修总包、教育局、投资(代建)公司、设计院、集成商、集成商(子客户)、咨询造价公司、甲方(企业用户)、甲方(普教)、甲方(中高职)、甲方(政府机关)、竞争同行、个人用户、操手、招标代理。注意含 4 个「甲方(…)」值 + 14 个非甲方角色值,证明客户类型 ≠ 仅甲方,是通用角色字典。主表 `customer_type` 存字典值编码(冗余名快照可选),列表/详情按字典名展示。 + +- **[D19] 归档=删除语义 + 去掉「已被合并」(grill Q9,用户拍板)** — 客户不再有「已被合并」态(合并本就在 Out-of-scope,A4-6 不做,状态值随之取消)。`archive_status` 收敛为 `有效1 / 已归档2` 两值;**已归档即删除**(软删),不做独立 tab/子页,只在列表加一个 `archive_status` 筛选条件:**默认排除已归档**,勾选后查询已归档。总览/我的客户主列表默认只出 `archive_status=1`。票 10 提醒排除条件同步收敛为「公海 + 已归档」。**§5.3 拍板 A(20260902,一期只做归档)**:**不提供任何前端「删除客户」入口**——用户侧只有归档(软删可恢复)+ 合并(源客户系统软删,且合并本体 Out-of-scope);`deleted` 标志仅系统在合并时置 1,不开放手动删除、无前端删除按钮,DB 不做物理删除。详见 `issues/03`。 + +- **[D20] 重潜触发本期不实现、只留 seam(grill Q10 拍板 A → 第三轮 fresh 复核翻案,20260902 改票 B)** — **推翻「已提交(2)触发」的临时口径**。最新原型 `a4-4-1`(8-26)明文「任一方案卡**提交并报备成功**后 → 重潜客户」,是**两个条件**(提交 + 报备成功);而「报备/推送成功」依赖推送链路(spin-out 本期不可达)。据此本期决定:**不自动触发潜在→重潜**,客户阶段实际只有「潜在」;`CustomerStagePort.promote` 的 seam **保留**(契约、CAS 只前进、幂等 全部保留),等报备成功链路(商机方案卡「已推送(3)」或 A5 回调)就绪后再由该链路调用 promote 触发重潜。**不用「已提交」凑合**——避免出现「提交了但报备没成功、客户却已重潜」的错误状态。customer_stage 字段/枚举/三态 CAS 机制不变(D11)。详见 `issues/05`。 + +- **[D21] 相似判定算法 seam 化(grill Q11,用户拍板)** — 客户名称相似度判定**抽成接口/端口**(`CustomerNameSimilarityPort` 或 `CustomerDedupService` 内可替换策略),一期只做**最简实现**,后续迭代可换算法。一期最简实现 = **MySQL ngram 分词器(token_size=2)+ FULLTEXT `MATCH ... AGAINST` 取候选 + 相似度计算**,全在 MySQL 内完成、不引外部服务;阈值从「客户查重设置」配置读取(70/80/90%)。接口契约稳定,算法实现可插拔,后续可换 Levenshtein / Jaccard / 外部相似度服务而不改调用方。详见 `issues/04`。 + +- **[D22] 直属销售总监 = 组织架构推导,不改 crm-auth(grill Q12,用户拍板 A)** — **分两种触发场景**:`transfer_reason=离职` → 系统**自动确定唯一**接收总监(按 from_user 所在部门向上找「部门负责人」`dept.leader_user_id` 或等价,该负责人是「销售总监」角色则命中;无/停用/多条直属关系 → 阻断并提示「联系管理员维护组织关系」);`transfer_reason=岗位调动/区域调整` → **在本人启用的直属销售总监范围内自行搜索选择**(下拉选,**不阻断**)。**不在 SysUser 加 `superior_user_id` 字段**(直属关系是组织属性非用户属性,加字段带来维护/一致性成本)。详见 `issues/08`。 + +- **[D23] 交接单完成判定 = 自动完成(grill Q13,用户拍板 A)** — 交接单状态「已完成」由自定义判定:`assignedCount == totalCount`(全部待分配明细已分配)时自动置为已完成,**不设总监手动「完成交接」按钮**。每行明细独立分配,最后一行分配完自然触发终态;不引入二次确认状态机。详见 `issues/08`。 + +- **[D24] 文件内多行匹配同一客户默认「重复时不导入」(grill Q14,用户拍板 A)** — 导入时文件内多行匹配同一客户,`覆盖导入`/`重复时不导入` 互斥,**默认「重复时不导入」**:重复组全部标失败不写入,用户看失败明细处理。覆盖模式(按文件自上而下、最后一条通过校验的为准)作为可选项存在但不默认。详见 `issues/09`。 + +- **[D25] 超期提醒锚点 = 成为「有主」的时刻(grill Q15,用户拍板 A)** — 修正 D8 锚点语义:`last_valid_follow_time` 锚点在**客户成为有主时**赋初值,**公海不计时不提醒**。① 新建客户**直接指派负责人** → 锚点 = `create_time`(有人负责从建卡起算);② 新建客户**进公海**(无负责人)→ `last_valid_follow_time = NULL`(无锚点,不提醒),**被领取/分配时 → 锚点 = 领取时刻**,从此重计首次提醒天数。`last_valid_follow_time IS NULL` + `owner_user_id IS NOT NULL` 双保险兜住公海。同步影响:票 02(字段初始化语义)、票 07(领取时赋锚点 + 跟进刷新)、票 03(领取/分配动作后置锚点)。详见 `issues/10`。 + +- **[D26] 联系人电话查重 = 可配置软提示(grill Q16 用户拍板 B → 第三轮 fresh 复核翻案,20260902 改票 A)** — **推翻「电话全局唯一索引」硬约束**。最新原型 `a7-3-3-2 客户查重设置`(8-26)明示:**统一社会信用代码**是系统强制规则(重复禁止创建、无关闭开关);**联系电话**仅作「提示疑似重复」,**可被总开关单独关闭**,不禁止创建。据此:① DB **不建** `uk_contact_phone(phone)` 唯一索引;② 电话查重=**可配置软提示**,跟随 crm-rule 客户查重设置单例的「联系电话」开关(默认开);③ 仅保留**表单级校验**——同一客户内两个联系人电话不得重复(来自票 04 B4 内联查重);④ 跨客户电话撞号只提示、不阻断。修正 D12「电话全局唯一」旧口径为「电话查重提示」。详见 `issues/06`。 + +- **[D27] 联系人来源字典 `contact_source` 种子 = 原型 4 项(grill Q17,用户拍板 A)** — crm-dict 字典分组 `contact_source` 种子值:**组会、组局、转介绍、其他**。加值后续在字典补,本期不做额外枚举。详见 `issues/06`。 + +- **[D28] 汇总卡片占位一律渲染 + 显"--"(grill Q18,用户拍板 A)** — 客户详情 4 张汇总卡片(含 A5 预留的「合同总额·累计回款」)**一律渲染卡片**,无数据源/接口失败时值显 `--`(不显 0,不留白),保持布局稳定;A5 建好后自动接数据。与其他外部模块卡片失败显"--"口径统一。详见 `issues/07`。 + +- **[D29] 公海列表「进入公海时间」列保留 + 可排序(grill Q19,用户拍板 A)** — 公海列表 `enter_pool_time` 列保留作固定列,且加入票 11 排序字段池(支持升/降序;降序=先进先看)。复用 saved-view 排序机制,成本极低。详见 `issues/03`。 + +- **[D9] 客户三视图/公海/总览已定稿(票 03 resolved)** — 三个 workspace(公海/总览/我的客户)= 逻辑视图,靠 `@DataScope` + `owner_user_id IS NULL` + `archive_status` 过滤区分,不建独立表。内置视图/常用检索硬编码不入库;三视图模式(列表/卡片/看板),看板分组 = customer_stage / customer_star / relation_star。归属迁移动作枚举(领取/分配/抛公海/交割换主/归档)+ oplog + `enter_pool_time`。已归档走 `archive_status` 筛选(D19),无「已被合并」tab;领取/分配动作后置 `last_valid_follow_time` 锚点(D25)。详见 `issues/03`。 + +- **[D10] 新增/查重/企查查已定稿(票 04 resolved)** — 分层查重(输入相似提示 / 提交精确 / 落库前终检),核心服务 `CustomerDedupService`;信用代码全局唯一硬拦,名称相似弹窗确认。查重口径从「客户查重设置」配置读(D4-rev2)。相似判定算法 seam 化,一期最简 = MySQL ngram FULLTEXT(D21)。企查查 = 外部服务契约 seam(port 占位 + 回填),不深挖服务内部。详见 `issues/04`。 + +- **[D11] 客户阶段只前进已定稿(票 05 resolved)** — `customer_stage` 潜在1/重潜2/已成交3,硬编码三态、只增不减;迁移用 `UPDATE ... WHERE customer_stage < ?` CAS 只前进。重潜触发本期不实现、只留 `CustomerStagePort.promote` seam(D20 改票 B:等「方案卡提交并报备成功」链路就绪再调用;不用「已提交」凑合);seam 契约/CAS/幂等保留。已成交由 A5 项目中标驱动,留 seam。详见 `issues/05`。 + +- **[D12] 联系人 CRUD 已定稿(票 06 resolved)** — `customer_contact`(crm-customer 独立表,权限继承客户,无独立 ACL);契约含 CRUD + 全局搜索(商机硬依赖 promote)+ 随客户新增内嵌明细。电话查重=可配置软提示、不建唯一索引(D26);同客户内电话不重复为表单级校验;`contact_source` 字典种子 = 组会/组局/转介绍/其他(D27)。职务字段三列占位不做联动。详见 `issues/06`。 + +- **[D13] 客户详情/跟进/操作日志已定稿(票 07 resolved)** — 详情页 8 页签骨架(关联项目/战略协议 seam 显 "-");跟进 `customer_follow` append-only,跟进刷新 `last_valid_follow_time`;关联商机页签只读反查 crm-opportunity;团队成员页签;操作日志 `customer_oplog` 字段级(对称商机 oplog 范式,「把 {字段} 从 {旧} 修改为 {新}」);**§5.5 拍板 A(20260902)**:`customer_oplog` **无限期保留**——审计日志不设保留期/清理/冷热分离(原型 L250「日志生成后不可修改或删除」= append-only 审计)。4 张汇总卡片外部模块失败显 "--" + 重试;预留占位卡一律渲染显 "--"(D28)。详见 `issues/07`。 + +- **[D14] crm-customer 领域上下文已建(票 01 resolved)** — 建 `crm-customer/CONTEXT.md` + `CONTEXT-MAP.md` 加行;依赖方向 `crm-opportunity → crm-customer`(商机反查客户/联系人),crm-customer 不反向依赖商机。详见 `issues/01`。 + +- **[D15] 客户交割已定稿(票 08 resolved)** — 两段式(发起交接 / 逐条分配);`customer_transfer` + 明细表,发起时整批原子、分配时每行独立。跨模块副作用:换主同步商机 owner(crm-opportunity),项目 owner 同步留 seam。直属销售总监 = 组织架构推导(D22),完成判定 = 自动完成 assignedCount==totalCount(D23)。`transfer_reason` 字典(离职/岗位调动/区域调整)。详见 `issues/08`。 + +- **[D16] 客户导入已定稿(票 09 resolved)** — 三种导入方式(仅新增 / 仅更新 / 新增或更新);匹配顺序 = 客户编号 → 信用代码,不按名称自动覆盖;异步任务模型(预校验/结果页/失败明细),行级部分成功;模板版本校验。文件内多行匹配同一客户默认「重复时不导入」(D24)。契约 `POST /api/customer/import/upload|/{taskId}/confirm`、`GET /{taskId}|/failures|/page|/template`。详见 `issues/09`。 + +- **[D17] 客户超期提醒规则表 + Job 已定稿(票 10 resolved)** — 见 D8(配置形态/提醒链/锚点)。补充:`customer_pending_notice` 待发通知表(对称 OpportunityPendingNotice,noticeType=OVERDUE_REMIND,notice_status 0待发/1已发/2已失效,payload JSON 含锚点快照);Job 只落待发不直接发(平台通知 spin-out);锚点语义按 D25(成为有主时刻)。详见 `issues/10`。 + +- **[D17b] 客户保存视图接入平台已定稿(票 11 resolved)** — 复用 `user_saved_view` 平台,不新建视图机制;3 个 scope_key(公海/总览/我的客户各一);`CustomerSavedViewFilter` 字段池(未知字段/操作符跳过);默认视图互斥继承平台;`enter_pool_time` 加入排序字段池(D29)。详见 `issues/11`。 + ## Not yet specified | 票 | 主题 | 依赖 | 前沿位置 | |---|---|---|---| -| 01 | 客户域领域词汇 + CONTEXT.md 骨架 (task) | — | ✅ 可开工(无依赖) | -| 02 | **客户主表字段清单** — 扇出最广,多票回填 | — | ✅ 可开工(无依赖) | -| 03 | 归属流转 + 三 workspace + 内置视图查询口径 | 02 | 02 done 后 | -| 04 | 新增查重校验 + 企查查工商回填 seam | 02 | 02 done 后 | -| 05 | 客户阶段(潜在→重潜→已成交,只前进;本图只做前两态) | 02 | 02 done 后 | -| 06 | 联系人 CRUD + 全局搜索 + 商机侧硬依赖接口 | 02 | 02 done 后 | -| 07 | 客户详情 8 页签 + 跟进记录 + 操作日志 | 02, 05 | 02+05 done 后 | -| 08 | 客户交割(跨模块同步商机/项目负责人事务) | 02, 03 | 02+03 done 后 | -| 09 | 客户导入(Excel,OCR 划出) | 02 | 02 done 后 | -| 10 | 客户规则族「超期未跟进提醒」(crm-rule 全局单例) | — | ✅ 可开工(无依赖) | -| 11 | 自定义视图接入 crm-preference(复用商机 saved-view) | 03 | 03 done 后(task) | - -**Frontier(当下可并行开工的票)**:`01`(task,建模块+CONTEXT 骨架)、`02`(主表,扇出最广,优先起 research)、`10`(规则单例,无依赖可并行)。02 resolve 后 03/04/05/06/09 集体解冻,再往下推。 - -**已知 grill 挂号**(不阻塞开票,各票 resolve 时问用户拍板): -- **票 04**:客户名称相似查重的算法/阈值(Trigram / MySQL 全文 / 编辑距离…) -- **票 05**:重潜的触发点到底是「方案卡提交(状态2)」还是「方案卡推送/报备成功(状态3)」? -- **票 08**:直属销售总监怎么定(是否在 SysUser 加 `superior_user_id`)? -- **票 09**:Excel 导入的相似客户处理策略(跳过/创建带标/报错) -- **票 10**:"规则在客户新增时生效"的确认解读——新增客户时初始化 `last_valid_follow_time = create_time`,30 天没跟进首次提醒(本条已在 grill Q4-补 与用户初步对齐,票 resolve 时最终确认) +| 01 | 客户域领域词汇 + CONTEXT.md 骨架 (task) | — | ✅ **resolved**(D14) | +| 02 | **客户主表字段清单** — 扇出最广,多票回填 | — | ✅ **resolved**(D7) | +| 03 | 归属流转 + 三 workspace + 内置视图查询口径 | 02 | ✅ **resolved**(D9) | +| 04 | 新增查重校验 + 企查查工商回填 seam | 02 | ✅ **resolved**(D10) | +| 05 | 客户阶段(潜在→重潜→已成交,只前进;本图只做前两态) | 02 | ✅ **resolved**(D11) | +| 06 | 联系人 CRUD + 全局搜索 + 商机侧硬依赖接口 | 02 | ✅ **resolved**(D12) | +| 07 | 客户详情 8 页签 + 跟进记录 + 操作日志 | 02, 05 | ✅ **resolved**(D13) | +| 08 | 客户交割(跨模块同步商机/项目负责人事务) | 02, 03 | ✅ **resolved**(D15) | +| 09 | 客户导入(Excel,OCR 划出) | 02 | ✅ **resolved**(D16) | +| 10 | 客户规则族「超期未跟进提醒」(crm-rule 全局单例) | — | ✅ **resolved**(D8) | +| 11 | 自定义视图接入 crm-preference(复用商机 saved-view) | 03 | ✅ **resolved**(D17) | + +**Frontier**:**空**——全部 11 张票已 resolve。spec 冻结,交棒 `/implement`。 + +**已知 grill 挂号**(不阻塞开票,各票 resolve 时问用户拍板;已拍板的已销号): +- ~~票 04 相似判定算法~~ **已销号(grill Q11 = seam 化,一期最简)** +- ~~票 05 重潜触发点~~ **已销号(grill Q10 = 保守,方案卡已提交(2))** +- ~~票 03 归档/合并~~ **已销号(grill Q9 = 归档即删除 + 筛选条件)** +- ~~customer_type 枚举~~ **已销号(grill Q8 = 18 值字典)** +- **票 08**:直属销售总监怎么定(是否在 SysUser 加 `superior_user_id`)?【待问】 +- **票 09**:Excel 导入的相似客户处理策略(跳过/创建带标/报错)【待问】 +- **票 10**:"规则在客户新增时生效"的确认解读——新增客户时初始化 `last_valid_follow_time = create_time`(本条已在 grill Q4-补 与用户初步对齐,票 resolve 时最终确认)【待问】 ## Out of scope @@ -73,6 +128,7 @@ - **客户查重列表 + 客户合并**(A4-6-1/2/3)— grill Q4 拍板不做;查重口径写死在新增流程。 - **OCR 快捷新增**(联系人/客户 OCR 识别)— 外部服务,划出;只在新增流程留 seam。 - **企查查等工商数据服务内部**— 只定调用契约 seam(调用点/回填范围/异常/精确查重触发),不深挖服务实现。 -- **客户阶段「已成交客户」**(由 A5 项目首次中标驱动)— 留 seam,本图只做潜在→重潜。 +- **客户阶段「已成交客户」**(由 A5 项目首次中标驱动)— 留 seam,A5 未建。 +- **客户阶段「重潜客户」**(由「方案卡提交并报备成功」驱动)— 留 seam(D20 改票 B),报备成功链路(方案卡已推送3 或 A5 回调)本期不可达;`CustomerStagePort.promote` 契约已定,本期无调用方。**本期客户阶段实际只有「潜在」。** - **「关联项目」详情页签**(读 A5 项目模块)— A5 未建,划出。 - **联系人导入**— 划出(客户导入本图做,联系人导入随图谱 effort)。 diff --git a/.scratch/customer-module/tickets/01-module-skeleton.md b/.scratch/customer-module/tickets/01-module-skeleton.md new file mode 100644 index 0000000..0f0bf4f --- /dev/null +++ b/.scratch/customer-module/tickets/01-module-skeleton.md @@ -0,0 +1,16 @@ +# 01 — 客户模块骨架 + +**What to build:** 创建 `crm-customer` Maven 子模块并挂进根工程与 `crm-app`,使空壳能 `mvn compile` 通过、单体应用能启动、健康检查能通。后续所有客户功能都落在这个模块里;本票只交付"能跑起来的空壳",不含任何业务功能。 + +**Blocked by:** None — can start immediately. + +**Status:** ready-for-agent + +**依赖方向(关键,避免 Maven 循环)**:`crm-customer` 只依赖 `crm-base`/`crm-auth`/`crm-rule`/`crm-dict`/`crm-file`/`crm-preference`。**不依赖 `crm-opportunity`**——「关联商机」反查(票 06)与「商机读客户」(票 11)均通过 customer 侧定义的出站 port 解耦,Maven 依赖只走 `crm-opportunity → crm-customer` 单向,循环依赖靠 Spring DI 解除。 + +- [ ] `crm-customer/pom.xml` 创建,dependencies:`crm-base` / `crm-auth` / `crm-rule` / `crm-dict` / `crm-file` / `crm-preference` + `lombok`(provided) + `spring-boot-starter-test`(test) + `h2`(test)(对称 `crm-opportunity/pom.xml`) +- [ ] 根 `pom.xml` `` 加入 `crm-customer`;`crm-app/pom.xml` 加入 `crm-customer` +- [ ] 包结构建好:`com.crm.customer`(controller / service / mapper / domain.entity / domain.enums / domain.dto / domain.param / port / query / config,对称 opportunity) +- [ ] 最小可行性:`Customer` 实体占位(继承 `BaseEntity` + `@TableName("customer")` + `@Entity @Table`)+ 一个 `GET /api/customer/ping` 健康接口,返回模块名/版本 +- [ ] 验证:`mvn compile` 全工程通过;`crm-app` 启动成功;`@SpringBootApplication(scanBasePackages="com.crm")` + `@MapperScan("com.crm.**.mapper")` + `@EntityScan("com.crm")` 自动扫到 customer 的 controller/mapper/实体 +- [ ] 源码规范:所有 `.java` 为 UTF-8 无 BOM(AGENTS.md);无孤立单大写字母触发 Hibernate 命名策略(命名检查清单) diff --git a/.scratch/customer-module/tickets/02-customer-crud-dedup.md b/.scratch/customer-module/tickets/02-customer-crud-dedup.md new file mode 100644 index 0000000..c20edfb --- /dev/null +++ b/.scratch/customer-module/tickets/02-customer-crud-dedup.md @@ -0,0 +1,19 @@ +# 02 — 客户主表 CRUD + 查重校验 + +**What to build:** 客户主数据能新增/编辑/查询/分页,且新增/编辑时内建三层查重校验(名称相似提示、信用代码唯一硬拦、并发兜底)。这是全模块的扇出底座——字段定义、DataScope、索引、查重端口都会被后续票复用。 + +**Blocked by:** 01 — 客户模块骨架 + +**Status:** ready-for-agent + +**Spec 依据(决策细节详见 .scratch/customer-module/issues/02-answer.md 与 04-answer.md)**:票 02 全字段清单 + 票 04 三层查重口径。本票只做主表实体与查重,**不含**联系人(票 03)、视图/归属(票 04)、详情页(票 06)。 + +- [ ] `customer` 实体全字段(票 02-answer §A1),含 `@DataScope(module="customer", ownerColumn="owner_user_id", deptColumn="owner_dept_id")`;系统字段继承 `BaseEntity`;`deleted` 默认 0 且**一期仅系统在合并时置 1**(§5.3 拍板 A,不开放手动删除/无前端删除入口) +- [ ] `@Table` 索引全部建好(票 02 A5):`uk_unified_credit_code`(unified_credit_code, MySQL NULL 不参与唯一约束) / `idx_owner_stage` / `idx_dept_stage` / `idx_name`(customer_name, FULLTEXT ngram token_size=2,供相似查重) / `idx_parent` / `idx_last_follow` +- [ ] 显式 `@Column(name=...)` 检查:票 02 §A2 结论为**全表无需**——所有字段均完整词+完整词,无孤立单大写陷阱,按清单复核后落实体 +- [ ] CRUD 契约:`POST /api/customer`(新增)/ `PUT /api/customer/{id}`(编辑)/ `GET /api/customer/{id}`(详情带出)/ `GET /api/customer/page`(分页)。编辑走 CAS 乐观锁 +- [ ] **三层查重**(票 04 §B1):L1 名称停输防抖(~300ms)相似提示(只提示不阻止,无权命中脱敏为"存在匹配记录");L2 保存时信用代码精确一致→硬拦禁止 + 名称相似→弹窗"仍要创建/返回修改";L3 仍要创建后写库前再查一次(信用代码+名称)仍冲突才写,DB UNIQUE 兜底 +- [ ] `CustomerNameSimilarityPort` 出站端口 seam(票 04 §B2):契约稳定、实现可插拔;一期最简实现=MySQL `MATCH(customer_name) AGAINST(?)` 取 topN + 判定"相似";阈值从 crm-rule「客户查重设置」读取(70/80/90%,非写死) +- [ ] 信用代码校验(票 04 §B3):格式 `^[0-9A-Z]{18}$`;精确相等→硬拦;命中无权查看的客户只提示"系统已存在该企业",不显 name/编号/负责人 +- [ ] 新增时初始化:`customer_stage`=1(潜在)、`archive_status`=1(有效);若直接指派负责人,`last_valid_follow_time`=create_time;若进公海,`last_valid_follow_time`=NULL(票 10 锚点语义) +- [ ] 服务层集成测试(H2 MySQL 模式):新增/编辑/查重三层的正常与冲突路径全覆盖 diff --git a/.scratch/customer-module/tickets/03-contact-subentity.md b/.scratch/customer-module/tickets/03-contact-subentity.md new file mode 100644 index 0000000..22d1232 --- /dev/null +++ b/.scratch/customer-module/tickets/03-contact-subentity.md @@ -0,0 +1,18 @@ +# 03 — 联系人子表 + +**What to build:** 客户下的联系人能增删改查、全局搜索,且联系人权限继承客户(无独立 ACL)。同客户内电话不重复做表单级校验;跨客户电话命中只软提示、不禁止(D26 口径)。 + +**Blocked by:** 02 — 客户主表 CRUD + 查重校验 + +**Status:** ready-for-agent + +**Spec 依据(详见 issues/06-answer.md)**:联系人独立表 `customer_contact` + 商机侧硬依赖契约。D26 翻案后口径:**不建电话唯一索引**,跨客户电话=可配置软提示。 + +- [ ] `customer_contact` 实体(票 06 §A 全字段):customer_id(NOT NULL,无孤立联系人) / name / jobTitleName / jobTitleId / jobTitleCategory / jobTitleLevel / phone / isKeyContact / isInternal / giftRemark / source + BaseEntity;`deleted` 逻辑删除 +- [ ] 索引:`idx_contact_customer`(customer_id, deleted);`idx_contact_name`(name)。**不建** `uk_contact_phone`(D26 已撤销) +- [ ] 权限模型(票 06):**无独立 `@DataScope`**;服务层按所属客户校验——对客户有读权限→对其全部联系人有读权限;无客户读写权限时隐藏联系人增删改入口、服务端仍校验 +- [ ] CRUD 契约:`POST /api/customer/contact` / `PUT /api/customer/contact/{id}` / `DELETE /api/customer/contact/{id}` / `GET /api/customer/{id}/contacts`(按客户查列表) +- [ ] 全局搜索:`GET /api/customer/contact/search`(带出所属客户名,商机「商机关键联系人」数据源) +- [ ] **电话查重(D26 最终口径)**:同一客户内同表格电话相同两行→禁止提交并定位重复行(表单级硬校验);无电话→按"客户+姓名+职务"提示疑似重复(只提示);跨客户电话命中→**软提示疑似重复、不阻断**,受 crm-rule「客户查重设置→联系电话」开关控制(默认开、可关) +- [ ] **商机侧硬依赖契约**:`POST /api/customer/quick-create`(最小必填集快速创建);`GET /api/customer/search`(名称/联系人/电话/类型四维模糊+分页)——供票 11 对接 +- [ ] 手机号格式校验;联系人不设独立 ACL 的服务层测试覆盖 diff --git a/.scratch/customer-module/tickets/04-workspaces-ownership.md b/.scratch/customer-module/tickets/04-workspaces-ownership.md new file mode 100644 index 0000000..0d6cc0b --- /dev/null +++ b/.scratch/customer-module/tickets/04-workspaces-ownership.md @@ -0,0 +1,24 @@ +# 04 — 三 workspace 视图 + 归属流转 + +**What to build:** 客户能在「我的客户 / 客户总览 / 客户公海」三个 workspace 里按各自数据范围列出、切换视图模式,并支持归属流转动作(领取 / 分配 / 抛公海 / 归档 / 恢复)。三视图是逻辑视图不落池表;每个归属动作写操作日志、走 CAS 乐观锁。 + +**Blocked by:** 02 — 客户主表 CRUD + 查重校验 + +**Status:** ready-for-agent + +**Spec 依据(详见 issues/03-answer.md)**:D9 三视图=逻辑视图;D3 归属⊥档案双轴;D19 归档=删除语义(无独立 tab)。 + +- [ ] 三 workspace 查询(票 03 §A,逻辑视图**不落库**): + - 我的客户 = 当前用户为销售负责人或协同人 且 `archive_status=1` 且 owner 非空 + - 客户总览 = `@DataScope` 覆盖范围内 未删除、未进公海(默认只列 `archive_status=1`) + - 客户公海 = `archive_status=1` 且 `owner_user_id IS NULL` + - 管理职务不扩大「我的客户」范围(走「客户总览」) +- [ ] 内置视图口径(票 03 §B,**硬编码不落库**):我的客户(我负责的/我协同的/待跟进的/我关注的/最近访问);公海(最近访问/我关注的);总览(最近访问/我关注的/我负责的/我协同的) +- [ ] 视图模式(票 03 §C)+ **看板三分组页签**(客户阶段/客户星级/关系星级);⚠ 本期「客户阶段」分组下客户均堆在「潜在客户」列,重潜/已成交列恒空=预期行为(D20 改票 B),不得造假阶段数据 +- [ ] 共用列 + 差异列(票 03 §D):公海列含 `enter_pool_time`「进入公海时间」、无负责人列 +- [ ] **新增字段回填**:`customer.enter_pool_time`(票 03 §F,抛公海写入 / 领取·分配清空) +- [ ] **归属流转动作**(票 03 §E):领取 / 分配 / 抛公海 / 归档(1→2) / 恢复(2→1),各带前置校验("无进行中商机"读 crm-opportunity opp_status=2;**项目侧留 seam** A5 未建先跳过;归档/合并仅销售负责人可) +- [ ] 批量操作部分失败不回滚(逐条独立事务,跳过不可操作项);**例外**:批量归档整批预检,任一失败整批不执行 +- [ ] 领取/分配副作用(D25,票 10 锚点):`last_valid_follow_time` 为空时→赋当前时刻;抛公海/归属迁移不动锚点 +- [ ] 每个归属动作写 `customer_oplog`(票 06);CAS 乐观锁 + version +- [ ] `@DataScope` 映射(票 03 §G):总览套 `DataScopeEnum(SELF/DEPT/DEPT_AND_CHILD/ALL)`;公海按可见部门范围 diff --git a/.scratch/customer-module/tickets/05-customer-stage-seam.md b/.scratch/customer-module/tickets/05-customer-stage-seam.md new file mode 100644 index 0000000..eef845f --- /dev/null +++ b/.scratch/customer-module/tickets/05-customer-stage-seam.md @@ -0,0 +1,17 @@ +# 05 — 客户阶段(seam 版) + +**What to build:** 客户主记录有一条只前进不回退的阶段指针(潜在→重潜→已成交),字段/枚举/CAS 机制全部按三态建好,但**本期不自动触发**任何进阶——只暴露一个提阶 port 留给未来链路调用。每次阶段变化写系统操作日志。 + +**Blocked by:** 02 — 客户主表 CRUD + 查重校验 + +**Status:** ready-for-agent + +**Spec 依据(详见 issues/05-answer.md)**:D11 只前进 CAS;**D20 第三轮 fresh 复核翻案(20260902 用户拍板 B)= 本期不自动触发重潜、只留 seam**。旧 answer 票曾写"本期做前两态"=已废口径,本票按新口径。 + +- [ ] `customer_stage` 三态枚举(票 05 §A):潜在1(新建默认) / 重潜2 / 已成交3;只增不减、不可手动改(无面向前端 setter API、阶段条不可点);父子客户不联动 +- [ ] 只前进 CAS(票 05 §B):`UPDATE customer SET customer_stage=? WHERE id=? AND customer_stage < ?`(天然幂等);已是更高阶段=no-op 静默成功,不报错不写日志 +- [ ] `CustomerStagePort.promote(customerId, targetStage, triggerSource)` seam(票 05 §D):只前进、传入更低阶段=no-op、返回提升后阶段 +- [ ] **本期无调用方**(D20 改票 B):`triggerSource` 示例 `scheme_card_reported`(方案卡提交并报备成功,本期链路不可达)/`project_won`(A5 seam);~~`scheme_card_submitted`~~ 弃用(不用"已提交"单条件凑合触发,避免脏状态) +- [ ] 失败语义(票 05 §E):阶段提升是**派生冗余视图**,失败**不阻断**主业务(方案卡照常提交);记日志告警 + 幂等重试;弱一致即可(区别于 lead→opp 强一致主业务) +- [ ] 每次阶段变化写 `customer_oplog`(票 06),操作人=`系统` + 注明触发来源 +- [ ] 单测:只前进 CAS 的幂等/no-op/并发路径覆盖 diff --git a/.scratch/customer-module/tickets/06-detail-follow-oplog.md b/.scratch/customer-module/tickets/06-detail-follow-oplog.md new file mode 100644 index 0000000..9f8fd04 --- /dev/null +++ b/.scratch/customer-module/tickets/06-detail-follow-oplog.md @@ -0,0 +1,23 @@ +# 06 — 客户详情页(8 页签 + 跟进记录 + 操作日志) + +**What to build:** 客户详情页能展示公共头部 + 汇总卡片 + 8 个页签;跟进记录能追加(不可改删)且写库刷新超期提醒锚点;操作日志按 append-only 审计记录并可分页查询。外部模块页签(关联项目/战略协议)显"--"占位。 + +**Blocked by:** 02 — 客户主表 CRUD + 查重校验;05 — 客户阶段(seam 版) + +**Status:** ready-for-agent + +**Spec 依据(详见 issues/07-answer.md)**:D13 详情 8 页签 + append-only 跟进 + 字段级 oplog;§5.5 拍板 A = oplog 无限期保留。 + +**关联商机反查的依赖方向(关键)**:「关联商机」页签需读 crm-opportunity,但 Maven 依赖只能 `opportunity → customer`。故 customer 侧**定义出站 port**(如 `CustomerOpportunityQueryPort`),由 crm-opportunity 实现(对称 ADR-0029 lead→opp port 范式,票 11 落实现);本票只定义 port + 页签消费契约,反查真源接线归票 11。 + +- [ ] 公共头部(票 07 §A,8 页签共享):客户名称/阶段只读条/客户星级/关系星级/战略协议等级(快照)/已成交VIP等级(快照)/档案状态/负责人+部门/最近跟进摘要/待跟进提醒/关联业务计数 +- [ ] 汇总卡片 4 张(票 07 §A):均外部模块提供,接口失败显"--"+重试、不显 0(grill Q18) +- [ ] 8 页签骨架(票 07 §A):1 客户信息(票02) / 2 联系人(票03) / 3 跟进记录 / 4 关联商机(port 反查) / 5 关联项目(A5 显"--") / 6 战略协议(显"--") / 7 团队成员 / 8 操作日志;首次打开才加载,切回保留条件/分页 +- [ ] `customer_follow` 跟进记录(票 07 §B):followWay(字典 follow_way)/followContent/nextFollowTime/跟进人快照;**提交后不可改删**(纠错=新增补充);防重复提交 +- [ ] 跟进写库副作用(关键):`customer.last_valid_follow_time = now`(超期提醒锚点刷新,票 09);更新"待跟进"状态 +- [ ] 跟进契约:`POST /api/customer/{id}/follow` / `GET /api/customer/{id}/follow/page`(时间倒序+日期分组,按 follow_way/关键字/时间范围过滤) +- [ ] 团队成员页签(票 07 §D):销售负责人快照 + 协同人列表;`GET /api/customer/{id}/members` +- [ ] `customer_oplog`(票 07 §E):action 枚举(CREATE/UPDATE/FOLLOW/ARCHIVE/RESTORE/TRANSFER/CLAIM/POOL/STAGE_CHANGE)/detail`把{字段}从{旧}修改为{新}`/操作人快照;**只追加不可改删**;**§5.5 无限期保留**(不设保留期/清理/冷热分离) +- [ ] oplog 契约:`GET /api/customer/{id}/oplog/page`(时间倒序+日期分组+关键字/时间范围过滤) +- [ ] `CustomerOpportunityQueryPort` seam 定义(关联商机页签只读反查,真源归票 11);`GET /api/customer/{id}/detail` +- [ ] crm-dict 新增 `follow_way` 分组(微信/拜访/电话/邮件/会议) diff --git a/.scratch/customer-module/tickets/07-customer-transfer.md b/.scratch/customer-module/tickets/07-customer-transfer.md new file mode 100644 index 0000000..f3dc06a --- /dev/null +++ b/.scratch/customer-module/tickets/07-customer-transfer.md @@ -0,0 +1,19 @@ +# 07 — 客户交割 + +**What to build:** 离职/调动/区域调整的销售本人能发起本人名下客户的两段式交割(发起交接→客户分配),交接单整批原子生成,接收总监再逐客户/批量分配给具体销售;换主时同步 crm-opportunity 商机负责人(项目侧留 seam)。 + +**Blocked by:** 04 — 三 workspace 视图 + 归属流转 + +**Status:** ready-for-agent + +**Spec 依据(详见 issues/08-answer.md)**:D15 两段式交割;D22 直属总监组织推导(20260902 复核补:离职=系统自动唯一确定;调动/区域调整=在启用的直属总监范围内下拉选)。 + +- [ ] 流程模型(票 08 §A,两段式):发起交接(owner→接收总监) → 客户分配(总监→具体销售) +- [ ] 权限:只允许离职/调动/区域调整的销售**本人**发起本人名下客户交接(主管/管理员不可代办) +- [ ] 可交接客户 = 该销售为**唯一负责人** 且 未删除 且 非公海(仅协同人的不纳入) +- [ ] 接收总监(D22):离职=系统自动确定唯一直属销售总监;调动/区域调整=在本人**启用的**直属总监范围内搜索选择 +- [ ] `customer_transfer` 交接单实体(票 08 §B1):transferNo(唯一)/from/toDirector 快照/reason(员工离职·岗位调动·区域调整)/totalCount/assignedCount/status(0待分配·1已完成) +- [ ] `customer_transfer_detail` 明细(票 08 §B2):transferId/customerId/assignStatus/assignUser 快照/assignTime;`UNIQUE(transfer_id, customer_id)` +- [ ] 交接单生成:全部变更成功才生成(整批原子);交接编号 + 状态"待分配" + 进度 0/N +- [ ] 事务语义(票 08 §C,跨模块):换 owner 同步 crm-opportunity 商机负责人;**项目侧留 seam**(A5 未建);每步写 `customer_oplog`(TRANSFER) +- [ ] 契约:发起交接 / 交接单列表 / 分配(逐客户+批量)/ 进度查询 diff --git a/.scratch/customer-module/tickets/08-customer-import.md b/.scratch/customer-module/tickets/08-customer-import.md new file mode 100644 index 0000000..4704f51 --- /dev/null +++ b/.scratch/customer-module/tickets/08-customer-import.md @@ -0,0 +1,19 @@ +# 08 — 客户导入 + +**What to build:** 用户能上传 Excel 异步导入客户主数据 + 联系人子表,三选一导入方式(仅新增/仅更新/新增或更新),行级处理允许部分成功,导入前预校验展示预计数、导入后出结果页与失败明细下载。 + +**Blocked by:** 02 — 客户主表 CRUD + 查重校验;03 — 联系人子表 + +**Status:** ready-for-agent + +**Spec 依据(详见 issues/09-answer.md)**:D16 三模式导入 + 异步 + 行级部分成功;OCR 快捷新增划出(Out-of-scope)。D26 翻案后联系人电话口径。 + +- [ ] 三导入方式(票 09 §B):仅新增/仅更新/新增或更新;匹配顺序=① 客户编号完全一致 → ② 统一社会信用代码完全一致 +- [ ] 匹配边界:编号与信用代码均缺失/均未匹配→不凭名称自动覆盖(名称相似列"疑似重复"本次不写入);分别匹配到不同客户→该行冲突禁止导入;文件内多行匹配同一客户→覆盖/不导入互斥(默认不导入) +- [ ] 字段语义(票 09 §C):新增导入必填空→行失败;更新导入空白单元格=不修改(V1 不支持清空);`customer_stage` 固定=潜在不得指定;禁止导入改负责人/协同人/阶段回退/工商字段/战略协议/汇总字段 +- [ ] **联系人子表口径(D26 翻案后)**:电话匹配本客户→更新;匹配他客户→**软提示疑似重复、不禁止导入**(跟随「联系电话」开关,关则不提示)、**不做跨客户电话唯一硬拦**;电话空→按"客户+姓名+职务"疑似重复不自动覆盖;同文件内同客户电话相同两行→该行失败 +- [ ] 原子单元=单条客户主记录/单条联系人子记录;联系人失败不回滚已成功客户主记录 +- [ ] 任务模型(票 09 §D,对称商机/线索导入范式):异步任务;预校验(文件类型.xls/.xlsx、≤20MB、模板版本、表头无空、单sheet≤50000行、忽略公式/图片);行级部分成功 +- [ ] 结果页:任务状态/总行数/新增成功/更新成功/未变化/失败/提交人/时间;失败明细(sheet/行号/客户识别/失败字段/原因)可下载、敏感值按权限脱敏 +- [ ] 操作日志:记任务ID/方式/模板版本/文件哈希/成功失败数;**不记文件名/内容/敏感原值**;超时异常→标失败不让重复提交 +- [ ] 契约(票 09 §D):upload / {taskId}/confirm / {taskId} / {taskId}/failures / import/page / import/template diff --git a/.scratch/customer-module/tickets/09-overdue-reminder.md b/.scratch/customer-module/tickets/09-overdue-reminder.md new file mode 100644 index 0000000..4cc6ac3 --- /dev/null +++ b/.scratch/customer-module/tickets/09-overdue-reminder.md @@ -0,0 +1,18 @@ +# 09 — 超期未跟进提醒 + +**What to build:** 后台可配一条全局单例的「超期未跟进提醒」规则(首次触发天数/二次间隔天数/开关),调度 Job 定期扫描超期客户并落待发通知。计时锚点=客户"成为有主"的时刻,跟进即刷新。 + +**Blocked by:** 02 — 客户主表 CRUD + 查重校验;06 — 客户详情页(跟进记录刷新锚点) + +**Status:** ready-for-agent + +**Spec 依据(详见 issues/10-answer.md)**:D8 三层对称商机回收提醒但配置=单例(不套版本化);D15/Q15 锚点=成为有主时刻。 + +- [ ] **crm-rule 侧** `customer_reminder_rule` 单例表(票 10 §B,跨模块改动):单行表 id 固定=1;masterEnabled/firstTriggerEnabled/firstTriggerDays(默认30)/secondIntervalEnabled/secondIntervalDays(默认7);无版本号/状态机/rule_code +- [ ] `CustomerReminderRuleInitializer` 种子初始化器(对称 `StageTemplateInitializer` 兜底范式)植入默认行 +- [ ] 校验:`firstTriggerDays>0`、`secondIntervalDays>0`;保存=全量覆盖(保存即生效);RBAC 功能权限 `customer.reminder.rule` +- [ ] 规则契约:`GET /api/rule/customer/reminder`(读单行)/ `PUT /api/rule/customer/reminder`(全量覆盖+记操作日志) +- [ ] **crm-customer 侧** `customer_pending_notice` 待发通知表(对称 `opportunity_pending_notice`) +- [ ] `CustomerReminderJob`(@Scheduled,对称 `OpportunityMaintenanceJob`):masterEnabled=0 整轮跳过;扫 `last_valid_follow_time` 超期客户写待发通知 +- [ ] 触发口径(票 10 §C,锚点=成为有主时刻,Q15 拍板 A):① 新建直接指派负责人→锚点=create_time;② 新建进公海(无负责人)→锚点=NULL 不提醒,被领取/分配时→锚点=领取时刻;③ 每次写跟进刷新锚点(票 06) +- [ ] 只前进、确定性提醒链;索引 `idx_last_follow`(last_valid_follow_time, owner_user_id) 扫表(票 02 已建) diff --git a/.scratch/customer-module/tickets/10-saved-view-integration.md b/.scratch/customer-module/tickets/10-saved-view-integration.md new file mode 100644 index 0000000..f2f6e88 --- /dev/null +++ b/.scratch/customer-module/tickets/10-saved-view-integration.md @@ -0,0 +1,17 @@ +# 10 — 自定义视图接入 crm-preference + +**What to build:** 三个客户 workspace 各自能保存/加载自定义检索视图,复用 crm-preference 的 `user_saved_view` 平台能力(不重写),通过 filter 白名单把保存的条件翻译成查询、且不越权改变数据范围。 + +**Blocked by:** 04 — 三 workspace 视图 + 归属流转 + +**Status:** ready-for-agent + +**Spec 依据(详见 issues/11-answer.md)**:D17 复用 saved-view 平台;rule of three(商机样本1+客户样本2,不抽共享翻译器)。 + +- [ ] 复用 crm-preference `user_saved_view`,**不重写**(各自 `CustomerSavedViewFilter`) +- [ ] 3 个 scope_key(票 11 §B):客户总览=`customer.overview` / 我的客户=`customer.mine` / 客户公海=`customer.pool`;公海双视角共用 `customer.pool`(视角只影响行内操作,不影响筛选) +- [ ] `CustomerSavedViewFilter`(票 11 §C):filter_json → MyBatis-Plus wrapper 翻译器(对称 `OpportunitySavedViewFilter`) +- [ ] 字段池白名单(票 11 §C):客户名称/编号(含/不含);类型/行业/阶段/星级/关系星级/战略等级/VIP等级/档案状态/地区(属于任一/不属于任一);最近跟进时间(早于/晚于/从未/已有);进入公海时间(仅pool);负责人/部门(仅overview/mine);创建时间。**未知字段/操作符→静默跳过** +- [ ] 排序字段池:创建时间/最近跟进时间/进入公海时间;文本字段不排 +- [ ] PageParam 接入(票 11 §D):三 workspace PageParam 加 `savedViewId`;ViewQueryImpl 有 savedViewId→取 filter 叠加 wrapper +- [ ] 验收(票 11 §E):scope_key 三值互斥且与 opportunity.* 无碰撞;白名单内生效/外跳过;每 scope 仅一个 isDefault;端到端保存→恢复;**saved-view 只叠加筛选不改变 @DataScope** diff --git a/.scratch/customer-module/tickets/11-opportunity-customer-wiring.md b/.scratch/customer-module/tickets/11-opportunity-customer-wiring.md new file mode 100644 index 0000000..4da7eed --- /dev/null +++ b/.scratch/customer-module/tickets/11-opportunity-customer-wiring.md @@ -0,0 +1,18 @@ +# 11 — 商机模块客户接口对接(切真源) + +**What to build:** 把 crm-opportunity 里之前 A4 未建时的降级 stub 全部切到真实 customer 数据源,让商机侧「关联客户 / 关键联系人 / 客户搜索 / 快速建客户」真正打通客户模块;同时接上 customer 侧「关联商机」页签的反查 port。这是清偿 A4 未建时留下的技术债。 + +**Blocked by:** 02 — 客户主表 CRUD + 查重校验;03 — 联系人子表;06 — 客户详情页(关联商机 port 定义) + +**Status:** ready-for-agent + +**背景**:A4 未建期间 crm-opportunity 用了多处降级 stub(返回空列表/仅存引用 id)。customer 建成后需切真源,签名不变。依赖方向只走 `opportunity → customer` 单向;customer 侧「关联商机」反查通过 customer 定义、opportunity 实现的出站 port 解耦(ADR-0029 范式),不形成 Maven 循环。 + +- [ ] `crm-opportunity/pom.xml` 增加依赖 `crm-customer`(单向,`opportunity → customer`) +- [ ] `OpportunitySubController` / `IOpportunitySubService#listCustomerContacts(customerId)`:stub 返回空列表 → 改调 customer `GET /api/customer/{id}/contacts` 真源;`CustomerContactDTO` 由 stub 字段占位 → 真源填充(签名不变) +- [ ] `searchCustomers(param)`:票 04 D-02「候选客户池降级=取关联客户快照去重」→ 切 customer `POST /api/customer/search`(四维模糊+分页)真源 +- [ ] `OpportunityCustomer.customerId`("客户模块 A4 未建:引用 id + 名快照并存"):确认关联真实 customer 记录;`customer_name_snapshot` 免 join 展示保留(快照口径不变) +- [ ] `POST /api/customer/quick-create`(商机弹窗快速建客户入口)真源接线 +- [ ] **customer 侧「关联商机」页签**(票 06 定义的 `CustomerOpportunityQueryPort`):在 crm-opportunity 实现该 port(按 customer_id 反查该客户全部商机:名称/编号/阶段/预计金额/负责人/最近更新),供客户详情页签只读展示 +- [ ] 回归:切真源后 crm-opportunity 原有关联客户/联系人相关测试仍绿;跨模块调用无循环依赖、`mvn compile` 全绿 +- [ ] 清理 stub 上遗留的"A4 未建/降级"注释,改为正式实现说明 diff --git a/.scratch/customer-module/tickets/12-prototype-alignment.md b/.scratch/customer-module/tickets/12-prototype-alignment.md new file mode 100644 index 0000000..f9747eb --- /dev/null +++ b/.scratch/customer-module/tickets/12-prototype-alignment.md @@ -0,0 +1,22 @@ +# 12 — 原型页面对齐验收 + +**What to build:** 全模块开发完成后,按蓝湖原型逐页对齐验收:字段/交互/状态/枚举/按钮逐项核对真实实现与原型是否一致,不符处修正。这是整个客户模块的收口验收,确保交付物与设计稿一致。 + +**Blocked by:** 01–11(全部功能票完成,且 `mvn compile` 通过、crm-app 能启动、各票关键接口冒烟通过) + +**Status:** ready-for-agent + +**验收源**:蓝湖 A4 客户模块原型(docId `bade4454`);fresh 提取文本在 `D:/code/crm-需求梳理/lanhu-mcp/data/axure_extract_bade4454_screenshots/`。逐页对照 `.txt` + 必要时 OCR `.png`(OCR 为 fallback,Windows CJK 路径可能 400)。 + +**逐页对齐清单(至少覆盖)**: + +- [ ] `A4-1-1 客户公海列表`:列集(已成交VIP客户等级/进入公海时间)、操作(详情/关注·取消关注/分配)、检索项、分页 +- [ ] `A4-2-1 客户总览` / `我的客户`:身份筛选(我负责的/我协同的)、看板三分组页签(客户阶段/客户星级/关系星级)、视图模式切换 +- [ ] `A4-3-3-1 新增客户`:全字段 + 客户类型下拉(后台配置字典) + 停输防抖相似查重 + 信用代码硬拦 +- [ ] `A4-4-1 客户详情`:公共头部 + 汇总卡片 4 张(失败显"--") + 8 页签;操作区只有归档/标记重点客户(无删除按钮,§5.3);已归档子页有恢复 +- [ ] `A4-4-8 操作日志`:append-only、字段级旧→新、时间倒序+日期分组 +- [ ] `A4-7-1 客户交割`:两段式、接收总监(离职自动/调动·区域调整下拉选)、整批原子、进度 +- [ ] `A4-3-2 客户导入`:三方式、预校验、结果页、失败明细 +- [ ] `A7-3-3-1 超期未跟进提醒` / `A7-3-3-2 客户查重设置`:单例配置项、查重开关(名称/电话/信用代码) +- [ ] 归档/领取/分配/抛公海动作弹窗文案与前置校验(无进行中商机/项目) +- [ ] 逐项记录"符合/不符→已修正",不符处开子任务修复直至对齐 diff --git a/.scratch/customer-module/tickets/13-api-documentation.md b/.scratch/customer-module/tickets/13-api-documentation.md new file mode 100644 index 0000000..e79a8ac --- /dev/null +++ b/.scratch/customer-module/tickets/13-api-documentation.md @@ -0,0 +1,19 @@ +# 13 — 接口文档(对照原型) + +**What to build:** 全模块开发完成后,对照原型界面产出/完善客户模块接口文档,保证每个原型界面用到的接口契约齐全、参数/出参/状态码/权限/脱敏规则齐备且与实现一致。交付一份可供前端与联调直接使用的接口文档。 + +**Blocked by:** 01–11(全部功能票完成);建议在 12 原型对齐后或并行进行 + +**Status:** ready-for-agent + +**文档载体**:复用现有 knife4j/swagger(OpenAPI)注解体系(对称 crm-opportunity 的 `@Schema`/`@Operation` 写法);接口文档以 knife4j 在线文档为主、必要时补一份 Markdown 汇总。 + +- [ ] 按原型界面梳理接口清单(每个界面→其依赖的接口),确保无遗漏、无孤立接口 +- [ ] 客户 CRUD / 查重(票 02):请求/响应 DTO 的 `@Schema` 描述齐全;查重弹窗返回结构(相似候选/脱敏字段)文档化 +- [ ] 联系人 CRUD + 搜索 + quick-create(票 03):含商机侧硬依赖接口 `search`/`contacts`/`quick-create` 契约 +- [ ] 三 workspace 列表 + 归属流转(票 04):分页出参、看板分组出参、各归属动作入参/前置校验错误码 +- [ ] 详情 8 页签 + 跟进 + oplog(票 06):各页签接口、分页/分组/过滤参数、失败显"--"约定 +- [ ] 交割(票 07)/ 导入(票 08)/ 提醒配置(票 09)/ saved-view(票 10)接口契约 +- [ ] 统一标注:权限(RBAC 功能权限 / @DataScope 数据范围)、字段级脱敏规则、错误码与业务含义 +- [ ] 校验:文档中每个接口都能在 knife4j 中打开并与实现签名一致;参数示例可直接联调 +- [ ] 与票 12 对齐结果保持同步——原型对齐中修正的接口同步更新文档 diff --git a/.scratch/customer-module/tickets/README.md b/.scratch/customer-module/tickets/README.md new file mode 100644 index 0000000..572efb9 --- /dev/null +++ b/.scratch/customer-module/tickets/README.md @@ -0,0 +1,46 @@ +# 客户模块(A4)实现票 — 依赖总览 + +从冻结 spec(`../map.md` + `../../crm-customer/CONTEXT.md` + `../issues/NN-answer.md` 决议票)折出的 tracer-bullet 实现票。每张=一条贯穿 schema→API→测试的垂直切片,blocker-first 编号。 + +## 依赖图 + +``` +01 骨架 (无 blocker) + └─ 02 主表CRUD+查重 (⭐扇出最广) + ├─ 03 联系人子表 + ├─ 04 三workspace+归属流转 + │ ├─ 07 客户交割 + │ └─ 10 saved-view接入 + ├─ 05 客户阶段(seam版) + │ └─ 06 详情(8页签+跟进+oplog) ← 也 blocked by 02 + │ └─ 09 超期提醒 ← 也 blocked by 02 + ├─ 08 客户导入 ← blocked by 02 + 03 + └─ 11 商机客户接口对接 ← blocked by 02 + 03 + 06 +12 原型对齐验收 ← blocked by 01–11 全部 +13 接口文档 ← blocked by 01–11 全部(可与 12 并行) +``` + +## 前沿顺序(可开工的票=其 blocker 全完成) + +| 波次 | 可并行开工 | +|---|---| +| 1 | **01** | +| 2 | **02** | +| 3 | **03** / **04** / **05** | +| 4 | **06**(需02+05) / **07**(需04) / **08**(需02+03) / **10**(需04) | +| 5 | **09**(需02+06) / **11**(需02+03+06) | +| 6 | **12** / **13**(需 01–11 全部 + mvn compile 通过 + crm-app 启动 + 关键接口冒烟) | + +## 每张票 `/implement` 时的通用要求 + +- `.java` 一律 UTF-8 无 BOM;新增 entity 过一遍孤立单大写字母检查清单(AGENTS.md) +- 依赖方向单向 `opportunity → customer`;「关联商机」反查走 customer 定义、opportunity 实现的出站 port(ADR-0029 范式),**不建 Maven 循环** +- 每张票收工前 `mvn compile` 全绿 + 服务层集成测试(H2 MySQL 模式)通过 +- 每张票 `/implement` 之间清 context 重新开始;完成后 `/code-review` + +## 已翻案/新拍板口径(实现时以此为准,勿回退旧 answer 正文) + +- **D20 改票 B**:客户阶段本期**不自动触发重潜**,只留 `CustomerStagePort` seam(票 05) +- **D26 拍板 A**:联系人电话=可配置软提示,**不建电话唯一索引**(票 03/08) +- **§5.3 拍板 A**:一期只做归档,**无前端删除入口**,`deleted` 仅系统合并时置位(票 02/04) +- **§5.5 拍板 A**:`customer_oplog` **无限期保留**(票 06) diff --git a/.scratch/opportunity-e2e/demo/shot-12-13-create-result.png b/.scratch/opportunity-e2e/demo/shot-12-13-create-result.png index 46ca8cfaeaeff01331ea415930d2705bbbdcae4f..da172df66a8a2994b61c2a0eb6cbeeeabbbec062 100644 GIT binary patch literal 61495 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ͣa}EEpQGF!- zZ@Pj_84G=JW@|!&1>WMQ4o6yZ@)(f7BmibWF}`1?aX8|LV~ck7%Hx+!%I<}o4s20H zsZ#fej3mi_fN?2Hxmx(AH~xYk_Wr1Euh0F4sEVdOf+E{rYFJk~O@P@|lCft2Drc9V zA)8KR%Q@FL5B!`|hh>#G@Z!8x(?(Lj|DW9FzI$fp}ZR0OgHSgo)2uVd1v3 z*EzcaIsNVJ{9;@>+(i6l+26SZac)fEZ8!&XH#;tmxVNVZa&;VY2DKUR|6&GOX z0+&0y^&${ql2bHy&MqpmBIsyOMBun~I^9ZFpSI&sopzj3@5D22w7?S0fv|d$<#c^e zgBWfxtImo@d&`cUFubF|rDz~r$3j|JZeDTI;vp8m`Z_$AX||$$#a{yNZT%MdPMNx+ zp|yLw(}L8UQJtuIw#t3eeTqjZqU5piJU04qRUrB6ey+pKn^I%{{unbw*6_6IoO|3m zrq4!nFP8Ab*B;EVN65E!iRMPR z3-86t#?Gn*EE2f9OTs-EbBpptJjD8+!fChrvm(UUGr;x8q!!Vl4J3M6)L2#`?+m-hCA^Xqj#Dj^>3zGONfb5B`zR!PxcI@{=C2xcCY`5mY1Dz|cgV0sN_PiKvf% znbDcW7%;Aj$$UKXx-BW8VuS%l!2~l0a-IhnLNtv(#Wnuq)={!OU_a1gpzc33xXd$K z%#xr=OG~ChGe0-e9eDr{rg220=^7~7Z z1vm4(npKTSzpFpfGrG=?B@T}bDjRZkS3qau(|=i$TzHwQP(O6MfC4!X9C+*(d$>$Cab{E&Bs zPx0_ha#tmo7CQi(`P~F9ZFid$(~UDe>$GzFVoa95S!s9JQbrvrT+S9oEag`nv8MQv zKP4i*J6R%@f2t7@0gc~Z4gKoZsE_W<3gptf-|OtXPow?3R%zTAVwcgli;6mZ|J%Ny zY43|M9LD{G+_*^IIDb?tM}YPXQ}zx?^?Z0h3+EmlM>J{D5e7;k2^aK_6w2 z%JH$jMY04*OL>|545xGMq@N;Ef-b&znfx*T2Z=v_$U(∾1Dbr}_ryS8k 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 literal 72798 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%0Di#$_7UV#4pic~rnDG)*}b=7 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 diff --git a/.scratch/opportunity-e2e/demo/shot-12-14-detail-after-create.png b/.scratch/opportunity-e2e/demo/shot-12-14-detail-after-create.png index 46ca8cfaeaeff01331ea415930d2705bbbdcae4f..fa16a79f63aa121bbda7cc66ab9caaa28e8ef3c4 100644 GIT binary patch literal 57726 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%0A&QvfLWAF?{$hn zz|1rAjcgu?Oo4{;=v{wVw>HGJAMfC8z87=jH=wVdG?`cz<3};=qwbZqhQ>29emm&n z5TcDnDzP#_N9P9zA(_Xvob(pKhl;1@cEDLfD57^aY4z9Bd9wielX5(%>)1OLX$ z)|N>*(>3WGtE>>|BhqR%s_4rR;H;0u1rrJRgt*5S*|Fd09hdWJhP8X(ziAATW(T|BAzazD`vpeaFkFTRxxNXNQDO?MYJ4OvB|9+EYFm8SzNL5*>fo6a_1X7#c zuz8v#9YIOKBpqZ+?A8M-wi~}fTd0ZwXAsj?fIsqd8y6(ycN||QD}86c7V^f-q%|X? zTyPdF_$zwr;0dv#8JOjKYy)gFm{QC97vg^#@!ds!<8S}qAC;PC|G(bet<+KfstBg!vn%9hC{{u2phxRyudVmkmmH(ubx*q}dm)V&Q`>5L%C zmynSBc%Sg;5pm@#FLxn+M>FufqJn6_$G^DR51;hLF&PoJXI$Wl_dxX0g_rI5+h64R zrij{`=$Gd-m75`UnbKbO|2oT-G(c;|L z+@cq;PRlEGoC;}0$Co)0R~N;{r_gDj8~)5sXvG6uTq$aP5^>xpuN8XX5z`x*ZGNEu zvkNS5whIahDp8whs7#{R8=*^f)y37-H={@RGF#u4UA-FgrUtJ$aHd1G zh8&`bO}Pv~wcT~1@=%}4A@#!qWOG_Kh76hM-;RmeoY?K!06#h0#&-9=zi|(V+vm?f z%zPY{ls{W_BsA{0b0;%)l^D_H0yt5m1EK*`wszTol=SWbTYRKIr+`=+Tb;A;Ad zxkuw$`whrOKL2@)Yw_%6>(0%EOvDW99Nr8EHLJ7s7==uq&pBSXcm`xKpk;)`{hRuI zistUXPJhH^RcuB}@Viy_k&w(WU9?_IsD&m1gIO<+5R)#16bNAy<suS4q4-^ z5Pval-y|$wgL0|WBe=rIHl5?ud%JfijE~&({50q$3>K?n((9=+(uO9cU1W<(e2SExM%|Xm3S5Co=d{gR^ipL0&o%9RXPu>cJ z4N^C4vsX@QYBI|gvQ73Qbx%Tg z|6lvhUTLhUy2rZ|HrMpqC%Q;wF?op5Yt|!~TJwE zQ>kb~CY@&+VNZwso)=UW&Q33$_9#Nj+eup|+NYLmUBpa47U*xnE_B7sH!SF|*6uD( zPaJLN217`W>;t_e&y4=UCZ7uXx&SXn#7*~5lv!xW+`&^j9mR%}BYvOnM)4Eh`n_0& zO|W+DFH-vJ#8DfNmI&jrz~H3W%XLH`UNx?M`sud%kcaOri&k6^q&u}YV z#_ADE^!5qabOUbL83Th{?lwEKlDrG?K!*#Glm?MmU~wuZh)z&s|=7d*edVVCF3o}I-eQ8sRiDO>Y{m2 z+3ZPDMcHl|wx)Jv)=XHaeGJOZV*OqGY<5&DOAh|V2_x8`HOFV{Xl!b{4}=Cy>AK%S z+6H3E43fvEb$9EV{1HbJ*5eDsqu}}zIJQMss@3Z!xw)L+icix&y9y;yZ%~bj$BMrP!*FH8RR;2Y;s&}p z)e?;nv9t?^LD~K5SO8l*H}b|rb9ph0jFZek5SfNQafXf892&>#lx7$6zQ0EDxsf>4 z*C?HPv0knr3LS77GY_bqg5q=^$&0z0J~vBu64kpn(Lo*NZxi-VYJehjszIbAu3RmU z{^z34sW6VJ_o3|*{k;IM+rh6Hnw_dEVFS5kC*ohalPo8&gn zSc&Y{#+_E_@4jae^e;W?YJzFnuU2(wN|2rOW&t71a*C2k$~AEtj~0^K+W=x~1#Pox zVAD#7E%@YhW&Tl#UX`qqh26X=4=3NfSufBH`9-(V2c|o^^*N%Hsonl=Gh<>uOB(d2 z8eMGS+$u2d79dbxUke6x<$k)+*ilFVJJyI*Z^!L5{##Y^zJ^d}9c_kwd83?}2rLG5 zberT^M0?O#QK%}^E%dwcSy4^dpzcPoJX#i{j*-&{tJ7h6F1D!d9US>RP+CqLb{JWy znl|D;G+MSfdCY5reJ4Oit0lqkz$nAoQ^@UU$u1>IZ1n}=?sH>!s(Frk1JIcwv$ja5 zf%z!@-7MY2%af^>##_F25Qiw|lF-O{UWTkOae;?@`L*Bmrx{Xgot+PL{aq@-;EuVp zwogg^@=nlIgva-k_l+e|0EYVOY;JE<95CglV)M0SjTF*B`99OezG58yYLG~0A$5Nq zu#cDvGN%G$2Alli0O`N~m;7Ex;vG* zUgCop>S|+iXTKaE`}u79Mv&kXJ^Cyka{6=$Oe-4lI$Cc+_T`VcwC;XZ>NjP+PW`}s z>KHTVH#j`7RQiddIMG>3HP7@A1Hy2E^@bYvb<__bn*y2J(@me5^)|M7ZBCxQ7vCQd z@$@U3r{m57oJ@OXm8t{m^ba+SM4m3(#`($jpFp(^-3J?BBlB(^h-4cs4NU`zCTBM! zn}NralDdCl1IbScIv9eDgDI2o`7)&f*lF zy#eG zI(qnO4H<4iyM5;|G2SbNdSY)K3Jxu%O4SR!_1aaHG>1W$&B|j23Blp5qvAqP3mh+#q z`u7cwh07(jwne5~MUh3q#pU~)vK|SwD|Y+vBo9ct`=OZ$HC2SFMHlz#ohLUEIJ zAYM59dN5r@EM){Ex#g6&i(AFCtWe3&))3x3I7Wu9sqN|cOOQtI8^sgIOQUDHWFvmt znDoX49y{etQ6sk$YK)P&jL_Tz_qDCYN~{KE0z{nj>kUGqR!6n>BA;EzRpW6l*R)i6 zlfx?FC@DyO%+SCS@3tsIypq z?7D+$%eJx#+EAzOdo4?H^xOg#QSjZYwk?TDueMrLM8rbeL7MS~K1Mtz*XXr{7!{ac z2c;8adhHwH5?&iVkd_wP;{%a70&2{YCzC$7FOuz`Gk>yZPhAXBCS;ygO2XCHbPHmZ zsfV{z@QX{n(BiD({nHTAAvd=Sz~MyLb1YE6>kPTLr`yPO8p2wxa2qo0ePZI02)t%J8yfc{385B*irYXIvxX;i} zGkF|{xXJ6j0o}3$-FdW+CsiEkFQSh&hk>Uug#E-9J`l6d4_S}S5$}n|qLfi-H3qtv zA^)fg+fdoP+P3zDUjCh7fu~GoeI?uU>2!31=?S~3G6Ub-D3#~^;*XAyGqNB~!5lg8 zu`1f+%YA=fnZAmrEDh8WGe?(dd%NAFwY`7p|pqKs`&_wX=H7bG^Vx zoU;6nSg#s~;fk``Mdwo;^wJAxi1n>tVQVd%1%Ck^aUf69L{*uU-uh?3Udi zP@Xa-gawAXnBJ(r>0&=}4EH-4Z!G8E8wi-^1bZ%^9!x*eB-=cyEel>lJa%*<3*1?l z1d(3)-Tz!CdfNMZ@lGWkr?`kX`(gMeV*XL-mZp)W8AUn}=VKxd+t${LDK(d7rqS|K zhGH-AM+C!&*5{j0qDR?dPHwjZ$?g@BZ#JN~gBdEuh9OCW1VbcP7SGy4wGhyRxEuIE zY7|T8RKWUXJ7Hn?JrI*(6KJ0yk%V~$@(kSs>mgr-D)**Xe_Z;a6q>UNWZ0W!m6igo z?_tTX?a5Lt{p&1-6i&KwBJ&TC1x<~5hTQ|2ymIvqr}GLVO@^M+)A)pZDzB=eQj6DE zNE>w3?!j!ZxfI0NcfdGlx_ieyA)dd5XMV7_U(i3*?qLNk z(W}Ogtfr>pz6~4N-LBtDXV<$uyQ)5+1g3umtLDXu8^e?NF7hi5GtT_Ts%i}P^N!MD zrS(s~%e5fv(m9>g{y2Pq>}oG%EglXP;Zz| zE<(w$FZg3#!yWN_FWX1ZZu6WIQ5_oflq_izwNeJR?}2Frz z197wylSmN1_+Ld5{%^A>So3_h4~@xbF|u2W~8k5F0QmJ*q$?Wtlzk9rm)V4NX1 zY-;F%t1222cqoD30XhSJa;_oKaIJPsms*U26ryr(?Lte3;7IZ=i<8(1e%*-(01Al~ z3mw!=uqcF<>3$W^f0j_h7S)YDd0hC}e1IkAvI7HcK8KlT9Ro6O5pl&)bO`nN1(}Pm z)K-P$4bEf_BGGn^Juc`T$q#|M#34d@u)^7>33LH-6`^_-=ma5V7ni6WDn&j_RO8im zeGR>gBc&^w0+Nk+7W_fyEbwsV#;7~Pva9JN=D3mglKo%I_EvNlm52NBafJoc^KiCX zixZMUSSZ%znB{Wmzk=sp=U;2-L})+Lbxs2#P0q9IsV#Ay5R zOPX$(OFSm<%OFv~@QocC6FjvnRTP~%B?%gu*-w4Vvd}0k)N+{NKM6)+7^$?$NvfHM z;v1I!AqO+7F~z;12e!CndGYM(z_p#ms0B{h5YL(i#b=|Cz}1*WMDW?X@r15DPewF1 zSik%04-eDe3IlcT0{ptnMKd;QlfZM0WcNBT)pd2oQV1r-q8N=on9tUtd9Ip6CwH;v zPmAd5CCDCTu|V)%{zU^fJTCJQj2?$kVq~^Qn(CpaGcM@1lbK;eh?lTh1OzjTX!ks{ zzDwO+$U;q0Z9>UT^3Lq20W)bc0q4^MfukuBP(>~Q2l2%uotYw z3FS;Rjf~QQ>;`0a4Qq9oI;uh}hU>?4t2>xsYp7m6NP*+SgQfw>GdQ?V8s7=h+zGVZ zcu~7>auPT`7|dUDi#C+nRw%_nVNr^Mn2NT)f!XiqnjHQ|vZ=yReAiy|5qL zxMQy4BPpTN8??|#C|{chEzWs6aJhBi`ai`X#gX(2WPSaScF_r5+?AUwP4*tofRv2I zEJx4gLg8A{#0Uitfeo7|9(!;_GCm*gn?0AeB$u(LX`bhDJgxV#P~B)`TAwx;!SKM&&e3B? zSvjdF@V3R2v1MWU?S}@=!|z`Lc7GJr6qnn@Xg*IER#HP z{FpzN+@TVTF?zvRFhan5-ru!Z}5Q8|GBOlqTWcE zBnyQ6Ew}BQMOx2U8#IK1irxw#mQ|Z$`N41poV5HHUQm;3x~LpF5EK-_K8e9#q=3v{ znB-r{$7Da9N+ETxxj}3-avLll1%Ht34-x;h4uTr3til#w5!a!{u6?OyI>^zgiZ8L! zr2=|`M71H^P!lc*Rw0vvgi|gq-oX<{mGIq6=3J_gkqR1fxPigo@iPD)R#9q|ifS^% z>B)%@#<>pB&D%$8-=haWM&P@aCfi)LLz820MzVmf3zsY~=LbGubm4~SUAXG{+HhNi zH@fmFdMg4sLr3-7pPUB zwgUA+_@Da%CnMi$V3G+)r{i9#Dy9Fm-&1ab!IVnl?$+Z#tSyjO8)2JDUvAKRsS9n< OU`XdPPL(J8@BR*ky#ee1 literal 72798 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%0Di#$_7UV#4pic~rnDG)*}b=7 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 diff --git a/.scratch/opportunity-e2e/opp-schema-migration.sql b/.scratch/opportunity-e2e/opp-schema-migration.sql new file mode 100644 index 0000000..c2508ba --- /dev/null +++ b/.scratch/opportunity-e2e/opp-schema-migration.sql @@ -0,0 +1,120 @@ +-- ============================================================ +-- 测试环境 crm 库 schema 补齐(opportunity / opportunity_customer) +-- 背景 20260901:导表后两表为残缺混合结构(缺 party_a_clear 等列, +-- 实体映射全列 SELECT/INSERT → SQLSyntaxErrorException → 50001)。 +-- 用法:Navicat 整段执行 / mysql -uroot -p crm < opp-schema-migration.sql +-- 幂等:列已存在自动跳过(information_schema 判存 + 动态 SQL,MySQL 8 无 ADD COLUMN IF NOT EXISTS)。 +-- 对齐实体:Opportunity.java(L40-230)+ OpportunityCustomer.java + BaseEntity.java。 +-- 更省事的替代:重启 crm-app 容器即可,jpa.hibernate.ddl-auto=update 会自动补齐全部缺列。 +-- ============================================================ + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS crm_add_column $$ +CREATE PROCEDURE crm_add_column( + IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_ddl TEXT) +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = p_table AND COLUMN_NAME = p_column + ) THEN + SET @s = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_ddl); + PREPARE st FROM @s; EXECUTE st; DEALLOCATE PREPARE st; + SELECT CONCAT('ADD ', p_table, '.', p_column) AS applied; + END IF; +END $$ + +DELIMITER ; + +-- ---------- opportunity 主表 ---------- +CALL crm_add_column('opportunity','opp_name', "varchar(200) NOT NULL COMMENT '商机名称'"); +CALL crm_add_column('opportunity','opp_source', "varchar(64) NOT NULL COMMENT '商机来源字典code'"); +CALL crm_add_column('opportunity','opp_type', "varchar(64) NOT NULL COMMENT '商机类型字典code'"); +CALL crm_add_column('opportunity','industry_code', "varchar(64) NOT NULL COMMENT '行业字典code'"); +CALL crm_add_column('opportunity','bid_form', "varchar(64) NOT NULL COMMENT '招标形式字典code'"); +CALL crm_add_column('opportunity','locality_type', "varchar(64) NOT NULL COMMENT '项目属地字典code'"); +CALL crm_add_column('opportunity','party_a_clear', "tinyint NOT NULL DEFAULT 0 COMMENT '甲方是否明确'"); +CALL crm_add_column('opportunity','party_a', "varchar(200) NULL COMMENT '甲方名称'"); +CALL crm_add_column('opportunity','province_code', "varchar(12) NOT NULL COMMENT '省份国标code'"); +CALL crm_add_column('opportunity','city_code', "varchar(12) NOT NULL COMMENT '市/区国标code'"); +CALL crm_add_column('opportunity','remark', "text NULL COMMENT '备注'"); +CALL crm_add_column('opportunity','project_amount', "decimal(14,2) NULL COMMENT '项目金额'"); +CALL crm_add_column('opportunity','address', "varchar(255) NULL COMMENT '项目详细地址'"); +CALL crm_add_column('opportunity','owner_user_id', "bigint NULL COMMENT '领取人ID'"); +CALL crm_add_column('opportunity','owner_name_snapshot', "varchar(64) NULL COMMENT '领取人姓名快照'"); +CALL crm_add_column('opportunity','owner_dept_id', "bigint NULL COMMENT '领取人部门快照'"); +CALL crm_add_column('opportunity','origin_dept_id', "bigint NULL COMMENT '原始血缘部门'"); +CALL crm_add_column('opportunity','pool_reason', "varchar(32) NULL COMMENT '进入公海原因'"); +CALL crm_add_column('opportunity','creator_user_id', "bigint NOT NULL COMMENT '创建人ID'"); +CALL crm_add_column('opportunity','opp_status', "tinyint NOT NULL COMMENT '商机状态'"); +CALL crm_add_column('opportunity','current_stage_id', "bigint NOT NULL COMMENT '当前阶段节点ID'"); +CALL crm_add_column('opportunity','stage_template_id', "bigint NOT NULL COMMENT '阶段模板版本行ID'"); +CALL crm_add_column('opportunity','stage_template_version', "varchar(20) NOT NULL COMMENT '阶段模板版本号冗余'"); +CALL crm_add_column('opportunity','source_lead_id', "bigint NULL COMMENT '来源线索ID'"); +CALL crm_add_column('opportunity','source_lead_name', "varchar(200) NULL COMMENT '线索名快照'"); +CALL crm_add_column('opportunity','source_phone', "varchar(32) NULL COMMENT '联系电话快照'"); +CALL crm_add_column('opportunity','source_product_code', "varchar(64) NULL COMMENT '需求产品码快照'"); +CALL crm_add_column('opportunity','claim_time', "datetime NULL COMMENT '领取时刻'"); +CALL crm_add_column('opportunity','last_valid_follow_time', "datetime NULL COMMENT '最近有效跟进时间'"); +CALL crm_add_column('opportunity','primary_customer_id', "bigint NULL COMMENT '主要意向客户ID'"); +CALL crm_add_column('opportunity','primary_customer_name_snapshot', "varchar(200) NULL COMMENT '主客户名快照'"); +CALL crm_add_column('opportunity','key_contact_id', "bigint NULL COMMENT '商机关键联系人ID'"); +CALL crm_add_column('opportunity','key_contact_name_snapshot', "varchar(200) NULL COMMENT '商机关键联系人姓名快照'"); +CALL crm_add_column('opportunity','key_contact_company_snapshot', "varchar(200) NULL COMMENT '关键人所在公司快照'"); +CALL crm_add_column('opportunity','scheme_budget', "decimal(18,2) NULL COMMENT '方案卡预算快照'"); +CALL crm_add_column('opportunity','scheme_card_status', "int NULL COMMENT '方案卡状态快照'"); +CALL crm_add_column('opportunity','pause_time', "datetime NULL COMMENT '暂缓开始时刻'"); +CALL crm_add_column('opportunity','pause_reason', "varchar(32) NULL COMMENT '暂缓原因'"); +CALL crm_add_column('opportunity','pause_expected_restart_date', "date NULL COMMENT '预计重启日期'"); +CALL crm_add_column('opportunity','pause_remark', "varchar(500) NULL COMMENT '暂缓补充说明'"); +CALL crm_add_column('opportunity','version', "int NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'"); +-- BaseEntity 公共列(通常都在,幂等兜底) +CALL crm_add_column('opportunity','creator_id', "varchar(50) NULL COMMENT '创建用户id'"); +CALL crm_add_column('opportunity','updater_id', "varchar(50) NULL COMMENT '更新用户id'"); +CALL crm_add_column('opportunity','create_time', "datetime NULL COMMENT '创建时间'"); +CALL crm_add_column('opportunity','update_time', "datetime NULL COMMENT '更新时间'"); +CALL crm_add_column('opportunity','delete_key', "tinyint NOT NULL DEFAULT 0 COMMENT '逻辑删除位'"); + +-- ---------- opportunity_customer 关联客户表 ---------- +CALL crm_add_column('opportunity_customer','opportunity_id', "bigint NOT NULL COMMENT '所属商机ID'"); +CALL crm_add_column('opportunity_customer','customer_id', "bigint NOT NULL COMMENT '客户ID'"); +CALL crm_add_column('opportunity_customer','customer_name_snapshot', "varchar(200) NOT NULL COMMENT '客户名快照'"); +CALL crm_add_column('opportunity_customer','customer_role', "varchar(32) NULL COMMENT '客户角色字典code'"); +CALL crm_add_column('opportunity_customer','is_primary_intended', "tinyint NOT NULL DEFAULT 0 COMMENT '是否主要意向客户'"); +CALL crm_add_column('opportunity_customer','delete_key', "bigint NOT NULL DEFAULT 0 COMMENT '软删除复用键'"); +CALL crm_add_column('opportunity_customer','creator_id', "varchar(50) NULL COMMENT '创建用户id'"); +CALL crm_add_column('opportunity_customer','updater_id', "varchar(50) NULL COMMENT '更新用户id'"); +CALL crm_add_column('opportunity_customer','create_time', "datetime NULL COMMENT '创建时间'"); +CALL crm_add_column('opportunity_customer','update_time', "datetime NULL COMMENT '更新时间'"); + +-- ---------- 索引/约束补齐(不阻塞运行,建议顺手执行) ---------- +SET @ix := (SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'opportunity' AND INDEX_NAME = 'idx_source_lead'); +SET @sql := IF(@ix = 0, + 'ALTER TABLE `opportunity` ADD UNIQUE INDEX `idx_source_lead` (`source_lead_id`)', + 'SELECT 1'); +PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; + +SET @ix := (SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'opportunity' AND INDEX_NAME = 'idx_owner_status'); +SET @sql := IF(@ix = 0, + 'ALTER TABLE `opportunity` ADD INDEX `idx_owner_status` (`owner_user_id`, `opp_status`)', + 'SELECT 1'); +PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; + +SET @ix := (SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'opportunity' AND INDEX_NAME = 'idx_owner_dept_status'); +SET @sql := IF(@ix = 0, + 'ALTER TABLE `opportunity` ADD INDEX `idx_owner_dept_status` (`owner_dept_id`, `opp_status`)', + 'SELECT 1'); +PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; + +DROP PROCEDURE crm_add_column; + +-- 执行完核对(应全部返回 0 行 = 缺列已补齐): +-- SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS +-- WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN ('opportunity','opportunity_customer') +-- AND COLUMN_NAME IN ('party_a_clear','project_amount','address','key_contact_id', +-- 'key_contact_name_snapshot','key_contact_company_snapshot', +-- 'customer_role','is_primary_intended'); diff --git a/.scratch/patch-naming-trap.md b/.scratch/patch-naming-trap.md new file mode 100644 index 0000000..2f88ff0 --- /dev/null +++ b/.scratch/patch-naming-trap.md @@ -0,0 +1,45 @@ +### JPA/Hibernate 命名策略:孤立单大写字母陷阱 + +Spring Boot 默认的 `SpringPhysicalNamingStrategy` 把驼峰转蛇形时,**会把"孤立的单个大写字母"当成缩略语(同 `URL` / `ID` 处理),不插下划线**。触发条件:某个大写字母的**左侧是小写或字符串开头、右侧是大写或字符串结尾**。 + +这会导致 Hibernate 建表的物理列名与 MyBatis-Plus 生成 SQL 时的列名(走 `camelToUnderscore`)**不一致**,报错形如: + +``` +java.sql.SQLSyntaxErrorException: Unknown column 'party_a_clear' in 'field list' +``` + +已知踩坑记录: + +| Java 字段 | 期望列名 | Hibernate 实际建成 | MyBatis SQL 写的 | +|---|---|---|---| +| `partyA` | `party_a` | `partya` ❌ | `party_a` | +| `partyAClear` | `party_a_clear` | `partyaclear` ❌ | `party_a_clear` | + +**规则**:Java 字段名里出现下述形态的**孤立单大写字母**时,**必须**在 `@Column` 上显式写 `name="蛇形列名"` 钉死: + +- 结尾单大写:`xxxA`、`xxxB`、`xxxZ` +- 夹在小写与大写词之间的单大写:`xxxAYyy`、`xxxBClear` +- 连续单大写:`xxxABC`(会整段当缩略语,不断词) + +反例(**这些形态不会踩坑,无需 `@Column(name=...)`**): + +- `ownerUserId` → `owner_user_id` ✅(`Id` 是两字符段,按普通词处理) +- `bidForm` → `bid_form` ✅(`Form` 是完整词) +- `provinceCode` → `province_code` ✅ + +**修复模板**: + +```java +@Column(name = "party_a_clear", columnDefinition = "tinyint not null default 0 comment '甲方是否明确'") +private Integer partyAClear = 0; +``` + +**为什么不换命名策略**:换策略会影响全部已建对的列(几十列),代价远大于给个位数问题字段加 `@Column(name=...)`。 + +**为什么线上很久才暴露**:`ddl-auto: update` 只加列不改列。老字段可能长期只走 SELECT(`ResultMap` 兜住)不写入,直到某天走 INSERT/UPDATE 才炸。**新加 Java 字段时**如果撞规则,第一次持久化就会报 `Unknown column`。 + +**新增字段检查清单**(写完 entity、跑起来之前): + +1. 字段名里有没有孤立单大写字母?没有 → 直接过。 +2. 有 → 加 `@Column(name = "snake_case_name", columnDefinition = "...")`。 +3. `ddl-auto: update` 环境下,如果表已经存在且字段名撞过坑,需要一条 `ALTER TABLE t CHANGE 旧列名 新列名 类型 ...;` 手工迁移,Hibernate 不会自动改名。 diff --git a/.scratch/patch_ctx_53.md b/.scratch/patch_ctx_53.md new file mode 100644 index 0000000..06ac60e --- /dev/null +++ b/.scratch/patch_ctx_53.md @@ -0,0 +1 @@ +`archive_status` 单字段(有效 1 / 已归档 2),正交于归属与阶段(D19 去掉「已被合并」态,合并 Out-of-scope,A4-6 不做)。**已归档 = 删除语义(软删)**:默认排除于列表筛选、加 `archive_status` 筛选可查,可恢复(恢复回有效)。仅销售负责人可归档。**§5.3 拍板 A(一期只做归档)**:**不提供任何前端「删除客户」入口**——系统级 `deleted` 软删标志仅在合并时由系统置 1,不开放手动删除、无前端删除按钮;DB 不做物理删除。归档与合并是仅有的两种"移除"用户动作,且归档可恢复、合并本体 Out-of-scope。 diff --git a/.scratch/patch_map_53_55.md b/.scratch/patch_map_53_55.md new file mode 100644 index 0000000..171379e --- /dev/null +++ b/.scratch/patch_map_53_55.md @@ -0,0 +1,3 @@ +总览/我的客户主列表默认只出 `archive_status=1`。票 10 提醒排除条件同步收敛为「公海 + 已归档」。**§5.3 拍板 A(20260902,一期只做归档)**:**不提供任何前端「删除客户」入口**——用户侧只有归档(软删可恢复)+ 合并(源客户系统软删,且合并本体 Out-of-scope);`deleted` 标志仅系统在合并时置 1,不开放手动删除、无前端删除按钮,DB 不做物理删除。详见 `issues/03`。 + +操作日志 `customer_oplog` 字段级(对称商机 oplog 范式,「把 {字段} 从 {旧} 修改为 {新}」);**§5.5 拍板 A(20260902)**:`customer_oplog` **无限期保留**——审计日志不设保留期/清理/冷热分离(原型 L250「日志生成后不可修改或删除」= append-only 审计)。 diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/AGENTS.md b/AGENTS.md index 1d1adea..63e84f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,52 @@ All `.java` source files **must be UTF-8 without BOM** (no `EF BB BF` byte prefi ``` - Run a full BOM scan across all `*.java` files after bulk edits (Write/SearchReplace) and before `mvn compile`. +### JPA/Hibernate 命名策略:孤立单大写字母陷阱 + +Spring Boot 默认的 `SpringPhysicalNamingStrategy` 把驼峰转蛇形时,**会把"孤立的单个大写字母"当成缩略语(同 `URL` / `ID` 处理),不插下划线**。触发条件:某个大写字母的**左侧是小写或字符串开头、右侧是大写或字符串结尾**。 + +这会导致 Hibernate 建表的物理列名与 MyBatis-Plus 生成 SQL 时的列名(走 `camelToUnderscore`)**不一致**,报错形如: + +``` +java.sql.SQLSyntaxErrorException: Unknown column 'party_a_clear' in 'field list' +``` + +已知踩坑记录: + +| Java 字段 | 期望列名 | Hibernate 实际建成 | MyBatis SQL 写的 | +|---|---|---|---| +| `partyA` | `party_a` | `partya` ❌ | `party_a` | +| `partyAClear` | `party_a_clear` | `partyaclear` ❌ | `party_a_clear` | + +**规则**:Java 字段名里出现下述形态的**孤立单大写字母**时,**必须**在 `@Column` 上显式写 `name="蛇形列名"` 钉死: + +- 结尾单大写:`xxxA`、`xxxB`、`xxxZ` +- 夹在小写与大写词之间的单大写:`xxxAYyy`、`xxxBClear` +- 连续单大写:`xxxABC`(会整段当缩略语,不断词) + +反例(**这些形态不会踩坑,无需 `@Column(name=...)`**): + +- `ownerUserId` → `owner_user_id` ✅(`Id` 是两字符段,按普通词处理) +- `bidForm` → `bid_form` ✅(`Form` 是完整词) +- `provinceCode` → `province_code` ✅ + +**修复模板**: + +```java +@Column(name = "party_a_clear", columnDefinition = "tinyint not null default 0 comment '甲方是否明确'") +private Integer partyAClear = 0; +``` + +**为什么不换命名策略**:换策略会影响全部已建对的列(几十列),代价远大于给个位数问题字段加 `@Column(name=...)`。 + +**为什么线上很久才暴露**:`ddl-auto: update` 只加列不改列。老字段可能长期只走 SELECT(`ResultMap` 兜住)不写入,直到某天走 INSERT/UPDATE 才炸。**新加 Java 字段时**如果撞规则,第一次持久化就会报 `Unknown column`。 + +**新增字段检查清单**(写完 entity、跑起来之前): + +1. 字段名里有没有孤立单大写字母?没有 → 直接过。 +2. 有 → 加 `@Column(name = "snake_case_name", columnDefinition = "...")`。 +3. `ddl-auto: update` 环境下,如果表已经存在且字段名撞过坑,需要一条 `ALTER TABLE t CHANGE 旧列名 新列名 类型 ...;` 手工迁移,Hibernate 不会自动改名。 + ## Tooling / harness ### File-edit tool degradation strategy (when `edit` silently drops its payload) diff --git a/CONTEXT-MAP.md b/CONTEXT-MAP.md index 9414910..24303a0 100644 --- a/CONTEXT-MAP.md +++ b/CONTEXT-MAP.md @@ -12,6 +12,7 @@ This is a multi-module Maven monorepo. Each `crm-{域}` module is a bounded cont | `crm-base` | 基础包(通用能力,非业务域) | [`crm-base/CONTEXT.md`](./crm-base/CONTEXT.md) | | `crm-lead` | 线索业务(生命周期/状态机/四视图) | [`crm-lead/CONTEXT.md`](./crm-lead/CONTEXT.md) | | `crm-opportunity` | 商机业务(状态+阶段双轴/四视图/转项目) | [`crm-opportunity/CONTEXT.md`](./crm-opportunity/CONTEXT.md) | +| `crm-customer` | 客户业务(三阶段/三 workspace/联系人/交割) | [`crm-customer/CONTEXT.md`](./crm-customer/CONTEXT.md) | | `crm-rule` | 业务规则域(线索公海池 + 商机规则子域) | [`crm-rule/CONTEXT.md`](./crm-rule/CONTEXT.md) | | `crm-preference` | 列偏好(用户级列显隐/排序) | [`crm-preference/CONTEXT.md`](./crm-preference/CONTEXT.md) | | `crm-app` | 唯一启动入口 | _(暂无独立领域语言)_ | diff --git a/crm-app/.classpath b/crm-app/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-app/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-app/.factorypath b/crm-app/.factorypath new file mode 100644 index 0000000..66b2b4f --- /dev/null +++ b/crm-app/.factorypath @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-app/.project b/crm-app/.project new file mode 100644 index 0000000..84d155d --- /dev/null +++ b/crm-app/.project @@ -0,0 +1,34 @@ + + + crm-app + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974709 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-app/.settings/org.eclipse.core.resources.prefs b/crm-app/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..abdea9a --- /dev/null +++ b/crm-app/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding/=UTF-8 diff --git a/crm-app/.settings/org.eclipse.jdt.apt.core.prefs b/crm-app/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-app/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-app/.settings/org.eclipse.jdt.core.prefs b/crm-app/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-app/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-app/.settings/org.eclipse.m2e.core.prefs b/crm-app/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-app/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/crm-auth/.classpath b/crm-auth/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-auth/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-auth/.factorypath b/crm-auth/.factorypath new file mode 100644 index 0000000..5b79b17 --- /dev/null +++ b/crm-auth/.factorypath @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-auth/.project b/crm-auth/.project new file mode 100644 index 0000000..50e483e --- /dev/null +++ b/crm-auth/.project @@ -0,0 +1,34 @@ + + + crm-auth + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974768 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-auth/.settings/org.eclipse.core.resources.prefs b/crm-auth/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..839d647 --- /dev/null +++ b/crm-auth/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/crm-auth/.settings/org.eclipse.jdt.apt.core.prefs b/crm-auth/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-auth/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-auth/.settings/org.eclipse.jdt.core.prefs b/crm-auth/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-auth/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-auth/.settings/org.eclipse.m2e.core.prefs b/crm-auth/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-auth/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/crm-base/.classpath b/crm-base/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-base/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-base/.factorypath b/crm-base/.factorypath new file mode 100644 index 0000000..92efe8d --- /dev/null +++ b/crm-base/.factorypath @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-base/.project b/crm-base/.project new file mode 100644 index 0000000..52609de --- /dev/null +++ b/crm-base/.project @@ -0,0 +1,34 @@ + + + crm-base + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974777 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-base/.settings/org.eclipse.core.resources.prefs b/crm-base/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..f9fe345 --- /dev/null +++ b/crm-base/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/crm-base/.settings/org.eclipse.jdt.apt.core.prefs b/crm-base/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-base/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-base/.settings/org.eclipse.jdt.core.prefs b/crm-base/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-base/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-base/.settings/org.eclipse.m2e.core.prefs b/crm-base/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-base/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/crm-customer/CONTEXT.md b/crm-customer/CONTEXT.md new file mode 100644 index 0000000..b09ff96 --- /dev/null +++ b/crm-customer/CONTEXT.md @@ -0,0 +1,57 @@ +# 客户业务(crm-customer) + +客户的全生命周期业务域:客户实体与三阶段(潜在 / 重潜 / 已成交)、三个 workspace(客户总览 / 我的客户 / 客户公海)、新增 / 编辑 / 导入、联系人子实体、跟进记录与操作日志(`customer_oplog` **无限期保留**,审计日志不设清理/冷热分离,§5.5)、客户交割(离职/调岗批量换主),以及客户规则族「超期未跟进提醒」。依赖方向单向 `crm-opportunity → crm-customer`(商机读客户、建关联);`crm-customer → crm-rule → (crm-auth / crm-dict / crm-file)`;客户读「关联商机」走下游反查上游(同库单体阶段可接受,见 ADR-0029 循环依赖技术债)。访问控制走三机制模型,`@DataScope(module="customer", ownerColumn="owner_user_id", deptColumn="owner_dept_id")`。 + +## Language + +**客户**: +`crm-customer` 的核心聚合根。归属由 `owner_user_id`(销售负责人)+ `owner_dept_id`(销售部门快照)表达,owner 空 = 在客户公海;档案正交维度 `archive_status`(有效 / 已归档)独立于归属(D19 去掉「已被合并」,合并 Out-of-scope)。阶段字段 `customer_stage`(潜在 / 重潜 / 已成交)只前进不回退,由外部成功结果驱动。 +_Avoid_: 线索(转化前)、商机(下游)、联系人(子实体) + +**销售负责人**: +客户的 `owner_user_id`,当前唯一持有该客户的销售。客户「我的客户」视图的第一层过滤依据(负责人 = 当前用户);抛公海 / 交割 / 分配时变更。协同人可见客户但不视为负责人。 +_Avoid_: 领取人(线索术语)、创建人、持有者 + +**销售部门**: +客户的 `owner_dept_id`,负责人所在部门的快照,随负责人变更而更新(非全生命周期不变,区别于线索的 `dept_id`)。是数据范围过滤的部门维度。 +_Avoid_: 归属部门、池归属部门、owner_dept + +**协同人**: +客户团队中非负责人的成员。协同人可见该客户(进「我的客户」),但不得归档 / 合并。抛公海时清空协同人;交割时不自动恢复原协同人(保留原有其他协同人)。 +_Avoid_: 团队成员(泛指)、共享人 + +**客户阶段**: +`customer_stage` 单字段(潜在 1 / 重潜 2 / 已成交 3),**只前进不回退、不可手动改**,由外部成功结果驱动:潜在 →(任一方案卡**提交并报备成功**)→ 重潜 →(任一项目首次中标赢单)→ 已成交。**本期两个跃迁均 seam 化不自动触发**(D20 改票 B):报备成功链路本期不可达(不用「已提交」凑合,避免造出错误的重潜状态),A5 项目模块未建;`CustomerStagePort.promote` 契约/只前进 CAS/幂等 全部建好,等对应链路就绪再由其调用。**本期客户阶段实际只有「潜在」**。 +_Avoid_: 客户状态、归属状态、客户类型 + +**档案状态**: +`archive_status` 单字段(有效 1 / 已归档 2),正交于归属与阶段(D19 去掉「已被合并」态,合并 Out-of-scope,A4-6 不做)。**已归档 = 删除语义(软删)**:默认排除于列表筛选、加 `archive_status` 筛选可查,可恢复(恢复回有效)。仅销售负责人可归档。**§5.3 拍板 A(一期只做归档)**:**不提供任何前端「删除客户」入口**——系统级 `deleted` 软删标志仅在合并时由系统置 1,不开放手动删除、无前端删除按钮;DB 不做物理删除。归档与合并是仅有的两种"移除"用户动作,且归档可恢复、合并本体 Out-of-scope。 +_Avoid_: 删除、禁用、作废 + +**客户公海**: +`owner_user_id` 为空 且 `archive_status=有效` 的客户的**逻辑视图**(无独立池实体,对称商机公海)。进入公海 = 清 owner + 协同人;领取 = 当前用户成为 owner。有进行中商机 / 项目的客户不得抛公海(项目侧留 seam)。 +_Avoid_: 公海池实体(那是线索规则域概念)、池 + +**客户总览**: +管理视角 workspace,`@DataScope` 覆盖组织范围的未删除、未进入公海客户(含有效 / 已归档两态;默认只列 `archive_status=有效`,已归档走筛选)。管理职务本身不扩大「我的客户」数据范围。 +_Avoid_: 全部客户、全局列表 + +**我的客户**: +销售视角 workspace,当前用户为销售负责人**或协同人** 且 `archive_status=有效` 且非公海的客户。已归档客户 = `archive_status=已归档` 的客户(负责人为当前用户),列表加筛选展示、**不做独立子页/独立 tab**(D19:删除了「已被合并」态、「已归档」用筛选条件承载)。 +_Avoid_: 私海(那是线索术语) + +**联系人**: +`customer_contact` 子实体,每条必属一个客户(`customer_id` NOT NULL),无孤立联系人。无独立 ACL,权限继承所属客户。电话查重=可配置软提示(跟随 crm-rule 客户查重设置「联系电话」开关,不建电话唯一索引,D26)、同客户内电话不重复为表单级校验;职务先存文本名 + 可选标准职务 id/类别/层级占位(二级联动图谱留后续)。 +_Avoid_: 线索联系人、决策人(那是客户字段) + +**人脉关系单位**: +客户下的关系单位名标签集(如「XX 集团」),是**纯文本标签**,非关联到 CRM 客户实体。 +_Avoid_: 关联客户、子客户(父客户关联是另一字段) + +**客户交割**: +销售因员工离职 / 岗位调动 / 区域调整,把本人名下客户批量交给直属销售总监,再由总监分配给具体销售。两段式:发起交接(owner → 总监,整批原子)+ 客户分配(owner → 销售)。同步变更关联商机 / 项目负责人(项目侧留 seam)。只允许离职/调动/调整的销售本人发起。 +_Avoid_: 分配(那是公海/日常分配动作)、转交 + +**超期未跟进提醒**: +客户规则族唯一规则(crm-rule 全局单例配置)。客户 `last_valid_follow_time`(新建 = create_time,每次跟进刷新)距今超过首次触发天数(默认 30)触发首次提醒,之后每隔二次提醒间隔(默认 7)重复提醒。只落待发通知 `customer_pending_notice`,不直接发送(平台通知能力 spin-out)。公海 / 已归档客户不参与(「已被合并」态已随 D19 取消)。 +_Avoid_: 超时回收(那是线索/商机公海回收)、逾期 diff --git a/crm-dict/.classpath b/crm-dict/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-dict/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-dict/.factorypath b/crm-dict/.factorypath new file mode 100644 index 0000000..f228cf0 --- /dev/null +++ b/crm-dict/.factorypath @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-dict/.project b/crm-dict/.project new file mode 100644 index 0000000..112ee90 --- /dev/null +++ b/crm-dict/.project @@ -0,0 +1,34 @@ + + + crm-dict + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974789 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-dict/.settings/org.eclipse.core.resources.prefs b/crm-dict/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..f9fe345 --- /dev/null +++ b/crm-dict/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/crm-dict/.settings/org.eclipse.jdt.apt.core.prefs b/crm-dict/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-dict/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-dict/.settings/org.eclipse.jdt.core.prefs b/crm-dict/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-dict/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-dict/.settings/org.eclipse.m2e.core.prefs b/crm-dict/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-dict/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/crm-file/.classpath b/crm-file/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-file/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-file/.factorypath b/crm-file/.factorypath new file mode 100644 index 0000000..c8f5a56 --- /dev/null +++ b/crm-file/.factorypath @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-file/.project b/crm-file/.project new file mode 100644 index 0000000..18014a3 --- /dev/null +++ b/crm-file/.project @@ -0,0 +1,34 @@ + + + crm-file + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974805 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-file/.settings/org.eclipse.core.resources.prefs b/crm-file/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..f9fe345 --- /dev/null +++ b/crm-file/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/crm-file/.settings/org.eclipse.jdt.apt.core.prefs b/crm-file/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-file/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-file/.settings/org.eclipse.jdt.core.prefs b/crm-file/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-file/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-file/.settings/org.eclipse.m2e.core.prefs b/crm-file/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-file/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/crm-lead/.classpath b/crm-lead/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-lead/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-lead/.factorypath b/crm-lead/.factorypath new file mode 100644 index 0000000..30b5320 --- /dev/null +++ b/crm-lead/.factorypath @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-lead/.project b/crm-lead/.project new file mode 100644 index 0000000..62aab29 --- /dev/null +++ b/crm-lead/.project @@ -0,0 +1,34 @@ + + + crm-lead + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974823 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-lead/.settings/org.eclipse.core.resources.prefs b/crm-lead/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..f9fe345 --- /dev/null +++ b/crm-lead/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/crm-lead/.settings/org.eclipse.jdt.apt.core.prefs b/crm-lead/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-lead/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-lead/.settings/org.eclipse.jdt.core.prefs b/crm-lead/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-lead/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-lead/.settings/org.eclipse.m2e.core.prefs b/crm-lead/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-lead/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/crm-opportunity/.classpath b/crm-opportunity/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-opportunity/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-opportunity/.factorypath b/crm-opportunity/.factorypath new file mode 100644 index 0000000..c32a28f --- /dev/null +++ b/crm-opportunity/.factorypath @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-opportunity/.project b/crm-opportunity/.project new file mode 100644 index 0000000..b7ed06f --- /dev/null +++ b/crm-opportunity/.project @@ -0,0 +1,34 @@ + + + crm-opportunity + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974839 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-opportunity/.settings/org.eclipse.core.resources.prefs b/crm-opportunity/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..f9fe345 --- /dev/null +++ b/crm-opportunity/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/crm-opportunity/.settings/org.eclipse.jdt.apt.core.prefs b/crm-opportunity/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-opportunity/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-opportunity/.settings/org.eclipse.jdt.core.prefs b/crm-opportunity/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-opportunity/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-opportunity/.settings/org.eclipse.m2e.core.prefs b/crm-opportunity/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-opportunity/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/Opportunity.java b/crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/Opportunity.java index 05edd30..b886e18 100644 --- a/crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/Opportunity.java +++ b/crm-opportunity/src/main/java/com/crm/opportunity/domain/entity/Opportunity.java @@ -65,12 +65,15 @@ public class Opportunity extends BaseEntity { @Column(columnDefinition = "varchar(64) not null comment '项目属地字典code'") private String localityType = ""; + // 【命名策略坑】partyA / partyAClear 里的孤立单大写字母 'A' 被 SpringPhysicalNamingStrategy + // 当成缩略语(同 URL/ID 处理),不插下划线,会建成 partya / partyaclear。 + // 必须用 @Column(name=...) 显式钉死物理列名。详见 AGENTS.md「命名策略:孤立单大写字母陷阱」。 @Comment("甲方是否明确 0/1") - @Column(columnDefinition = "tinyint not null default 0 comment '甲方是否明确'") + @Column(name = "party_a_clear", columnDefinition = "tinyint not null default 0 comment '甲方是否明确'") private Integer partyAClear = 0; @Comment("甲方名称(party_a_clear=1 时必填)") - @Column(columnDefinition = "varchar(200) comment '甲方名称'") + @Column(name = "party_a", columnDefinition = "varchar(200) comment '甲方名称'") private String partyA; @Comment("省份国标 code(GB/T 2260,6位)") diff --git a/crm-preference/.classpath b/crm-preference/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-preference/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-preference/.factorypath b/crm-preference/.factorypath new file mode 100644 index 0000000..541539a --- /dev/null +++ b/crm-preference/.factorypath @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-preference/.project b/crm-preference/.project new file mode 100644 index 0000000..dfed46a --- /dev/null +++ b/crm-preference/.project @@ -0,0 +1,34 @@ + + + crm-preference + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974854 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-preference/.settings/org.eclipse.core.resources.prefs b/crm-preference/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..f9fe345 --- /dev/null +++ b/crm-preference/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/crm-preference/.settings/org.eclipse.jdt.apt.core.prefs b/crm-preference/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-preference/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-preference/.settings/org.eclipse.jdt.core.prefs b/crm-preference/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-preference/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-preference/.settings/org.eclipse.m2e.core.prefs b/crm-preference/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-preference/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/crm-rule/.classpath b/crm-rule/.classpath new file mode 100644 index 0000000..df66b20 --- /dev/null +++ b/crm-rule/.classpath @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-rule/.factorypath b/crm-rule/.factorypath new file mode 100644 index 0000000..30b5320 --- /dev/null +++ b/crm-rule/.factorypath @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crm-rule/.project b/crm-rule/.project new file mode 100644 index 0000000..1f58d84 --- /dev/null +++ b/crm-rule/.project @@ -0,0 +1,34 @@ + + + crm-rule + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1788308974867 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/crm-rule/.settings/org.eclipse.core.resources.prefs b/crm-rule/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..839d647 --- /dev/null +++ b/crm-rule/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/crm-rule/.settings/org.eclipse.jdt.apt.core.prefs b/crm-rule/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/crm-rule/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations +org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations diff --git a/crm-rule/.settings/org.eclipse.jdt.core.prefs b/crm-rule/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1de9b43 --- /dev/null +++ b/crm-rule/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,20 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/crm-rule/.settings/org.eclipse.m2e.core.prefs b/crm-rule/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/crm-rule/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1