-- ============================================================================ -- yxe 库结构与数据修正 —— 全量累计脚本 -- ============================================================================ -- 用途:yxe 业务库(yaoxiaoer)的建表 / 加列 / 历史数据修正,汇总为一个可整 -- 文件重复执行的脚本,供新环境初始化与生产库复核执行。 -- 日期:2026-10-11 -- 背景:原先这些变更散落在 yxe-java/ 根目录的 12 个独立 SQL 文件中,同一张表 -- 的建表与加列分散在多处,后续维护容易只改一处;本次合并为单文件, -- 原文件内容完整保留在本文件对应分节中。 -- -- 由以下 12 个文件合并而来(合并后原文件删除,历史可经 git log 追溯): -- yxe_self_report.sql -- yxe_self_report_add_activity_time.sql -- yxe_self_report_add_reject_reason.sql -- yxe_visit_report.sql (2026-10-11 新增) -- yxe_visit_list_add_visit_form_fields_2026-10-11.sql(2026-10-11 新增) -- yxe_user_task_add_income.sql -- yxe_chronic_disease_product.sql -- sys_user_add_user_group.sql -- transport_maintenance_plan.sql -- transport_maintenance_record.sql -- fix_self_report_chain_id_placeholder_2026-10-11.sql(2026-10-11 数据修正) -- fix_task_reward_null_npe_2026-10-11.sql (2026-10-11 数据修正) -- -- 性质:整体幂等,可整文件重复执行。 -- 建表用 CREATE TABLE IF NOT EXISTS;加列统一走 information_schema 判断后 -- 再 ADD(原先部分 ALTER 是裸语句,重复执行或与建表语句叠加时会报 -- Duplicate column name,本次一并改为条件执行);数据修正限定条件, -- 重复执行不改变结果。 -- -- 执行顺序:按本文件自上而下执行即可。分节顺序已考虑依赖: -- 先建表/加列(第 1–8 节),最后做历史数据修正(第 9–10 节)。 -- 注意第 1 节的建表已内联 activity_time / reject_reason, -- 第 2–3 节的加列会自动跳过(已存在)。 -- -- 配套代码(各变更的详细说明见对应分节): -- yxe-java YxeSelfReport* / YxeVisitReport* / YxeVisitList* / YxeTask* / -- YxeUserTask* / YxeChronicDiseaseProduct* 及对应 Controller -- yxe-mp packageUser/self-report/*、pages/visit/index、pages/audit/index -- yxe-admin src/api/yxe/*、src/views/yxe/* -- ============================================================================ -- ############################################################################ -- 1. yxe_self_report —— 自主填报(驻店填报)建表 -- ############################################################################ -- 用途:自主填报(2026-10 起对外称「驻店填报」)主表 -- 背景:自主填报功能的原始建表语句;后续新增的 activity_time / reject_reason -- 两列已内联到本建表语句中(见下方列定义),因此第 2–3 节的加列在 -- 新库上会走「已存在则跳过」分支。 -- 影响:新库建表 0 行;已存在的库不做任何改动。 -- 性质:幂等(CREATE TABLE IF NOT EXISTS)。 -- 配套代码:YxeSelfReport 实体与 YxeSelfReportController 全套。 -- 执行前核对(应为 0,即表不存在) SELECT COUNT(*) AS table_exists FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_self_report'; CREATE TABLE IF NOT EXISTS `yxe_self_report` ( `id` int NOT NULL AUTO_INCREMENT, `user_id` int DEFAULT NULL COMMENT '提交人ID', `merchant_id` int DEFAULT NULL COMMENT '门店ID', `merchant_name` varchar(255) DEFAULT NULL COMMENT '门店名称', `chain_id` varchar(64) DEFAULT NULL COMMENT '连锁ID', `chain_name` varchar(255) DEFAULT NULL COMMENT '连锁名称', `date` varchar(32) DEFAULT NULL COMMENT '填报日期', `activity_time` varchar(32) DEFAULT NULL COMMENT '活动时间', `reward` decimal(10,2) DEFAULT NULL COMMENT '奖金金额', `image` longtext COMMENT '全部图片集合', `clock_photos` longtext COMMENT '上下班打卡图', `booth_photos` longtext COMMENT '门头摆台照', `sales_groups` longtext COMMENT '销量填写JSON', `receipt_photo` longtext COMMENT '小票图', `summary` longtext COMMENT '活动小结', `rating` varchar(32) DEFAULT NULL COMMENT '门店评分', `lat` varchar(64) DEFAULT NULL COMMENT '纬度', `lng` varchar(64) DEFAULT NULL COMMENT '经度', `location` varchar(500) DEFAULT NULL COMMENT '定位地址', `sort_number` int DEFAULT 0 COMMENT '排序', `comments` varchar(500) DEFAULT NULL COMMENT '备注', `status` tinyint DEFAULT 0 COMMENT '状态:0待审核,1通过,2拒绝', `reject_reason` varchar(500) DEFAULT NULL COMMENT '驳回原因', `deleted` tinyint DEFAULT 0 COMMENT '逻辑删除', `tenant_id` int DEFAULT 10325 COMMENT '租户ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_yxe_self_report_user_id` (`user_id`), KEY `idx_yxe_self_report_merchant_id` (`merchant_id`), KEY `idx_yxe_self_report_status` (`status`), KEY `idx_yxe_self_report_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自主填报'; -- 执行后核对(应返回 1 行,且列/注释符合预期) SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT, ORDINAL_POSITION FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_self_report' ORDER BY ORDINAL_POSITION; -- ############################################################################ -- 2. yxe_self_report —— 新增 activity_time(活动时间) -- ############################################################################ -- 用途:自主填报新增「活动时间」 -- 背景:申报活动时间需要与填报日期(date)区分开,单独一列留存。 -- 注意:该列已内联进第 1 节的建表语句,本节的加列在新库上会跳过, -- 保留此节是为兼容「先有旧表、后加列」的存量库。 -- 影响:ALTER 本身 0 行;存量数据该列为 NULL,不做回填。 -- 性质:幂等(information_schema 判断列是否存在后 ADD)。 -- 配套代码:YxeSelfReport.activityTime、yxe-mp create.vue 活动日期选择器。 -- 执行前核对(应为 0,即列不存在) SELECT COUNT(*) AS col_exists FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_self_report' AND COLUMN_NAME = 'activity_time'; SET @db := DATABASE(); SET @sql := IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yxe_self_report' AND COLUMN_NAME = 'activity_time') = 0, 'ALTER TABLE `yxe_self_report` ADD COLUMN `activity_time` varchar(32) DEFAULT NULL COMMENT ''活动时间'' AFTER `date`', 'SELECT ''yxe_self_report.activity_time already exists'' AS skipped' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- ############################################################################ -- 3. yxe_self_report —— 新增 reject_reason(驳回原因) -- ############################################################################ -- 用途:自主填报新增「驳回原因」 -- 背景:审核端驳回后需要把原因回传给提交人,并在重新提交时展示。 -- 注意:该列已内联进第 1 节的建表语句,本节同样是为存量库兼容保留。 -- 影响:ALTER 本身 0 行;存量数据该列为 NULL。 -- 性质:幂等(information_schema 判断列是否存在后 ADD)。 -- 配套代码:YxeSelfReport.rejectReason、YxeSelfReportController#resubmit。 -- 执行前核对(应为 0,即列不存在) SELECT COUNT(*) AS col_exists FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_self_report' AND COLUMN_NAME = 'reject_reason'; SET @sql := IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yxe_self_report' AND COLUMN_NAME = 'reject_reason') = 0, 'ALTER TABLE `yxe_self_report` ADD COLUMN `reject_reason` varchar(500) DEFAULT NULL COMMENT ''驳回原因'' AFTER `status`', 'SELECT ''yxe_self_report.reject_reason already exists'' AS skipped' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 执行后核对(第 2–3 节合计应为 2 行) SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT, ORDINAL_POSITION FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_self_report' AND COLUMN_NAME IN ('activity_time', 'reject_reason') ORDER BY ORDINAL_POSITION; -- ############################################################################ -- 4. yxe_visit_report —— 跑店填报建表 -- ############################################################################ -- 用途:新增 yxe_visit_report 表(跑店填报) -- 背景:甲方要求把「自主填报」拆成「驻店填报 / 跑店填报」两种类型: -- 1) 原自主填报页改为「驻店填报」,数据仍存 yxe_self_report,不动; -- 2) 新增「跑店填报」,填报内容为「巡店列表(多门店,每条含门店/定位/打卡照片) -- + 活动小结」。 -- 巡店列表是「一次填报多家门店」的数组结构,yxe_self_report 既没有多门店 -- 字段,也没有区分驻店/跑店类型的字段;本次选择新建独立表,避免污染既有 -- 历史数据与既有审核/导出逻辑。 -- 影响:新建空表,0 行业务数据;不改动 yxe_self_report 任何结构。 -- 性质:幂等(CREATE TABLE IF NOT EXISTS)。 -- 配套代码: -- yxe-java YxeVisitReport.java / YxeVisitReportParam.java / YxeVisitReportMapper.java -- / YxeVisitReportMapper.xml / YxeVisitReportService(+Impl) -- / YxeVisitReportController.java -- yxe-mp packageUser/self-report/select.vue(选择填报类型) -- packageUser/self-report/visit.vue(跑店填报) -- packageUser/self-report/records.vue(填报记录加类型切换) -- pages/audit/index.vue(药审核加跑店填报类型) -- yxe-admin src/api/yxe/yxeVisitReport/*、src/views/yxe/yxeSelfReport/*(类型筛选与跑店详情) -- 执行前核对(应为 0,即表不存在) SELECT COUNT(*) AS table_exists FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_visit_report'; -- 变更:建表(已存在则跳过) CREATE TABLE IF NOT EXISTS `yxe_visit_report` ( `id` int NOT NULL AUTO_INCREMENT, `user_id` int DEFAULT NULL COMMENT '提交人ID', `store_count` int DEFAULT 0 COMMENT '巡店门店数', `store_list` longtext COMMENT '巡店列表JSON(门店/定位/打卡照片)', `image` longtext COMMENT '全部图片集合', `summary` longtext COMMENT '活动小结', `reward` decimal(10,2) DEFAULT NULL COMMENT '奖金金额(审核时填写)', `lat` varchar(64) DEFAULT NULL COMMENT '纬度(整单,取首条门店)', `lng` varchar(64) DEFAULT NULL COMMENT '经度(整单,取首条门店)', `location` varchar(500) DEFAULT NULL COMMENT '定位地址(整单,取首条门店)', `sort_number` int DEFAULT 0 COMMENT '排序', `comments` varchar(500) DEFAULT NULL COMMENT '备注', `status` tinyint DEFAULT 0 COMMENT '状态:0待审核,1通过,2拒绝', `reject_reason` varchar(500) DEFAULT NULL COMMENT '驳回原因', `deleted` tinyint DEFAULT 0 COMMENT '逻辑删除', `tenant_id` int DEFAULT 10325 COMMENT '租户ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_yxe_visit_report_user_id` (`user_id`), KEY `idx_yxe_visit_report_status` (`status`), KEY `idx_yxe_visit_report_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跑店填报'; -- 执行后核对(应返回 18 行,且列/注释符合预期) SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT, ORDINAL_POSITION FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_visit_report' ORDER BY ORDINAL_POSITION; -- ############################################################################ -- 5. yxe_visit_list —— 新增客户拜访表单字段 -- ############################################################################ -- 用途:yxe_visit_list 表新增客户拜访表单字段(拜访对象类型、拜访日期、提交人姓名) -- 背景:小程序 pages/visit/index 按「客户拜访」新原型重构为多卡片表单: -- 1) 新增「拜访对象 - 类型」(地点 / 门店经理 / 其他); -- 2) 新增「拜访信息 - 拜访日期」,默认当天; -- 3) 新增「我的信息 - 姓名」,进页面自动带出账号 real_name,仍允许修改, -- 因此与 sys_user.real_name 可能不一致,需要独立字段留痕。 -- 三项均为本次新增,历史记录该字段为空(前端展示为 "-")。 -- 影响:ALTER 语句本身 0 行;已有历史数据新增列为 NULL,不做回填。 -- 性质:幂等(通过 information_schema 判断列是否存在后再 ADD)。 -- 配套代码: -- yxe-java YxeVisitList.java / YxeVisitListParam.java / YxeVisitListMapper.xml -- / YxeVisitListController.java(导出表头) -- yxe-mp pages/visit/index.vue -- yxe-admin src/api/yxe/yxeVisitList/model、列表列、详情、编辑弹窗 -- 备注:本列注释在源码中已由「门店/门店经理/其他」改为「地点/门店经理/其他」。 -- 对**已存在该列**的库(列已建、注释仍是旧值),本节按幂等设计跳过, -- 不会修正注释;如需同步注释,另行执行: -- ALTER TABLE `yxe_visit_list` MODIFY COLUMN `visit_type` varchar(16) -- DEFAULT NULL COMMENT '拜访对象类型: 地点/门店经理/其他'; -- 新库建列时直接用下面语句,注释即为新值。 -- 执行前核对(应为 0 行,即三列均不存在) SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_visit_list' AND COLUMN_NAME IN ('visit_type', 'visit_date', 'submitter_name'); -- 变更:按列分别判断,已存在则跳过 SET @db := DATABASE(); SET @sql := IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yxe_visit_list' AND COLUMN_NAME = 'visit_type') = 0, 'ALTER TABLE `yxe_visit_list` ADD COLUMN `visit_type` varchar(16) DEFAULT NULL COMMENT ''拜访对象类型: 地点/门店经理/其他'' AFTER `name`', 'SELECT ''visit_type already exists'' AS skipped' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yxe_visit_list' AND COLUMN_NAME = 'visit_date') = 0, 'ALTER TABLE `yxe_visit_list` ADD COLUMN `visit_date` varchar(32) DEFAULT NULL COMMENT ''拜访日期'' AFTER `visit_type`', 'SELECT ''visit_date already exists'' AS skipped' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql := IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yxe_visit_list' AND COLUMN_NAME = 'submitter_name') = 0, 'ALTER TABLE `yxe_visit_list` ADD COLUMN `submitter_name` varchar(64) DEFAULT NULL COMMENT ''提交人姓名(拜访人自填)'' AFTER `visit_date`', 'SELECT ''submitter_name already exists'' AS skipped' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 执行后核对(应为 3 行,且类型/注释符合预期) SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT, ORDINAL_POSITION FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_visit_list' AND COLUMN_NAME IN ('visit_type', 'visit_date', 'submitter_name') ORDER BY ORDINAL_POSITION; -- ############################################################################ -- 6. yxe_user_task —— 新增 income(任务收入) -- ############################################################################ -- 用途:用户任务新增「任务收入」 -- 背景:任务完成后的收入需要单独留存,便于业绩统计与提成核算。 -- 影响:ALTER 本身 0 行;存量数据该列为 NULL,不做回填。 -- 性质:幂等(information_schema 判断列是否存在后 ADD)。 -- 配套代码:YxeUserTask.income、yxe-mp pages/bonus/index。 -- 执行前核对(应为 0,即列不存在) SELECT COUNT(*) AS col_exists FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_user_task' AND COLUMN_NAME = 'income'; SET @db := DATABASE(); SET @sql := IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yxe_user_task' AND COLUMN_NAME = 'income') = 0, 'ALTER TABLE `yxe_user_task` ADD COLUMN `income` decimal(10, 2) NULL DEFAULT NULL COMMENT ''任务收入'' AFTER `chain`', 'SELECT ''yxe_user_task.income already exists'' AS skipped' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 执行后核对(应为 1 行) SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT, ORDINAL_POSITION FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'yxe_user_task' AND COLUMN_NAME = 'income'; -- ############################################################################ -- 7. sys_user —— 新增 user_group(用户分组) -- ############################################################################ -- 用途:系统用户新增「用户分组」 -- 背景:审核与导出需要按用户分组筛选(yxe_self_report / yxe_visit_report 的 -- userGroup 查询条件即走本列)。 -- 影响:ALTER 本身 0 行;存量用户该列为 NULL。 -- 性质:幂等(information_schema 判断列是否存在后 ADD)。 -- 配套代码:YxeSelfReportMapper.xml / YxeVisitReportMapper.xml 的 userGroup 条件、 -- yxe-admin 自主填报列表「用户分组」筛选项。 -- 执行前核对(应为 0,即列不存在) SELECT COUNT(*) AS col_exists FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_user' AND COLUMN_NAME = 'user_group'; SET @db := DATABASE(); SET @sql := IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'sys_user' AND COLUMN_NAME = 'user_group') = 0, 'ALTER TABLE sys_user ADD COLUMN user_group VARCHAR(50) NULL COMMENT ''用户分组'' AFTER user_level', 'SELECT ''sys_user.user_group already exists'' AS skipped' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 执行后核对(应为 1 行) SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT, ORDINAL_POSITION FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_user' AND COLUMN_NAME = 'user_group'; -- ############################################################################ -- 8. 其余建表:慢病产品 / 运输保养 / 运输维修 -- ############################################################################ -- === 8.1 yxe_chronic_disease_product —— 慢病产品管理 ========================= -- 用途:慢病产品基础资料(连锁 / 产品名 / 规格粒数 / 每日用量 / 每盒天数等) -- 影响:新库建表 0 行。 -- 性质:幂等(CREATE TABLE IF NOT EXISTS)。 CREATE TABLE IF NOT EXISTS `yxe_chronic_disease_product` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', `chain` varchar(100) NOT NULL COMMENT '连锁', `product_name` varchar(100) NOT NULL COMMENT '产品名', `spec_count` decimal(10,2) NOT NULL COMMENT '规格粒/片', `daily_usage_count` decimal(10,2) NOT NULL COMMENT '说明书每天使用/次', `dosage_per_time` decimal(10,2) NOT NULL COMMENT '每次几粒/片', `box_usage_days` decimal(10,2) DEFAULT NULL COMMENT '1盒服用几天', `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态, 0禁用, 1启用', `deleted` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除, 0否, 1是', `tenant_id` int DEFAULT NULL COMMENT '租户id', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), KEY `idx_ycdp_product_name` (`product_name`), KEY `idx_ycdp_chain` (`chain`), KEY `idx_ycdp_deleted` (`deleted`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='慢病产品管理'; -- === 8.2 transport_maintenance_plan —— 保养记录 ============================ -- 用途:车辆/船舶保养记录 -- 影响:新库建表 0 行。 -- 性质:幂等(CREATE TABLE IF NOT EXISTS)。 CREATE TABLE IF NOT EXISTS `transport_maintenance_plan` ( `maintenance_plan_id` int NOT NULL AUTO_INCREMENT COMMENT '保养记录ID', `vehicle_type` varchar(10) NOT NULL COMMENT '车船类型: 车辆, 船舶', `vehicle_no` varchar(30) NOT NULL COMMENT '车牌号/船号', `maintainer` varchar(20) DEFAULT NULL COMMENT '保养人', `maintenance_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '保养时间', `mileage` decimal(12,2) DEFAULT NULL COMMENT '里程/航程数', `mileage_unit` varchar(10) DEFAULT NULL COMMENT '里程/航程单位: 公里, 海里', `maintenance_item` varchar(100) DEFAULT NULL COMMENT '保养项目', `cost` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '费用', `store_name` varchar(50) DEFAULT NULL COMMENT '店名', `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `address` varchar(100) DEFAULT NULL COMMENT '地址', `next_maintenance_time` datetime DEFAULT NULL COMMENT '下次保养时间', `next_maintenance_mileage` decimal(12,2) DEFAULT NULL COMMENT '下次保养里程/航程', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `attachments` longtext COMMENT '附件, JSON或逗号分隔文件地址', `sort_number` int NOT NULL DEFAULT 0 COMMENT '排序(数字越小越靠前)', `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态, 0正常, 1禁用', `deleted` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除, 0否, 1是', `user_id` int DEFAULT NULL COMMENT '创建人ID', `organization_id` int DEFAULT NULL COMMENT '组织ID', `tenant_id` int DEFAULT 10325 COMMENT '租户ID', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`maintenance_plan_id`), KEY `idx_tmp_vehicle_type` (`vehicle_type`), KEY `idx_tmp_vehicle_no` (`vehicle_no`), KEY `idx_tmp_maintenance_time` (`maintenance_time`), KEY `idx_tmp_next_maintenance_time` (`next_maintenance_time`), KEY `idx_tmp_organization_id` (`organization_id`), KEY `idx_tmp_tenant_id` (`tenant_id`), KEY `idx_tmp_deleted` (`deleted`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='保养记录'; -- === 8.3 transport_maintenance_record —— 维修记录 ========================== -- 用途:车辆/船舶维修记录 -- 影响:新库建表 0 行。 -- 性质:幂等(CREATE TABLE IF NOT EXISTS)。 CREATE TABLE IF NOT EXISTS `transport_maintenance_record` ( `maintenance_record_id` int NOT NULL AUTO_INCREMENT COMMENT '维修记录ID', `vehicle_type` varchar(10) NOT NULL COMMENT '车船类型: 车辆, 船舶', `vehicle_no` varchar(30) NOT NULL COMMENT '车牌号/船号', `maintainer` varchar(20) DEFAULT NULL COMMENT '维修人', `maintenance_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '维修时间', `location` varchar(100) DEFAULT NULL COMMENT '维修位置', `replaced_part` varchar(200) DEFAULT NULL COMMENT '更换零件', `cost` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '费用', `company` varchar(50) DEFAULT NULL COMMENT '维修单位', `contact` varchar(20) DEFAULT NULL COMMENT '联系方式', `address` varchar(100) DEFAULT NULL COMMENT '地址', `factory_time` datetime DEFAULT NULL COMMENT '出厂时间', `mileage` decimal(12,2) DEFAULT NULL COMMENT '里程/航程数', `mileage_unit` varchar(10) DEFAULT NULL COMMENT '里程/航程单位: 公里, 海里', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `attachments` longtext COMMENT '附件, JSON或逗号分隔文件地址', `sort_number` int NOT NULL DEFAULT 0 COMMENT '排序(数字越小越靠前)', `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态, 0正常, 1禁用', `deleted` tinyint NOT NULL DEFAULT 0 COMMENT '是否删除, 0否, 1是', `user_id` int DEFAULT NULL COMMENT '创建人ID', `organization_id` int DEFAULT NULL COMMENT '组织ID', `tenant_id` int DEFAULT 10325 COMMENT '租户ID', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`maintenance_record_id`), KEY `idx_tmr_vehicle_type` (`vehicle_type`), KEY `idx_tmr_vehicle_no` (`vehicle_no`), KEY `idx_tmr_maintenance_time` (`maintenance_time`), KEY `idx_tmr_factory_time` (`factory_time`), KEY `idx_tmr_organization_id` (`organization_id`), KEY `idx_tmr_tenant_id` (`tenant_id`), KEY `idx_tmr_deleted` (`deleted`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='维修记录'; -- 执行后核对(三张表应各返回 1 行) SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN ('yxe_chronic_disease_product', 'transport_maintenance_plan', 'transport_maintenance_record') ORDER BY TABLE_NAME; -- ############################################################################ -- 9. 数据修正:yxe_self_report.chain_id 残留位置型占位符 -- ############################################################################ -- 用途:修正 yxe_self_report.chain_id 中残留的位置型占位符(chain_0、chain_3、chain_6 …) -- 背景:前端 normalizeMerchantChain 在接口未返回 chainId 时会回退成 `chain_${index}`, -- 该值经审核端 saveMerchantEdit 写入 chain_id 落库。而按连锁筛选走的是等值匹配 -- (YxeSelfReportMapper.xml: a.chain_id = #{param.chainId}),占位符导致这些记录 -- 在按连锁筛选时被永久漏掉。本系统没有连锁表,chain 名称即事实上的连锁标识 -- (连锁字典 sys_dict_data.dict_id=175 的 dict_data_code 同样是连锁名)。 -- 影响:本次实际 5 行(chain_6 -> 老百姓 3 行;chain_3 -> 百姓人家 2 行)。 -- 可重复执行:修正后不再匹配 WHERE 条件,无副作用。 -- 性质:幂等(以 chain_name 为准回填 chain_id,限定 REGEXP 占位符)。 -- 配套代码:MerchantServiceImpl.groupByChain 返回稳定 chainId; -- AuditSelfReportItem.vue 兜底链改用连锁名并在写入前拦截 chain_ 前缀。 -- 执行前核对(本地库曾为 5 行;执行后应为 0 行) SELECT id, chain_id, chain_name FROM yxe_self_report WHERE chain_id REGEXP '^chain_[0-9]+$' ORDER BY id; -- 修正 UPDATE yxe_self_report SET chain_id = chain_name WHERE chain_id REGEXP '^chain_[0-9]+$' AND chain_name IS NOT NULL AND chain_name <> ''; -- 执行后核对(remaining_placeholder 与 mismatch 均应为 0) SELECT SUM(chain_id REGEXP '^chain_[0-9]+$') AS remaining_placeholder, SUM(chain_id <> chain_name) AS mismatch, COUNT(*) AS total FROM yxe_self_report; -- ############################################################################ -- 10. 数据修正:yxe_task.reward 为 NULL 的历史脏数据 -- ############################################################################ -- 用途:把 yxe_task.reward 为 NULL 的历史脏数据规范化为 0,消除业绩统计接口的空指针隐患 -- 背景:小程序 pages/performance/index 页面请求的 /yxe-user-task/userRank 与 /yxe-task/data -- 同时返回 {"code":1,"message":"操作失败","error":"java.lang.NullPointerException"}, -- 页面「我的完成进度/完成率」与人员排行榜全部空白。 -- 根因:yxe_task.reward 为 NULL 的任务被计入完成数后执行 -- BigDecimal.ZERO.add(task.getReward()) -- → BigDecimal.add(null) 抛 NPE,整个接口失败。 -- 本库命中数据为 yxe_task.id=663(title='测试手动',status=2 已完成, -- 其 yxe_user_task.id=630 / user_id=875 的 status=2)。注意任务状态为 2 的只有这 1 条, -- 但它足以让「全量排行榜」接口整体失败——userRank 不传 name 时遍历全部用户。 -- 影响:1 行。 -- 性质:幂等(重复执行时 WHERE 不再命中)。 -- 配套代码: -- 1) 读侧兜底:YxeTaskController#data、YxeUserTaskController#userRank -- 对 reward 做 nullToZero,接口不再报错; -- 2) 写侧堵源:YxeTaskController#save / #saveBatch 在新增时把缺失的 reward 兜底为 0, -- 不再产生新的 NULL 脏数据。 -- 注意 #update / #updateBatch **刻意不补默认值**:二者是部分更新语义 -- (MyBatis-Plus 默认 NOT_NULL 策略),null 表示“本次不修改该字段”, -- 若在那里兜底会把任务原有奖金静默清零,直接影响用户实得收益。 -- 3) 回归测试:src/test/java/com/gxwebsoft/yxe/controller/TaskRewardNullSafetyTest.java -- 说明:本脚本属于「历史数据规范化」。即使不执行,接口也已不再报错(读侧兜底), -- 但金额会按 0 计;执行后数据本身也归于一致。 -- 执行前核对:先看清 NULL 分布在哪些任务、是否已有人完成任务 SELECT t.id AS task_id, t.title, t.status AS task_status, t.reward, COUNT(ut.id) AS user_task_cnt, SUM(ut.status = 2) AS completed_cnt FROM yxe_task t LEFT JOIN yxe_user_task ut ON ut.task_id = t.id WHERE t.reward IS NULL GROUP BY t.id, t.title, t.status, t.reward ORDER BY t.id; -- 变更:仅规范化「已完成/进行中」且 reward 为 NULL 的任务,避免影响尚未开始的任务 UPDATE yxe_task SET reward = 0.00 WHERE reward IS NULL AND status IN (1, 2); -- 执行后核对(remaining_null 应为 0) SELECT SUM(reward IS NULL) AS remaining_null, SUM(reward IS NULL AND status IN (1, 2)) AS remaining_null_active, COUNT(*) AS total FROM yxe_task; -- ============================================================================ -- 执行记录 -- ============================================================================ -- 2026-10-11 本地开发库(127.0.0.1:3306/yaoxiaoer,root) -- 第 1 节 yxe_self_report 建表 —— 表已存在,跳过 -- 第 2 节 activity_time —— 列已存在,跳过 -- 第 3 节 reject_reason —— 列已存在,跳过 -- 第 4 节 yxe_visit_report 建表 —— 已执行(18 列,0 行业务数据) -- 第 5 节 yxe_visit_list 三列 —— 三列均已存在(visit_type/visit_date/submitter_name) -- 第 6 节 yxe_user_task.income —— 列已存在,跳过 -- 第 7 节 sys_user.user_group —— 列已存在,跳过 -- 第 8 节 三张建表 —— 三表均已存在,跳过 -- 第 9 节 chain_id 占位符修正 —— 已执行:受影响 5 行; -- 执行后 remaining_placeholder = 0,mismatch = 0 -- 功能验证:按连锁筛选命中数 -- chainId=老百姓 279 -> 282(找回 id 119、130、169) -- chainId=百姓人家 174 -> 176(找回 id 138、139) -- 第 10 节 yxe_task.reward 规范化 —— **尚未执行**:本地库仍有 -- yxe_task.id=663(title='测试手动',status=2)reward 为 NULL。 -- 接口已因读侧兜底不再报错,但金额按 0 计;如需数据一致请执行本节。 -- -- 生产库:尚未执行。需经确认后按本文件自上而下复核执行。 -- -- 备注(第 5 节):yxe_visit_list.visit_type 列的注释在本地库仍是旧值 -- 「拜访对象类型: 门店/门店经理/其他」,与源码中的新值「地点/...」不一致。 -- 因该列已存在,第 5 节按幂等设计跳过,不会修正注释;如需同步,见第 5 节备注。 -- ============================================================================