From ea212fa8b6be14964c7a5fdfe170e6c902da86d4 Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Mon, 17 Aug 2026 23:35:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AD=97=E5=85=B8=E7=A7=8D=E5=AD=90?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=E8=93=9D=E6=B9=96=E5=8E=9F=E5=9E=8B=E2=80=94?= =?UTF-8?q?=E2=80=94lead=5Fsource=20=E6=94=B9=E6=B8=A0=E9=81=93=2038=20?= =?UTF-8?q?=E9=A1=B9=E5=B9=B6=E6=96=B0=E5=A2=9E=20brand/product/scene?= =?UTF-8?q?=EF=BC=88frontend-integration=20=E7=A5=A8=E6=8D=AE=2002?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crm/dict/config/DictDataInitializer.java | 101 ++++++++++++++++-- .../dict/config/DictDataInitializerTest.java | 39 +++---- .../com/crm/lead/constant/LeadConstants.java | 10 +- .../java/com/crm/lead/domain/entity/Lead.java | 8 +- 4 files changed, 121 insertions(+), 37 deletions(-) diff --git a/crm-dict/src/main/java/com/crm/dict/config/DictDataInitializer.java b/crm-dict/src/main/java/com/crm/dict/config/DictDataInitializer.java index 39c3367..1d622f1 100644 --- a/crm-dict/src/main/java/com/crm/dict/config/DictDataInitializer.java +++ b/crm-dict/src/main/java/com/crm/dict/config/DictDataInitializer.java @@ -40,17 +40,51 @@ public class DictDataInitializer implements CommandLineRunner { private final DictGroupDefaultMapper dictGroupDefaultMapper; private final DictRefCountMapper dictRefCountMapper; - /*-------- 首批内置字典种子(Excel 替换口,见类注释)--------*/ + /*-------- 首批内置字典种子(Excel 替换口,见类注释) + * 渠道/品牌/需求产品/需求场景选项取自蓝湖原型 A2-1-2-2 新增线索表单(frontend-integration 票据 02); + * 原型渠道清单中的占位文本「列表项1」不入库。--------*/ private static final List SEED_GROUPS = List.of( - new GroupSeed("lead_source", "客户来源", 1, "客户首次触达渠道", + new GroupSeed("lead_source", "渠道", 1, "线索来源渠道(对齐蓝湖原型)", List.of( - new ItemSeed("ad", "广告投放", "ad", 1, null, false), - new ItemSeed("referral", "转介绍", "referral", 2, null, false), - new ItemSeed("exhibition", "展会活动", "exhibition", 3, null, false), - new ItemSeed("website", "官网咨询", "website", 4, null, false), - new ItemSeed("offline", "线下拜访", "offline", 5, null, false), - new ItemSeed("other", "其他", "other", 6, null, false))), + new ItemSeed("exhibition", "展会", "exhibition", 1, null, false), + new ItemSeed("digital_av", "数字音视", "digital_av", 2, null, false), + new ItemSeed("jd", "京东", "jd", 3, null, false), + new ItemSeed("netease_news", "网易新闻", "netease_news", 4, null, false), + new ItemSeed("tencent_sogou", "腾讯搜狗", "tencent_sogou", 5, null, false), + new ItemSeed("zhihu", "知乎", "zhihu", 6, null, false), + new ItemSeed("edu_procurement", "教育采购", "edu_procurement", 7, null, false), + new ItemSeed("baidu_20146", "百度20146", "baidu_20146", 8, null, false), + new ItemSeed("edu_equipment", "教育装备采购网", "edu_equipment", 9, null, false), + new ItemSeed("1688", "1688", "1688", 10, null, false), + new ItemSeed("accessory_cable", "配件线材", "accessory_cable", 11, null, false), + new ItemSeed("ifeng", "凤凰网", "ifeng", 12, null, false), + new ItemSeed("junqiao", "军桥", "junqiao", 13, null, false), + new ItemSeed("soft_article", "软文", "soft_article", 14, null, false), + new ItemSeed("baidu_broadcast", "百度广播", "baidu_broadcast", 15, null, false), + new ItemSeed("baidu_pro_sound", "百度专业扩声", "baidu_pro_sound", 16, null, false), + new ItemSeed("baidu_edu", "百度教育", "baidu_edu", 17, null, false), + new ItemSeed("search_360", "360搜索", "search_360", 18, null, false), + new ItemSeed("shunqi", "顺企网", "shunqi", 19, null, false), + new ItemSeed("security_net", "安防网", "security_net", 20, null, false), + new ItemSeed("huicong", "慧聪", "huicong", 21, null, false), + new ItemSeed("baidu_video_meeting", "百度视频会议", "baidu_video_meeting", 22, null, false), + new ItemSeed("wechat_moments", "微信朋友圈", "wechat_moments", 23, null, false), + new ItemSeed("message", "留言", "message", 24, null, false), + new ItemSeed("phone", "电话", "phone", 25, null, false), + new ItemSeed("kuaishou", "快手", "kuaishou", 26, null, false), + new ItemSeed("video_account", "视频号", "video_account", 27, null, false), + new ItemSeed("official_account", "公众号", "official_account", 28, null, false), + new ItemSeed("douyin", "抖音号", "douyin", 29, null, false), + new ItemSeed("xiaoke_ai", "销氪AI", "xiaoke_ai", 30, null, false), + new ItemSeed("baidu_meeting", "百度会议", "baidu_meeting", 31, null, false), + new ItemSeed("baiying_ai", "百应AI", "baiying_ai", 32, null, false), + new ItemSeed("xiaohongshu", "小红书", "xiaohongshu", 33, null, false), + new ItemSeed("baidu_lighting", "百度亮化", "baidu_lighting", 34, null, false), + new ItemSeed("short_video", "短视频", "short_video", 35, null, false), + new ItemSeed("baidu_aicaigou", "百度爱采购", "baidu_aicaigou", 36, null, false), + new ItemSeed("media", "媒体", "media", 37, null, false), + new ItemSeed("toutiao", "头条", "toutiao", 38, null, false))), new GroupSeed("customer_status", "客户状态", 2, "客户生命周期状态", List.of( new ItemSeed("potential", "潜在客户", "potential", 1, null, true), @@ -65,7 +99,56 @@ public class DictDataInitializer implements CommandLineRunner { new ItemSeed("education", "教育", "education", 5, null, false), new ItemSeed("retail", "零售", "retail", 6, null, false), new ItemSeed("real_estate", "房地产", "real_estate", 7, null, false), - new ItemSeed("other", "其他", "other", 8, null, false)))); + new ItemSeed("other", "其他", "other", 8, null, false))), + new GroupSeed("brand", "品牌", 4, "线索品牌(对齐蓝湖原型)", + List.of( + new ItemSeed("itc", "itc", "itc", 1, null, false), + new ItemSeed("shichang", "世畅", "shichang", 2, null, false), + new ItemSeed("lisheng", "丽声", "lisheng", 3, null, false), + new ItemSeed("crx", "CRX", "crx", 4, null, false), + new ItemSeed("three_a", "3A", "three_a", 5, null, false), + new ItemSeed("beihangxing", "北航星", "beihangxing", 6, null, false), + new ItemSeed("tekong", "特控", "tekong", 7, null, false), + new ItemSeed("sooann", "SOOANN", "sooann", 8, null, false), + new ItemSeed("jinye_chengxing", "金业诚兴", "jinye_chengxing", 9, null, false))), + new GroupSeed("product", "需求产品", 5, "线索需求产品(对齐蓝湖原型)", + List.of( + new ItemSeed("led_display", "LED显示屏", "led_display", 1, null, false), + new ItemSeed("conference_system", "会议系统", "conference_system", 2, null, false), + new ItemSeed("paperless_meeting", "无纸化会议", "paperless_meeting", 3, null, false), + new ItemSeed("stage_lighting", "舞台灯光", "stage_lighting", 4, null, false), + new ItemSeed("tourism_lighting", "文旅亮化", "tourism_lighting", 5, null, false), + new ItemSeed("record_broadcast", "录播系统", "record_broadcast", 6, null, false), + new ItemSeed("broadcast_system", "广播系统", "broadcast_system", 7, null, false), + new ItemSeed("conference_machine", "会议一体机", "conference_machine", 8, null, false), + new ItemSeed("video_conference", "视频会议", "video_conference", 9, null, false), + new ItemSeed("sound_system", "扩声系统", "sound_system", 10, null, false), + new ItemSeed("teaching_system", "教学系统", "teaching_system", 11, null, false), + new ItemSeed("command_center", "指挥中心", "command_center", 12, null, false), + new ItemSeed("medical_system", "医疗系统", "medical_system", 13, null, false), + new ItemSeed("info_distribution", "信发系统", "info_distribution", 14, null, false), + new ItemSeed("software_platform", "软件平台", "software_platform", 15, null, false), + new ItemSeed("security_surveillance", "安防监控", "security_surveillance", 16, null, false))), + new GroupSeed("scene", "需求场景", 6, "线索需求场景(对齐蓝湖原型)", + List.of( + new ItemSeed("meeting_room", "会议室", "meeting_room", 1, null, false), + new ItemSeed("auditorium", "报告厅", "auditorium", 2, null, false), + new ItemSeed("command_center", "指挥中心", "command_center", 3, null, false), + new ItemSeed("sports_venue", "文体场馆", "sports_venue", 4, null, false), + new ItemSeed("theater", "剧场剧院", "theater", 5, null, false), + new ItemSeed("bar_ktv", "酒吧KTV", "bar_ktv", 6, null, false), + new ItemSeed("higher_vocational", "高职教", "higher_vocational", 7, null, false), + new ItemSeed("basic_edu", "普教", "basic_edu", 8, null, false), + new ItemSeed("justice", "公检法司", "justice", 9, null, false), + new ItemSeed("medical", "医疗", "medical", 10, null, false), + new ItemSeed("military", "国防部队", "military", 11, null, false), + new ItemSeed("government", "政府单位", "government", 12, null, false), + new ItemSeed("transportation", "交通", "transportation", 13, null, false), + new ItemSeed("finance", "金融", "finance", 14, null, false), + new ItemSeed("energy", "能源", "energy", 15, null, false), + new ItemSeed("park", "园区", "park", 16, null, false), + new ItemSeed("cultural_tourism", "文旅", "cultural_tourism", 17, null, false), + new ItemSeed("exhibition_hall", "展厅", "exhibition_hall", 18, null, false)))); @Override public void run(String... args) { diff --git a/crm-dict/src/test/java/com/crm/dict/config/DictDataInitializerTest.java b/crm-dict/src/test/java/com/crm/dict/config/DictDataInitializerTest.java index 0c51fb1..7a34dab 100644 --- a/crm-dict/src/test/java/com/crm/dict/config/DictDataInitializerTest.java +++ b/crm-dict/src/test/java/com/crm/dict/config/DictDataInitializerTest.java @@ -31,23 +31,24 @@ class DictDataInitializerTest extends AbstractDictH2Test { } @Test - @DisplayName("首装:3 个内置分组 + 17 个内置项全部建库,ref_count 预建,种子默认项生效") + @DisplayName("首装:6 个内置分组 + 92 个内置项全部建库,ref_count 预建,种子默认项生效") void firstRun_createsBuiltinDictsWithDefaults() { initializer.run(); List groups = dictGroupMapper.selectList(null); - assertThat(groups).hasSize(3); + assertThat(groups).hasSize(6); assertThat(groups).extracting(DictGroup::getCode) - .containsExactlyInAnyOrder("lead_source", "customer_status", "industry"); + .containsExactlyInAnyOrder("lead_source", "customer_status", "industry", + "brand", "product", "scene"); assertThat(groups).allMatch(g -> Boolean.TRUE.equals(g.getBuiltin())); assertThat(groups).allMatch(g -> g.getStatus() == DictConstants.STATUS_ENABLED); List items = dictItemMapper.selectList(null); - assertThat(items).hasSize(17); // 6 + 3 + 8 + assertThat(items).hasSize(92); // 38 渠道 + 3 客户状态 + 8 行业 + 9 品牌 + 16 需求产品 + 18 需求场景 assertThat(items).allMatch(i -> Boolean.TRUE.equals(i.getBuiltin())); // 引用计数行预建(与新建接口同口径) - assertThat(dictRefCountMapper.selectCount(null)).isEqualTo(17); + assertThat(dictRefCountMapper.selectCount(null)).isEqualTo(92); DictGroup statusGroup = groups.stream() .filter(g -> g.getCode().equals("customer_status")).findFirst().orElseThrow(); DictItem potential = dictItemMapper.selectList(null).stream() @@ -68,9 +69,9 @@ class DictDataInitializerTest extends AbstractDictH2Test { initializer.run(); initializer.run(); - assertThat(dictGroupMapper.selectCount(null)).isEqualTo(3); - assertThat(dictItemMapper.selectCount(null)).isEqualTo(17); - assertThat(dictRefCountMapper.selectCount(null)).isEqualTo(17); + assertThat(dictGroupMapper.selectCount(null)).isEqualTo(6); + assertThat(dictItemMapper.selectCount(null)).isEqualTo(92); + assertThat(dictRefCountMapper.selectCount(null)).isEqualTo(92); assertThat(dictGroupDefaultMapper.selectCount(null)).isEqualTo(1); } @@ -82,12 +83,12 @@ class DictDataInitializerTest extends AbstractDictH2Test { // 管理员本地化:改内置项名 + 手建用户项(builtin=false) DictGroup leadSource = dictGroupMapper.selectList(null).stream() .filter(g -> g.getCode().equals("lead_source")).findFirst().orElseThrow(); - DictItem adItem = dictItemMapper.selectList(null).stream() - .filter(i -> i.getGroupId().equals(leadSource.getId()) && i.getCode().equals("ad")) + DictItem exhibitionItem = dictItemMapper.selectList(null).stream() + .filter(i -> i.getGroupId().equals(leadSource.getId()) && i.getCode().equals("exhibition")) .findFirst().orElseThrow(); - adItem.setName("广告投放-本地改名"); - adItem.setValue("LOCAL_VALUE"); // 管理员本地化改值 - dictItemMapper.updateById(adItem); + exhibitionItem.setName("展会-本地改名"); + exhibitionItem.setValue("LOCAL_VALUE"); // 管理员本地化改值 + dictItemMapper.updateById(exhibitionItem); DictItem userItem = new DictItem(); userItem.setGroupId(leadSource.getId()); @@ -102,8 +103,8 @@ class DictDataInitializerTest extends AbstractDictH2Test { initializer.run(); // 内置项名被种子刷新(种子的"改"语义),但字典值属业务契约不被覆盖 - DictItem refreshed = dictItemMapper.selectById(adItem.getId()); - assertThat(refreshed.getName()).isEqualTo("广告投放"); + DictItem refreshed = dictItemMapper.selectById(exhibitionItem.getId()); + assertThat(refreshed.getName()).isEqualTo("展会"); assertThat(refreshed.getValue()).isEqualTo("LOCAL_VALUE"); // 用户项原样保留:名称/值/内置标记均未动 DictItem untouched = dictItemMapper.selectList(null).stream() @@ -193,12 +194,12 @@ class DictDataInitializerTest extends AbstractDictH2Test { assertThat(dictGroupMapper.selectById(userGroup.getId()).getBuiltin()).isFalse(); assertThat(dictItemMapper.selectList(null).stream() .filter(i -> i.getGroupId().equals(userGroup.getId()))).isEmpty(); - // 种子其他分组不受影响(继续正常导入):customer_status + industry - assertThat(dictGroupMapper.selectCount(null)).isEqualTo(3); // lead_source(用户) + customer_status + industry + // 种子其他分组不受影响(继续正常导入):customer_status + industry + brand + product + scene + assertThat(dictGroupMapper.selectCount(null)).isEqualTo(6); // lead_source(用户) + 其余 5 个内置分组 assertThat(dictGroupMapper.selectList(null).stream() .filter(g -> g.getCode().equals("industry"))).hasSize(1); - assertThat(dictItemMapper.selectCount(null)).isEqualTo(11); // customer_status 3 + industry 8 - assertThat(dictRefCountMapper.selectCount(null)).isEqualTo(11); + assertThat(dictItemMapper.selectCount(null)).isEqualTo(54); // 92 - 渠道 38(用户分组整组跳过) + assertThat(dictRefCountMapper.selectCount(null)).isEqualTo(54); assertThat(dictGroupDefaultMapper.selectCount(null)).isEqualTo(1); // customer_status 默认项 } diff --git a/crm-lead/src/main/java/com/crm/lead/constant/LeadConstants.java b/crm-lead/src/main/java/com/crm/lead/constant/LeadConstants.java index 5382996..56e7ab2 100644 --- a/crm-lead/src/main/java/com/crm/lead/constant/LeadConstants.java +++ b/crm-lead/src/main/java/com/crm/lead/constant/LeadConstants.java @@ -26,12 +26,12 @@ public interface LeadConstants { int FEEDBACK_RECORD_DRAFT = 1; // 草稿 int FEEDBACK_RECORD_SUBMITTED = 2; // 已提交 - /*-------- 字典分组 code(对齐 crm-dict)---------*/ + /*-------- 字典分组 code(对齐 crm-dict 内置种子,frontend-integration 票据 02)---------*/ - String DICT_GROUP_CHANNEL = "lead.channel"; - String DICT_GROUP_BRAND = "lead.brand"; - String DICT_GROUP_PRODUCT = "lead.product"; - String DICT_GROUP_SCENE = "lead.scene"; + String DICT_GROUP_LEAD_SOURCE = "lead_source"; + String DICT_GROUP_BRAND = "brand"; + String DICT_GROUP_PRODUCT = "product"; + String DICT_GROUP_SCENE = "scene"; /*-------- 列偏好 scopeKey(预留,尚未接线)--------- * 线索四视图列偏好的作用范围键,每菜单独立(参 crm-preference/CONTEXT.md 「scope」)。 diff --git a/crm-lead/src/main/java/com/crm/lead/domain/entity/Lead.java b/crm-lead/src/main/java/com/crm/lead/domain/entity/Lead.java index 3e0f567..1564343 100644 --- a/crm-lead/src/main/java/com/crm/lead/domain/entity/Lead.java +++ b/crm-lead/src/main/java/com/crm/lead/domain/entity/Lead.java @@ -64,19 +64,19 @@ public class Lead extends BaseEntity { // -------- 业务来源(字典 code)-------- - @Comment("渠道字典 code(lead.channel)") + @Comment("渠道字典 code(lead_source)") @Column(columnDefinition = "varchar(64) not null comment '渠道字典code'") private String channelCode; - @Comment("品牌字典 code(lead.brand)") + @Comment("品牌字典 code(brand)") @Column(columnDefinition = "varchar(64) not null comment '品牌字典code'") private String brandCode; - @Comment("需求产品字典 code(lead.product)") + @Comment("需求产品字典 code(product)") @Column(columnDefinition = "varchar(64) not null comment '需求产品字典code'") private String productCode; - @Comment("需求场景字典 code(lead.scene)") + @Comment("需求场景字典 code(scene)") @Column(columnDefinition = "varchar(64) comment '需求场景字典code'") private String sceneCode;