601 lines
40 KiB
Go
601 lines
40 KiB
Go
package logic
|
||
|
||
import (
|
||
"context"
|
||
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
)
|
||
|
||
// ============================================================================
|
||
// 启动幂等迁移
|
||
//
|
||
// 为什么不用迁移工具:本项目部署方式是 `go run ./main.go`,仓库里也没有 goose/flyway,
|
||
// 而 manifest/config 被 gitignore 后运维手上常常只有一份二进制 + 数据库地址。
|
||
// 把「建表 + 加列」做成启动时的幂等 SQL,可以让升级只需要重启服务,不必再手工敲 DDL。
|
||
//
|
||
// 安全约定:
|
||
// - 全部语句必须可重复执行(CREATE TABLE IF NOT EXISTS / 先查 information_schema 再 ALTER)
|
||
// - 失败只告警不阻断启动:缺表会让相关接口报错,但不该连带整个服务起不来
|
||
// ============================================================================
|
||
|
||
type migrateTable struct {
|
||
name string
|
||
sql string
|
||
}
|
||
|
||
type migrateColumn struct {
|
||
table string
|
||
column string
|
||
sql string
|
||
}
|
||
|
||
// Migrate 启动时执行幂等迁移;失败仅告警。
|
||
func Migrate(ctx context.Context) {
|
||
for _, t := range migrateTables() {
|
||
if _, err := g.DB().Exec(ctx, t.sql); err != nil {
|
||
g.Log().Warningf(ctx, "[migrate] 建表 %s 失败: %v", t.name, err)
|
||
}
|
||
}
|
||
for _, c := range migrateColumns() {
|
||
exists, err := columnExists(ctx, c.table, c.column)
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "[migrate] 检查列 %s.%s 失败: %v", c.table, c.column, err)
|
||
continue
|
||
}
|
||
if exists {
|
||
continue
|
||
}
|
||
if _, err = g.DB().Exec(ctx, c.sql); err != nil {
|
||
g.Log().Warningf(ctx, "[migrate] 加列 %s.%s 失败: %v", c.table, c.column, err)
|
||
continue
|
||
}
|
||
g.Log().Infof(ctx, "[migrate] 已新增列 %s.%s", c.table, c.column)
|
||
}
|
||
}
|
||
|
||
// columnExists 用 information_schema 判断列是否已存在(DATABASE() 取当前库,避免写死库名)
|
||
func columnExists(ctx context.Context, table, column string) (bool, error) {
|
||
value, err := g.DB().GetValue(ctx,
|
||
"SELECT COUNT(*) FROM information_schema.COLUMNS "+
|
||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?",
|
||
table, column)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return value.Int() > 0, nil
|
||
}
|
||
|
||
func migrateTables() []migrateTable {
|
||
return []migrateTable{
|
||
{"quota_packs", "CREATE TABLE IF NOT EXISTS `quota_packs` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`pack_key` varchar(64) NOT NULL COMMENT '档位标识,跨端契约'," +
|
||
"`name` varchar(64) NOT NULL DEFAULT '' COMMENT '展示名'," +
|
||
"`times` int NOT NULL DEFAULT 0 COMMENT '可用次数'," +
|
||
"`price_cents` bigint NOT NULL DEFAULT 0 COMMENT '价格(分)'," +
|
||
"`product_id` varchar(64) NOT NULL DEFAULT '' COMMENT '微信虚拟支付道具 ID'," +
|
||
"`valid_days` int NOT NULL DEFAULT 365 COMMENT '有效期天数,默认一年'," +
|
||
"`tool_key` varchar(64) NOT NULL DEFAULT '' COMMENT '绑定工具,空=通用档位'," +
|
||
"`badge` varchar(32) NOT NULL DEFAULT '' COMMENT '角标文案'," +
|
||
"`sort` int NOT NULL DEFAULT 0," +
|
||
"`is_enabled` tinyint NOT NULL DEFAULT 1," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"UNIQUE KEY `uk_pack_key` (`pack_key`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='次数包档位(付费额度包)'"},
|
||
{"user_tool_quota", "CREATE TABLE IF NOT EXISTS `user_tool_quota` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`user_id` bigint NOT NULL," +
|
||
"`tool_key` varchar(64) NOT NULL," +
|
||
"`times_left` int NOT NULL DEFAULT 0 COMMENT '剩余次数'," +
|
||
"`total_bought` int NOT NULL DEFAULT 0 COMMENT '累计购买次数'," +
|
||
"`expire_at` datetime DEFAULT NULL COMMENT '到期时间,NULL 视为已过期'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"UNIQUE KEY `uk_user_tool` (`user_id`,`tool_key`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户工具付费额度余额'"},
|
||
{"user_quota_usage", "CREATE TABLE IF NOT EXISTS `user_quota_usage` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`user_id` bigint NOT NULL," +
|
||
"`tool_key` varchar(64) NOT NULL," +
|
||
"`period_start` datetime NOT NULL COMMENT '当前周期起点'," +
|
||
"`period_end` datetime NOT NULL COMMENT '当前周期终点'," +
|
||
"`free_used` int NOT NULL DEFAULT 0 COMMENT '本周期已用免费额度'," +
|
||
"`member_used` int NOT NULL DEFAULT 0 COMMENT '本周期已用会员额度'," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"UNIQUE KEY `uk_user_tool` (`user_id`,`tool_key`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户工具额度周期用量'"},
|
||
{"settings", "CREATE TABLE IF NOT EXISTS `settings` (" +
|
||
"`k` varchar(64) NOT NULL," +
|
||
"`v` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`k`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台可改的全局配置'"},
|
||
// ===== 笔记域 =====
|
||
{"notes", "CREATE TABLE IF NOT EXISTS `notes` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`user_id` bigint NOT NULL," +
|
||
"`title` varchar(128) NOT NULL DEFAULT '' COMMENT '标题'," +
|
||
"`content` text COMMENT '正文'," +
|
||
"`tags` varchar(255) NOT NULL DEFAULT '' COMMENT '标签,逗号分隔'," +
|
||
"`is_pinned` tinyint NOT NULL DEFAULT 0 COMMENT '置顶'," +
|
||
"`remind_at` datetime DEFAULT NULL COMMENT '提醒时间,NULL=未设置'," +
|
||
"`remind_status` tinyint NOT NULL DEFAULT 0 COMMENT '0未设置 1待提醒 2已提醒 3发送失败 4已过期'," +
|
||
"`remind_sent_at` datetime DEFAULT NULL," +
|
||
"`remind_attempts` int NOT NULL DEFAULT 0," +
|
||
"`subscribe_count` int NOT NULL DEFAULT 0 COMMENT '已授权可发送次数'," +
|
||
"`deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除(不占额度)'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_user_active` (`user_id`,`deleted_at`)," +
|
||
"KEY `idx_remind` (`remind_status`,`remind_at`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='笔记'"},
|
||
// ===== 年会域:企业 / 员工 / 活动 =====
|
||
{"enterprises", "CREATE TABLE IF NOT EXISTS `enterprises` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`name` varchar(128) NOT NULL," +
|
||
"`contact` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`status` tinyint NOT NULL DEFAULT 1," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`),KEY `idx_name` (`name`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企业'"},
|
||
{"employees", "CREATE TABLE IF NOT EXISTS `employees` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`enterprise_id` bigint NOT NULL," +
|
||
"`name` varchar(64) NOT NULL," +
|
||
"`phone` varchar(20) NOT NULL," +
|
||
"`dept` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`status` tinyint NOT NULL DEFAULT 1," +
|
||
"`deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除'," +
|
||
"`deleted_from` text COMMENT '软删前 phone 原值(JSON),便于恢复'," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"UNIQUE KEY `uk_ent_phone` (`enterprise_id`,`phone`)," +
|
||
"KEY `idx_ent_name` (`enterprise_id`,`name`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企业员工库(跨活动复用)'"},
|
||
{"annual_events", "CREATE TABLE IF NOT EXISTS `annual_events` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`enterprise_id` bigint NOT NULL," +
|
||
"`title` varchar(128) NOT NULL," +
|
||
"`venue` varchar(128) NOT NULL DEFAULT ''," +
|
||
"`event_time` datetime DEFAULT NULL," +
|
||
"`status` varchar(16) NOT NULL DEFAULT 'draft' COMMENT 'draft/published/ended'," +
|
||
"`hall_layout` text COMMENT '画布/舞台 JSON'," +
|
||
"`code_url` varchar(512) NOT NULL DEFAULT ''," +
|
||
"`layout_version` int NOT NULL DEFAULT 0," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_ent_status` (`enterprise_id`,`status`),KEY `idx_status` (`status`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='年会活动'"},
|
||
// ===== 年会域:候场 / 桌 / 座(导入/审计表由 T09/T10 追加)=====
|
||
{"event_participants", "CREATE TABLE IF NOT EXISTS `event_participants` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`event_id` bigint NOT NULL,`employee_id` bigint NOT NULL," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"UNIQUE KEY `uk_event_emp` (`event_id`,`employee_id`)," +
|
||
"KEY `idx_event` (`event_id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动候场名单'"},
|
||
{"event_tables", "CREATE TABLE IF NOT EXISTS `event_tables` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`event_id` bigint NOT NULL," +
|
||
"`table_no` varchar(16) NOT NULL DEFAULT '',`name` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`x` int NOT NULL DEFAULT 0,`y` int NOT NULL DEFAULT 0," +
|
||
"`capacity` int NOT NULL DEFAULT 10,`rotation` int NOT NULL DEFAULT 0," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`),KEY `idx_event` (`event_id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圆桌'"},
|
||
{"event_seats", "CREATE TABLE IF NOT EXISTS `event_seats` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`event_id` bigint NOT NULL,`table_id` bigint NOT NULL," +
|
||
"`seat_no` int NOT NULL DEFAULT 0,`is_manual` tinyint NOT NULL DEFAULT 0," +
|
||
"`employee_id` bigint DEFAULT NULL COMMENT 'NULL=空位'," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"UNIQUE KEY `uk_evt_tbl_seat` (`event_id`,`table_id`,`seat_no`)," +
|
||
"KEY `idx_event_emp` (`event_id`,`employee_id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='座位'"},
|
||
{"event_import_logs", "CREATE TABLE IF NOT EXISTS `event_import_logs` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`enterprise_id` bigint NOT NULL,`event_id` bigint NOT NULL DEFAULT 0," +
|
||
"`file_name` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`status` varchar(16) NOT NULL DEFAULT 'draft' COMMENT 'draft/committed/cancelled'," +
|
||
"`token` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`snapshot` mediumtext COMMENT '解析行快照 JSON'," +
|
||
"`total` int NOT NULL DEFAULT 0,`inserted` int NOT NULL DEFAULT 0," +
|
||
"`updated` int NOT NULL DEFAULT 0,`skipped` int NOT NULL DEFAULT 0," +
|
||
"`conflict` int NOT NULL DEFAULT 0,`conflicts` text COMMENT '裁决结果 JSON'," +
|
||
"`operator` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`),UNIQUE KEY `uk_token` (`token`)," +
|
||
"KEY `idx_event` (`event_id`),KEY `idx_ent` (`enterprise_id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='导入记录/审计'"},
|
||
{"event_seat_queries", "CREATE TABLE IF NOT EXISTS `event_seat_queries` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`event_id` bigint NOT NULL," +
|
||
"`openid` varchar(64) NOT NULL DEFAULT '',`ip` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`phone_masked` varchar(20) NOT NULL DEFAULT ''," +
|
||
"`result` tinyint NOT NULL DEFAULT 0 COMMENT '0失败 1成功'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_evt_openid_time` (`event_id`,`openid`,`created_at`)," +
|
||
"KEY `idx_evt_ip_time` (`event_id`,`ip`,`created_at`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='查座审计与限流'"},
|
||
// ===== 后台权限与操作审计(T17)=====
|
||
{"admin_audit_logs", "CREATE TABLE IF NOT EXISTS `admin_audit_logs` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`admin_id` bigint NOT NULL DEFAULT 0," +
|
||
"`admin_account` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`admin_name` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`role_value` varchar(32) NOT NULL DEFAULT ''," +
|
||
"`action` varchar(64) NOT NULL DEFAULT '' COMMENT '如 user.set_level / event.status'," +
|
||
"`target_type` varchar(32) NOT NULL DEFAULT '' COMMENT 'user/event/table/admin...'," +
|
||
"`target_id` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`before_json` text COMMENT '操作前值 JSON'," +
|
||
"`after_json` text COMMENT '操作后值 JSON'," +
|
||
"`result` tinyint NOT NULL DEFAULT 1 COMMENT '0失败 1成功'," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`ip` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_admin_time` (`admin_id`,`created_at`)," +
|
||
"KEY `idx_action_time` (`action`,`created_at`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台操作审计'"},
|
||
// ===== 商业化域:优惠码 / 优惠券(追加到 migrateTables())=====
|
||
{"promo_codes", "CREATE TABLE IF NOT EXISTS `promo_codes` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`code` varchar(32) NOT NULL COMMENT '码值(唯一,大写字母+数字,排除易混字符)'," +
|
||
"`product_id` varchar(64) NOT NULL DEFAULT 'admin_1' COMMENT '绑定的支付道具 ID(须与 MP 后台逐字符一致)'," +
|
||
"`price_cents` bigint NOT NULL DEFAULT 1 COMMENT '该道具价格(分)'," +
|
||
"`scope` tinyint NOT NULL DEFAULT 0 COMMENT '0全部 1指定会员套餐 2指定次数包'," +
|
||
"`scope_keys` varchar(512) NOT NULL DEFAULT '' COMMENT '适用范围 key 的 JSON 数组'," +
|
||
"`max_uses` int NOT NULL DEFAULT 0 COMMENT '总使用上限,0=不限'," +
|
||
"`used_count` int NOT NULL DEFAULT 0 COMMENT '已用次数'," +
|
||
"`per_user_limit` int NOT NULL DEFAULT 1 COMMENT '单人限用次数'," +
|
||
"`valid_from` datetime DEFAULT NULL,`valid_to` datetime DEFAULT NULL," +
|
||
"`status` tinyint NOT NULL DEFAULT 1 COMMENT '1启用 0停用'," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`created_by` varchar(64) NOT NULL DEFAULT ''," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`),UNIQUE KEY `uk_code` (`code`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='优惠码(免单/固定价档位,独立于优惠券)'"},
|
||
{"coupons", "CREATE TABLE IF NOT EXISTS `coupons` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`title` varchar(64) NOT NULL COMMENT '券名'," +
|
||
"`sub_title` varchar(128) NOT NULL DEFAULT '' COMMENT '副标题'," +
|
||
"`coupon_type` tinyint NOT NULL DEFAULT 2 COMMENT '1免单券(走支付) 2展示券(仅展示,默认)'," +
|
||
"`kind` tinyint NOT NULL DEFAULT 2 COMMENT '1满减 2免单(面额语义/展示)'," +
|
||
"`value` int NOT NULL DEFAULT 0 COMMENT '面额(分,仅展示用途)'," +
|
||
"`threshold_cents` int NOT NULL DEFAULT 0 COMMENT '使用门槛(分,0=无门槛)'," +
|
||
"`scope` tinyint NOT NULL DEFAULT 0 COMMENT '0全部 1指定会员套餐 2指定次数包'," +
|
||
"`scope_keys` varchar(512) NOT NULL DEFAULT ''," +
|
||
"`total_limit` int NOT NULL DEFAULT 0 COMMENT '总发行上限,0=不限'," +
|
||
"`per_user_limit` int NOT NULL DEFAULT 1 COMMENT '单人限领'," +
|
||
"`valid_from` datetime DEFAULT NULL,`valid_to` datetime DEFAULT NULL," +
|
||
"`status` tinyint NOT NULL DEFAULT 1 COMMENT '1启用 0停用'," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`),KEY `idx_type_status` (`coupon_type`,`status`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='优惠券模板'"},
|
||
{"user_coupons", "CREATE TABLE IF NOT EXISTS `user_coupons` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`coupon_id` bigint NOT NULL,`user_id` bigint NOT NULL," +
|
||
"`coupon_title` varchar(64) NOT NULL DEFAULT '' COMMENT '券名快照(模板改名不影响已发券)'," +
|
||
"`coupon_type` tinyint NOT NULL DEFAULT 2 COMMENT '券型快照(模板改型不影响已发券)'," +
|
||
"`kind` tinyint NOT NULL DEFAULT 2,`value` int NOT NULL DEFAULT 0," +
|
||
"`threshold_cents` int NOT NULL DEFAULT 0," +
|
||
"`status` tinyint NOT NULL DEFAULT 0 COMMENT '0未使用 1已使用 2已过期 3已作废'," +
|
||
"`source` tinyint NOT NULL DEFAULT 1 COMMENT '1后台发放 2活动领取 3系统赠送'," +
|
||
"`granted_by` varchar(64) NOT NULL DEFAULT '' COMMENT '发放人 admin id'," +
|
||
"`granted_at` datetime DEFAULT NULL,`used_at` datetime DEFAULT NULL," +
|
||
"`expire_at` datetime DEFAULT NULL," +
|
||
"`used_order_no` varchar(64) NOT NULL DEFAULT '' COMMENT '占用/核销对应订单号'," +
|
||
"`used_by` varchar(64) NOT NULL DEFAULT '' COMMENT 'system=订单占用;admin=人工核销'," +
|
||
"`lock_at` datetime DEFAULT NULL COMMENT '下单占用时间(超时释放依据)'," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`created_at` datetime DEFAULT NULL,`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_user_status` (`user_id`,`status`)," +
|
||
"KEY `idx_coupon` (`coupon_id`)," +
|
||
"KEY `idx_user_lock` (`user_id`,`used_order_no`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户持有的优惠券(含券面快照 + 占用/核销记录)'"},
|
||
|
||
// ===== 软件开发服务域(迭代-2026-09-29)=====
|
||
{"goods", "CREATE TABLE IF NOT EXISTS `goods` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`category` tinyint NOT NULL DEFAULT 1 COMMENT '分类 1网站开发 2小程序开发 3App开发 4桌面程序开发'," +
|
||
"`title` varchar(128) NOT NULL DEFAULT '' COMMENT '服务名称'," +
|
||
"`summary` varchar(500) NOT NULL DEFAULT '' COMMENT '简介(列表卡片展示)'," +
|
||
"`content_md` mediumtext COMMENT '详细说明 Markdown 原文,小程序端解析渲染'," +
|
||
"`price_cents` bigint NOT NULL DEFAULT 0 COMMENT '定价(分),0=面议'," +
|
||
"`price_note` varchar(64) NOT NULL DEFAULT '' COMMENT '价格备注(如「起」「/项目」)'," +
|
||
"`cover_path` varchar(255) NOT NULL DEFAULT '' COMMENT '封面相对路径(/static/...),库内不存域名'," +
|
||
"`sort` int NOT NULL DEFAULT 0 COMMENT '排序值,越小越靠前'," +
|
||
"`is_enabled` tinyint NOT NULL DEFAULT 1 COMMENT '上架状态 0下架 1上架'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"`deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除'," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_cat_sort` (`is_enabled`,`category`,`sort`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='软件开发服务商品'"},
|
||
{"attachments", "CREATE TABLE IF NOT EXISTS `attachments` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`scene` varchar(32) NOT NULL DEFAULT '' COMMENT '上传场景 avatar/cover/attachment'," +
|
||
"`file_name` varchar(255) NOT NULL DEFAULT '' COMMENT '原始文件名'," +
|
||
"`file_path` varchar(255) NOT NULL DEFAULT '' COMMENT '相对路径(/static/...),库内不存域名'," +
|
||
"`ext` varchar(16) NOT NULL DEFAULT '' COMMENT '扩展名(小写含点)'," +
|
||
"`size_bytes` bigint NOT NULL DEFAULT 0 COMMENT '文件大小(字节)'," +
|
||
"`md5` char(32) NOT NULL DEFAULT '' COMMENT '内容 md5(落盘文件名)'," +
|
||
"`uploader_id` bigint NOT NULL DEFAULT 0 COMMENT '上传者 id(管理端或用户)'," +
|
||
"`uploader_type` varchar(16) NOT NULL DEFAULT 'admin' COMMENT '上传者类型 admin/user'," +
|
||
"`created_at` datetime DEFAULT NULL COMMENT '上传日期'," +
|
||
"`deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除(记录删除,文件本体按引用检查)'," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_created` (`created_at`)," +
|
||
"KEY `idx_path` (`file_path`)," +
|
||
"KEY `idx_scene` (`scene`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='上传附件登记(上传日期/上传者/大小,供后台附件管理)'"},
|
||
|
||
// ===== 软件开发服务域二期:介绍页模块 / 作品案例 / 报价申请 / 站内信(迭代-2026-09-30)=====
|
||
{"service_blocks", "CREATE TABLE IF NOT EXISTS `service_blocks` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`kind` varchar(16) NOT NULL DEFAULT 'rich' COMMENT '模块类型 hero/rich/image_text/image_list/developer/works/goods'," +
|
||
"`title` varchar(128) NOT NULL DEFAULT '' COMMENT '标题'," +
|
||
"`subtitle` varchar(255) NOT NULL DEFAULT '' COMMENT '副标题'," +
|
||
"`image_path` varchar(255) NOT NULL DEFAULT '' COMMENT '主图相对路径(hero/image_text/developer 头像),库内不存域名'," +
|
||
"`images` text COMMENT '图片列表 JSON 数组(相对路径,image_list 用)'," +
|
||
"`content` mediumtext COMMENT '正文(Markdown 原文,C 端解析渲染)'," +
|
||
"`sort` int NOT NULL DEFAULT 0 COMMENT '排序值,越小越靠前'," +
|
||
"`is_enabled` tinyint NOT NULL DEFAULT 1 COMMENT '启用 0停用 1启用'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_sort` (`is_enabled`,`sort`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='软件开发服务介绍页模块(后台可编辑,预设排版布局)'"},
|
||
{"service_works", "CREATE TABLE IF NOT EXISTS `service_works` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`category` tinyint NOT NULL DEFAULT 1 COMMENT '分类 1网站 2小程序 3App 4桌面(与 goods 对齐)'," +
|
||
"`title` varchar(128) NOT NULL DEFAULT '' COMMENT '作品名称'," +
|
||
"`summary` varchar(500) NOT NULL DEFAULT '' COMMENT '简介(列表卡片展示)'," +
|
||
"`content_md` mediumtext COMMENT '详情说明 Markdown 原文'," +
|
||
"`cover_path` varchar(255) NOT NULL DEFAULT '' COMMENT '封面相对路径'," +
|
||
"`images` text COMMENT '详情图集 JSON 数组(相对路径)'," +
|
||
"`price_cents` bigint NOT NULL DEFAULT 0 COMMENT '参考报价(分),0=面议'," +
|
||
"`price_note` varchar(64) NOT NULL DEFAULT '' COMMENT '报价备注(如「起」)'," +
|
||
"`sort` int NOT NULL DEFAULT 0 COMMENT '排序值,越小越靠前'," +
|
||
"`is_enabled` tinyint NOT NULL DEFAULT 1 COMMENT '上架状态 0下架 1上架'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"`deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除'," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_cat_sort` (`is_enabled`,`category`,`sort`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='软件开发作品案例'"},
|
||
{"service_quotes", "CREATE TABLE IF NOT EXISTS `service_quotes` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`user_id` bigint NOT NULL," +
|
||
"`work_id` bigint NOT NULL DEFAULT 0 COMMENT '关联作品 id,0=通用申请'," +
|
||
"`work_title` varchar(128) NOT NULL DEFAULT '' COMMENT '作品名快照(作品改名/删除不影响申请单)'," +
|
||
"`contact` varchar(64) NOT NULL DEFAULT '' COMMENT '联系方式(微信/手机/QQ)'," +
|
||
"`requirement` varchar(1000) NOT NULL DEFAULT '' COMMENT '需求描述'," +
|
||
"`budget` varchar(64) NOT NULL DEFAULT '' COMMENT '预算范围(文案,如 5k-1w)'," +
|
||
"`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待处理 1已报价 2已成交 3已关闭'," +
|
||
"`quote_cents` bigint NOT NULL DEFAULT 0 COMMENT '报价金额(分),0=面议/未填'," +
|
||
"`quote_note` varchar(500) NOT NULL DEFAULT '' COMMENT '报价说明(用户端可见)'," +
|
||
"`handled_by` bigint NOT NULL DEFAULT 0 COMMENT '处理管理员 id,0=未处理'," +
|
||
"`handled_at` datetime DEFAULT NULL COMMENT '最近处理时间'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_user` (`user_id`,`id`)," +
|
||
"KEY `idx_status` (`status`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='软件开发报价申请'"},
|
||
{"user_messages", "CREATE TABLE IF NOT EXISTS `user_messages` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`user_id` bigint NOT NULL," +
|
||
"`type` varchar(32) NOT NULL DEFAULT 'system' COMMENT '类型 order_delivered/quote_submitted/quote_handled/feedback_replied/project_update/system'," +
|
||
"`title` varchar(128) NOT NULL DEFAULT '' COMMENT '消息标题'," +
|
||
"`content` varchar(500) NOT NULL DEFAULT '' COMMENT '消息正文'," +
|
||
"`link` varchar(255) NOT NULL DEFAULT '' COMMENT '跳转路径(小程序内路由,空=不跳转)'," +
|
||
"`is_read` tinyint NOT NULL DEFAULT 0 COMMENT '0未读 1已读'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_user_read` (`user_id`,`is_read`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户站内信(会员开通/报价/反馈回复等通知)'"},
|
||
|
||
// ===== 软件开发域三期:开发项目(后台建单 → 用户扫码绑定 → 阶段/节点/交付物跟踪)=====
|
||
{"service_projects", "CREATE TABLE IF NOT EXISTS `service_projects` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`user_id` bigint NOT NULL DEFAULT 0 COMMENT '绑定用户 id,0=待扫码绑定'," +
|
||
"`bind_token` varchar(16) NOT NULL DEFAULT '' COMMENT '扫码绑定令牌(scene 携带,唯一)'," +
|
||
"`title` varchar(128) NOT NULL DEFAULT '' COMMENT '项目名称'," +
|
||
"`description` varchar(1000) NOT NULL DEFAULT '' COMMENT '需求描述'," +
|
||
"`price_cents` bigint NOT NULL DEFAULT 0 COMMENT '项目金额(分),0=面议'," +
|
||
"`price_note` varchar(64) NOT NULL DEFAULT '' COMMENT '金额备注'," +
|
||
"`due_note` varchar(64) NOT NULL DEFAULT '' COMMENT '交付周期文案(如「4 周」)'," +
|
||
"`status` tinyint NOT NULL DEFAULT 1 COMMENT '1进行中 2已交付 3已关闭'," +
|
||
"`code_url` varchar(255) NOT NULL DEFAULT '' COMMENT '绑定小程序码相对路径(/qrcode/...)'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"`deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除'," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"UNIQUE KEY `uk_bind_token` (`bind_token`)," +
|
||
"KEY `idx_user` (`user_id`,`id`)," +
|
||
"KEY `idx_status` (`status`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='软件开发项目订单'"},
|
||
{"service_project_stages", "CREATE TABLE IF NOT EXISTS `service_project_stages` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`project_id` bigint NOT NULL," +
|
||
"`title` varchar(64) NOT NULL DEFAULT '' COMMENT '阶段名(如 原型设计/UI设计/开发/测试)'," +
|
||
"`status` tinyint NOT NULL DEFAULT 0 COMMENT '0未开始 1进行中 2已完成'," +
|
||
"`sort` int NOT NULL DEFAULT 0 COMMENT '排序值,越小越靠前'," +
|
||
"`is_enabled` tinyint NOT NULL DEFAULT 1 COMMENT '0隐藏 1显示'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_project` (`project_id`,`sort`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开发项目阶段'"},
|
||
{"service_project_nodes", "CREATE TABLE IF NOT EXISTS `service_project_nodes` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`project_id` bigint NOT NULL," +
|
||
"`stage_id` bigint NOT NULL," +
|
||
"`title` varchar(64) NOT NULL DEFAULT '' COMMENT '小节点(可勾选事项)'," +
|
||
"`is_done` tinyint NOT NULL DEFAULT 0 COMMENT '0未完成 1已完成'," +
|
||
"`done_at` datetime DEFAULT NULL COMMENT '完成时间'," +
|
||
"`sort` int NOT NULL DEFAULT 0," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_stage` (`stage_id`,`sort`,`id`)," +
|
||
"KEY `idx_project` (`project_id`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开发项目阶段小节点'"},
|
||
{"service_project_files", "CREATE TABLE IF NOT EXISTS `service_project_files` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`project_id` bigint NOT NULL," +
|
||
"`stage_id` bigint NOT NULL DEFAULT 0 COMMENT '所属阶段,0=项目级'," +
|
||
"`file_name` varchar(128) NOT NULL DEFAULT '' COMMENT '展示名(默认原始文件名)'," +
|
||
"`file_path` varchar(255) NOT NULL DEFAULT '' COMMENT '相对路径(/static/...),库内不存域名'," +
|
||
"`sort` int NOT NULL DEFAULT 0," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_stage` (`stage_id`,`sort`,`id`)," +
|
||
"KEY `idx_project` (`project_id`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开发项目交付物(原型/UI 设计稿等图片)'"},
|
||
|
||
// ===== 软件开发域四期:维护模板 / 付款分期模板 / 项目分期(交付流程)=====
|
||
{"service_maintain_plans", "CREATE TABLE IF NOT EXISTS `service_maintain_plans` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`name` varchar(64) NOT NULL DEFAULT '' COMMENT '模板名(如 一年免费维护)'," +
|
||
"`free_years` int NOT NULL DEFAULT 0 COMMENT '免费维护年数,0=无免费'," +
|
||
"`yearly_fee_cents` bigint NOT NULL DEFAULT 0 COMMENT '后续维护费(元/年,分存储),0=免费无续费'," +
|
||
"`source_only` tinyint NOT NULL DEFAULT 0 COMMENT '1=仅交付源码无售后(不进维护期提醒)'," +
|
||
"`sort` int NOT NULL DEFAULT 0," +
|
||
"`is_enabled` tinyint NOT NULL DEFAULT 1," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_sort` (`is_enabled`,`sort`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维护模板(免费年限/年费/仅源码)'"},
|
||
{"service_pay_plans", "CREATE TABLE IF NOT EXISTS `service_pay_plans` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`name` varchar(64) NOT NULL DEFAULT '' COMMENT '模板名(如 两期·上线前结清)'," +
|
||
"`schedule` varchar(500) NOT NULL DEFAULT '' COMMENT '分期 JSON:[{seq,percent,trigger}],trigger=start/online_before/online_after/mid_demo'," +
|
||
"`sort` int NOT NULL DEFAULT 0," +
|
||
"`is_enabled` tinyint NOT NULL DEFAULT 1," +
|
||
"`remark` varchar(255) NOT NULL DEFAULT ''," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_sort` (`is_enabled`,`sort`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='付款分期模板(定金/尾款比例节点)'"},
|
||
{"service_project_pays", "CREATE TABLE IF NOT EXISTS `service_project_pays` (" +
|
||
"`id` bigint unsigned NOT NULL AUTO_INCREMENT," +
|
||
"`project_id` bigint NOT NULL," +
|
||
"`seq` int NOT NULL DEFAULT 1 COMMENT '第几期'," +
|
||
"`percent` int NOT NULL DEFAULT 0 COMMENT '付款比例(%)'," +
|
||
"`amount_cents` bigint NOT NULL DEFAULT 0 COMMENT '本期金额(分)= 项目金额 × 比例'," +
|
||
"`trigger` varchar(16) NOT NULL DEFAULT 'start' COMMENT '触发节点 start/online_before/online_after/mid_demo'," +
|
||
"`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付'," +
|
||
"`paid_at` datetime DEFAULT NULL," +
|
||
"`paid_note` varchar(64) NOT NULL DEFAULT '' COMMENT '收款备注(如 微信收款)'," +
|
||
"`created_at` datetime DEFAULT NULL," +
|
||
"`updated_at` datetime DEFAULT NULL," +
|
||
"PRIMARY KEY (`id`)," +
|
||
"KEY `idx_project` (`project_id`,`seq`,`id`)" +
|
||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开发项目分期付款(后台确认到账)'"},
|
||
}
|
||
}
|
||
|
||
func migrateColumns() []migrateColumn {
|
||
return []migrateColumn{
|
||
// 工具额度配置:默认值让老数据在升级后立刻可用(免费 3 次/周期、会员 100 次/周期、系数 1)
|
||
{"tools", "free_quota", "ALTER TABLE `tools` ADD COLUMN `free_quota` int NOT NULL DEFAULT 3 COMMENT '免费额度(每周期次数)'"},
|
||
{"tools", "member_quota", "ALTER TABLE `tools` ADD COLUMN `member_quota` int NOT NULL DEFAULT 100 COMMENT '会员额度(每周期次数)'"},
|
||
{"tools", "pay_cost", "ALTER TABLE `tools` ADD COLUMN `pay_cost` int NOT NULL DEFAULT 1 COMMENT '付费额度每次消耗系数'"},
|
||
|
||
// 额度周期:0 = 跟随全局设置
|
||
{"users", "quota_period_days", "ALTER TABLE `users` ADD COLUMN `quota_period_days` int NOT NULL DEFAULT 0 COMMENT '额度重置周期(天),0=跟随全局'"},
|
||
{"member_plans", "quota_period_days", "ALTER TABLE `member_plans` ADD COLUMN `quota_period_days` int NOT NULL DEFAULT 0 COMMENT '购买后生效的额度周期(天),0=不调整'"},
|
||
|
||
// 订单表承载两类商品
|
||
{"member_orders", "order_type", "ALTER TABLE `member_orders` ADD COLUMN `order_type` tinyint NOT NULL DEFAULT 1 COMMENT '1=会员套餐 2=次数包'"},
|
||
{"member_orders", "pack_key", "ALTER TABLE `member_orders` ADD COLUMN `pack_key` varchar(64) NOT NULL DEFAULT '' COMMENT '次数包档位'"},
|
||
{"member_orders", "tool_key", "ALTER TABLE `member_orders` ADD COLUMN `tool_key` varchar(64) NOT NULL DEFAULT '' COMMENT '次数包绑定工具'"},
|
||
{"member_orders", "times", "ALTER TABLE `member_orders` ADD COLUMN `times` int NOT NULL DEFAULT 0 COMMENT '次数包次数'"},
|
||
|
||
// T17:管理员启停状态(默认启用,老数据升级后立即可用)
|
||
{"admin_users", "status", "ALTER TABLE `admin_users` ADD COLUMN `status` tinyint NOT NULL DEFAULT 1 COMMENT '0停用 1启用'"},
|
||
|
||
// ===== T18 软删除(deleted_at / deleted_from)=====
|
||
// users:唯一键 uk_openid / uk_username,软删时墓碑化并保留原值
|
||
{"users", "deleted_at", "ALTER TABLE `users` ADD COLUMN `deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除'"},
|
||
{"users", "deleted_from", "ALTER TABLE `users` ADD COLUMN `deleted_from` text COMMENT '软删前唯一键原值(JSON),便于恢复'"},
|
||
// tools:软删仅置 deleted_at(不墓碑化 tool_key,见 admin_batch.go 说明)
|
||
{"tools", "deleted_at", "ALTER TABLE `tools` ADD COLUMN `deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除'"},
|
||
// employees:唯一键 uk_ent_phone,软删时墓碑化 phone 并保留原值
|
||
{"employees", "deleted_at", "ALTER TABLE `employees` ADD COLUMN `deleted_at` datetime DEFAULT NULL COMMENT '软删除时间,NULL=未删除'"},
|
||
{"employees", "deleted_from", "ALTER TABLE `employees` ADD COLUMN `deleted_from` text COMMENT '软删前 phone 原值(JSON),便于恢复'"},
|
||
|
||
// ===== member_orders 加列(追加到 migrateColumns();不加进 attach,依据约定 C4)=====
|
||
{"member_orders", "origin_price_cents", "ALTER TABLE `member_orders` ADD COLUMN `origin_price_cents` bigint NOT NULL DEFAULT 0 COMMENT '原价(分)'"},
|
||
{"member_orders", "paid_price_cents", "ALTER TABLE `member_orders` ADD COLUMN `paid_price_cents` bigint NOT NULL DEFAULT 0 COMMENT '实付(分)'"},
|
||
{"member_orders", "promo_code_id", "ALTER TABLE `member_orders` ADD COLUMN `promo_code_id` bigint NOT NULL DEFAULT 0 COMMENT '使用的优惠码 id,0=未使用'"},
|
||
{"member_orders", "promo_code", "ALTER TABLE `member_orders` ADD COLUMN `promo_code` varchar(32) NOT NULL DEFAULT '' COMMENT '优惠码值快照'"},
|
||
{"member_orders", "user_coupon_id", "ALTER TABLE `member_orders` ADD COLUMN `user_coupon_id` bigint NOT NULL DEFAULT 0 COMMENT '使用的免单券 user_coupons.id,0=未使用'"},
|
||
{"member_orders", "discount_cents", "ALTER TABLE `member_orders` ADD COLUMN `discount_cents` bigint NOT NULL DEFAULT 0 COMMENT '优惠金额(分)'"},
|
||
{"member_orders", "promo_kind", "ALTER TABLE `member_orders` ADD COLUMN `promo_kind` tinyint NOT NULL DEFAULT 0 COMMENT '使用凭证类型:0无 1优惠码 2免单券'"},
|
||
|
||
// ===== 订单退款/回收所购服务(退款能力)=====
|
||
// service_revoked:退款时是否已回收所购服务(用于列表展示 / 审计留痕 / 幂等判重)。
|
||
// refunded_at :退款时间(列表展示「退款时间」)。
|
||
{"member_orders", "service_revoked", "ALTER TABLE `member_orders` ADD COLUMN `service_revoked` tinyint NOT NULL DEFAULT 0 COMMENT '退款时是否已回收所购服务:0未回收 1已回收'"},
|
||
{"member_orders", "refunded_at", "ALTER TABLE `member_orders` ADD COLUMN `refunded_at` datetime DEFAULT NULL COMMENT '退款时间'"},
|
||
|
||
// ===== 真退款链路(调微信 /xpay/refund_order):退款单状态与金额落库 =====
|
||
// refund_no :商户退款单号(唯一;失败重发会换新号)
|
||
// refund_status :0未退款 1退款中 2退款成功 3退款失败(独立于订单 status)
|
||
// refund_amount :退款金额(分,全额退款 = paid_price_cents)
|
||
// refund_reason :退款原因(后台登记,iOS/手工退款必填)
|
||
// refund_finished_at:退款确认完成时间(回调 / 查单轮询 / 手工登记)
|
||
{"member_orders", "refund_no", "ALTER TABLE `member_orders` ADD COLUMN `refund_no` varchar(40) NOT NULL DEFAULT '' COMMENT '商户退款单号'"},
|
||
{"member_orders", "refund_status", "ALTER TABLE `member_orders` ADD COLUMN `refund_status` tinyint NOT NULL DEFAULT 0 COMMENT '退款状态:0未退款 1退款中 2退款成功 3退款失败'"},
|
||
{"member_orders", "refund_amount", "ALTER TABLE `member_orders` ADD COLUMN `refund_amount` bigint NOT NULL DEFAULT 0 COMMENT '退款金额(分)'"},
|
||
{"member_orders", "refund_reason", "ALTER TABLE `member_orders` ADD COLUMN `refund_reason` varchar(255) NOT NULL DEFAULT '' COMMENT '退款原因'"},
|
||
{"member_orders", "refund_finished_at", "ALTER TABLE `member_orders` ADD COLUMN `refund_finished_at` datetime DEFAULT NULL COMMENT '退款确认完成时间'"},
|
||
|
||
// ===== 反馈回复(官方回复 + 用户端未读提醒)=====
|
||
// reply_content:官方回复内容(后台「处理反馈」时填写,可多次更新)
|
||
// reply_by :回复管理员 id(审计留痕)
|
||
// reply_at :最近一次回复时间
|
||
// reply_read :用户端未读标记:0=有新回复未读(小红点) 1=已读
|
||
{"feedbacks", "reply_content", "ALTER TABLE `feedbacks` ADD COLUMN `reply_content` varchar(1000) NOT NULL DEFAULT '' COMMENT '官方回复内容'"},
|
||
{"feedbacks", "reply_by", "ALTER TABLE `feedbacks` ADD COLUMN `reply_by` bigint NOT NULL DEFAULT 0 COMMENT '回复管理员 id,0=无'"},
|
||
{"feedbacks", "reply_at", "ALTER TABLE `feedbacks` ADD COLUMN `reply_at` datetime DEFAULT NULL COMMENT '最近回复时间'"},
|
||
{"feedbacks", "reply_read", "ALTER TABLE `feedbacks` ADD COLUMN `reply_read` tinyint NOT NULL DEFAULT 0 COMMENT '用户端已读:0有新回复未读 1已读'"},
|
||
|
||
// ===== T04 座次:桌型 / 备注 / 排除(N4)=====
|
||
// 全部幂等加列;老活动无这些属性时回退默认值(普通桌 / 空备注 / 参与),保证老画布与自动排座不报错(S14)。
|
||
{"event_tables", "table_type", "ALTER TABLE `event_tables` ADD COLUMN `table_type` tinyint NOT NULL DEFAULT 0 COMMENT '桌型:0普通 1主桌 2签到台 3媒体席 4备用桌 9其他'"},
|
||
{"event_tables", "remark", "ALTER TABLE `event_tables` ADD COLUMN `remark` varchar(64) NOT NULL DEFAULT '' COMMENT '桌备注'"},
|
||
{"event_tables", "exclude_auto", "ALTER TABLE `event_tables` ADD COLUMN `exclude_auto` tinyint NOT NULL DEFAULT 0 COMMENT '1=不参与自动排序'"},
|
||
{"event_participants", "exclude_assign", "ALTER TABLE `event_participants` ADD COLUMN `exclude_assign` tinyint NOT NULL DEFAULT 0 COMMENT '1=不参与排座'"},
|
||
|
||
// ===== 开发项目四期:交付流程(维护模板 / 付款分期 / 验收维护期)=====
|
||
{"service_projects", "maintain_plan_id", "ALTER TABLE `service_projects` ADD COLUMN `maintain_plan_id` bigint NOT NULL DEFAULT 0 COMMENT '维护模板 id,0=未选择'"},
|
||
{"service_projects", "pay_plan_id", "ALTER TABLE `service_projects` ADD COLUMN `pay_plan_id` bigint NOT NULL DEFAULT 0 COMMENT '付款分期模板 id,0=未选择'"},
|
||
{"service_projects", "accepted", "ALTER TABLE `service_projects` ADD COLUMN `accepted` tinyint NOT NULL DEFAULT 0 COMMENT '0待验收 1已验收(用户手动确认)'"},
|
||
{"service_projects", "accepted_at", "ALTER TABLE `service_projects` ADD COLUMN `accepted_at` datetime DEFAULT NULL COMMENT '用户确认验收时间(维护期起点,后台可调 maintain_start_at)'"},
|
||
{"service_projects", "maintain_start_at", "ALTER TABLE `service_projects` ADD COLUMN `maintain_start_at` datetime DEFAULT NULL COMMENT '维护期起算日(默认=验收日,后台可调整)'"},
|
||
{"service_projects", "maintain_paid_until", "ALTER TABLE `service_projects` ADD COLUMN `maintain_paid_until` datetime DEFAULT NULL COMMENT '维护费已缴至(免费期=起点+免费年数;续费+1年)'"},
|
||
{"service_projects", "last_maintain_remind_at", "ALTER TABLE `service_projects` ADD COLUMN `last_maintain_remind_at` datetime DEFAULT NULL COMMENT '上次缴费提醒时间(隔 3 天一次的依据)'"},
|
||
}
|
||
}
|