Просмотр исходного кода

chore: document blocked helper and hms migration

彭峰 2 месяцев назад
Родитель
Сommit
a0103f7140

+ 1 - 0
docs/migration/admin-migration-status.md

@@ -28,6 +28,7 @@
 - 旧 `ECharts/ECharts` 六个动作依赖 `Content_Chart` 的 TName/ChartTitle/ToolBox/Package/option/SType/Tag 数据、七类 .NET 配置生成器、`SiteInfo.SiteUrl` 与 `/Common/ShowM` 渲染页;目标 Schema 中不存在任何 chart/echart/graph 数据类。所有动作通过 `legacyEChartsBlocker` 返回逐项原因,不用问卷统计或普通 Angular 图表冒充旧图表编辑器。
 - Exam 模块已把仍有历史数据的 `ExamSysPapers/ExamSysQuestions/PaperQuestions/ExamType/ContentPublish/PublishNode` 接入 Angular 只读列表、搜索、分页和详情,并关闭通用写入,防止破坏试卷 `qids/questList`、复合答案和报刊父子结构。教材版本、学校、班级、班级学员、旧 Course、答卷评分数据均未迁入;对应 21 个动作由 `legacyExamBlocker` 返回数据或运行时阻塞。`CourseBinding` 明确不冒充旧考试 Course。
 - 商城 `DeliverType/DeliverAdd/DeliverAdd_Submit/Deliver_API` 已由 `ShopFareTlp` 真实历史数据、`shopFareTemplates/saveShopFareTemplate/shopFareTemplateBatch` 和 Angular“商城运费模板”页面迁移:校验 Express JSON、名称防重并原子分配旧数字 ID。订单/物流单、Manufacturers 厂商和 Trademark 品牌表均不存在,剩余 12 个动作返回细分阻塞;`DeliveryCenter` 不被误作运费模板。
+- Helper 的数据库自初始化与帮助精灵依赖服务器 `DBConfig.json`、本地 SQL 文件、旧 Notification 数据和 SignalR 分组协议,目标均不存在,两个动作由 `legacyHelperBlocker` 明确阻塞。Hms 的 10 个动作管理的是另一个产品“逐浪主题大师”的七档会员卡,依赖 `ZL_UserBase.cardType/ICQ` 及 `ZL_Orderinfo/ZL_CartPro`;迁移数据没有这些字段或表,不能误用小树现有 `VIP` 合伙人等级,已由 `legacyHmsBlocker` 逐项说明。
 - 旧 `NodeAdd_Submit`、`Node_API` 与 `UnionNode_Move` 的核心栏目生命周期已迁为专用 `saveNode/nodeBatch`:新增栏目以临时来源键完成 Parse 初始写入,再使用 PostgreSQL 事务锁原子分配 `nodeId/orderId` 并替换为旧 `sourceKey`;保存会校验同级名称/目录重复、父栏目帐套和回收状态,移动会阻断自身/下级循环并递归修正所有后代深度。回收、恢复和每批 1–100 个栏目迁移已接入 Angular;永久删除仅在没有内容、下级栏目、节点权限和模型模板引用时开放,通用保存与删除不能绕过。
 - 旧 `SpecialAdd_Save` 与 `UnionSpecial_Move` 已迁为专用 `saveSpecial/specialBatch`:新增专题使用临时来源键和 PostgreSQL 事务锁原子分配 `specId/orderId`,保存时校验全帐套名称/目录唯一、父专题归属与层级循环;删除会拒绝仍有下级专题的记录。旧 `UnionSpecial_Merge` 被明确列为数据阻塞:`CommonModel.specialId` 只存在于 Parse Schema、PostgreSQL 物理列缺失,商品主表也未迁移,无法安全改写内容/商品专题关系。
 - 旧 `ModelManage/Field/Order_Submit` 的安全部分已迁为 `saveModelMetadata/saveModelFieldMetadata/modelFieldOrder`:现有模型可编辑名称、条目文案、图标、说明和内容模板元数据,现有字段可编辑别名、提示、显示/搜索配置,并可在同模型内批量排序;`modelId/modelType/tableName/fieldId/modelId/fieldName/fieldType/sourceKey` 等结构字段通用只读。新增、复制、删除模型或字段会显式返回 501,因为旧实现同时执行 PostgreSQL 建表/改列、标签生成和模板文件写入,目标托管环境没有该 DDL/旧模板运行时。

+ 20 - 0
scripts/deploy-admin-functions.mjs

@@ -1346,6 +1346,26 @@ async function handler(request, response) {
         PublishDesign: '旧报刊可视化设计器的前端资产、布局协议与渲染运行时未迁入'
       }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧考试动作'); fail(501, 'migration_blocked: ' + blockers[action]);
     }
+    if (operation === 'legacyHelperBlocker') {
+      const action = String(input.action || ''); const blockers = {
+        Index: '旧动作会读取服务器 DBConfig.json、等待三秒并执行本地 SQL 脚本建表;云函数环境没有该文件运行时,目标数据类也已由受控迁移预置',
+        HelpInfo: '目标 Schema 缺少旧 Notification 帮助信息数据,Angular/云函数也没有旧 SignalR 管理员分组与 HelpInfo 推送协议'
+      }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧帮助动作'); fail(501, 'migration_blocked: ' + blockers[action]);
+    }
+    if (operation === 'legacyHmsBlocker') {
+      const action = String(input.action || ''); const blockers = {
+        UList: '目标用户数据没有旧 ZL_UserBase.cardType 与 ICQ 到期时间字段,不能列出主题大师 VIP 卡用户',
+        VipAdd: '目标用户数据没有旧 cardType 空值状态,不能判定可开通主题大师 VIP 卡的用户',
+        VipAddJump: '旧七档主题大师卡与当前小树 VIP 合伙人等级不是同一业务,不能复用 VIP 字段',
+        VipUpdate: '目标未保留旧 cardType 与 ICQ 卡片到期时间,无法还原编辑页',
+        VipUpdate_Submit: '目标未保留旧 cardType/ICQ,不能安全写入卡种或按原到期日、当前时间重算期限',
+        VipRenewal: '目标没有旧主题大师卡到期时间,无法展示续约上下文',
+        VipRenewal_Submit: '目标没有旧 cardType/ICQ,不能按卡种天数或指定日期续约',
+        VipOverdue: '目标没有旧主题大师卡到期时间,无法展示强制过期上下文',
+        VipOverdue_Submit: '目标没有旧 ICQ 到期时间,不能执行强制过期且不能误写当前 VIP 合伙人字段',
+        OList: '目标 Schema 缺少旧 ZL_Orderinfo 与 ZL_CartPro,无法查询主题大师 VIP 卡订单'
+      }; if (!Object.prototype.hasOwnProperty.call(blockers, action)) fail(400, '不支持的旧 Hms 动作'); fail(501, 'migration_blocked: ' + blockers[action]);
+    }
     if (operation === 'shopFareTemplates') {
       const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20)); const search = String(input.search || '').trim(); if (search.length > 100) fail(400, '运费模板搜索词不能超过 100 个字符'); const fields = await schemaFor('ShopFareTlp'); const query = new Parse.Query('ShopFareTlp'); applyTenant(query, fields, context, input.companyId); if (search) query.matches('tlpName', escapeRegex(search), 'i'); query.ascending('id'); const total = await query.count({ useMasterKey: true }); query.skip((page - 1) * pageSize); query.limit(pageSize); const results = await query.find({ useMasterKey: true }); return response.json({ success: true, data: { className: 'ShopFareTlp', page, pageSize, total, results: results.map(serializeObject) } });
     }

+ 12 - 0
scripts/generate-admin-coverage.mjs

@@ -141,6 +141,18 @@ const explicit = new Map([
   ['Exam/PublishAdd', ['partial', '现有 ContentPublish 详情可查看;报刊父项上下文与专用编辑器尚未复刻']],
   ['Exam/PublishAdd_Submit', ['partial', 'ContentPublish title/imgPath/json/pid/cdate/attachFile 写入尚未开放专用校验流程']],
   ['Exam/PublishDesign', ['blocked-infrastructure', '旧报刊可视化设计器的前端资产、布局协议与渲染运行时未迁入']],
+  ['Helper/Index', ['blocked-infrastructure', '旧动作读取服务器 DBConfig.json 并执行本地 SQL 脚本建表;云函数没有该文件运行时,目标数据类已由受控迁移预置']],
+  ['Helper/HelpInfo', ['blocked-infrastructure', '目标缺少 Notification 帮助数据与旧 SignalR 管理员分组 HelpInfo 推送协议']],
+  ['Hms/UList', ['blocked-data', '目标用户数据未保留旧 ZL_UserBase.cardType 与 ICQ 到期时间']],
+  ['Hms/VipAdd', ['blocked-data', '目标没有旧 cardType 空值状态,不能判定可开通主题大师 VIP 卡的用户']],
+  ['Hms/VipAddJump', ['blocked-data', '旧七档主题大师卡与当前小树 VIP 合伙人等级不是同一业务,不能复用 VIP 字段']],
+  ['Hms/VipUpdate', ['blocked-data', '目标未保留旧 cardType 与 ICQ 卡片到期时间,无法还原编辑页']],
+  ['Hms/VipUpdate_Submit', ['blocked-data', '目标未保留旧 cardType/ICQ,不能安全重算卡种与期限']],
+  ['Hms/VipRenewal', ['blocked-data', '目标没有旧主题大师卡到期时间,无法展示续约上下文']],
+  ['Hms/VipRenewal_Submit', ['blocked-data', '目标没有旧 cardType/ICQ,不能按卡种天数或指定日期续约']],
+  ['Hms/VipOverdue', ['blocked-data', '目标没有旧主题大师卡到期时间,无法展示强制过期上下文']],
+  ['Hms/VipOverdue_Submit', ['blocked-data', '目标没有旧 ICQ 到期时间,不能误写当前 VIP 合伙人字段']],
+  ['Hms/OList', ['blocked-data', '目标 Schema 缺少旧 ZL_Orderinfo 与 ZL_CartPro,无法查询主题大师 VIP 卡订单']],
   ['Shop / Exp/DeliverType', ['implemented', '已由 shopFareTemplates 提供帐套隔离、名称搜索、分页和旧运费规则列表']],
   ['Shop / Exp/DeliverAdd', ['implemented', '已由 Angular 商城运费模板新建/编辑抽屉替代']],
   ['Shop / Exp/DeliverAdd_Submit', ['implemented', '已由 saveShopFareTemplate 校验计价方式、规则 JSON、名称防重并原子分配旧编号']],

+ 6 - 0
scripts/smoke-admin-functions.mjs

@@ -654,6 +654,12 @@ try {
   for (const action of ['AddEngLishQuestion','Setting','VersionList','AddVersion','SchoolManage','AddSchool','ClassRoomManage','AddClassRoom','StudentList','CourseManage','AddCourse','ToScore','Papers_Add','Question_Add','Version_GetList','Version_Add','School_Add','ClassRoom_Add','Course_Add','Setting_Update','PublishDesign']) {
     const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyExamBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧考试动作 ${action} 未返回明确迁移阻塞`);
   }
+  for (const action of ['Index','HelpInfo']) {
+    const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyHelperBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧帮助动作 ${action} 未返回明确迁移阻塞`);
+  }
+  for (const action of ['UList','VipAdd','VipAddJump','VipUpdate','VipUpdate_Submit','VipRenewal','VipRenewal_Submit','VipOverdue','VipOverdue_Submit','OList']) {
+    const blocked = await callFunctionError('xiaoshu/admin/gateway', login.sessionToken, { operation: 'legacyHmsBlocker', action }, 501); if (!String(blocked.message).includes('migration_blocked')) throw new Error(`旧 Hms 动作 ${action} 未返回明确迁移阻塞`);
+  }
   const fareSuffix = Date.now().toString(36); const fareRules = [{ name: '冒烟快递', mode: '1', enabled: true, price: '10', plus: '1', free_sel: '0', free_num: '', free_money: '' }]; const createdFare = await callFunction('xiaoshu/admin/gateway', login.sessionToken, { operation: 'saveShopFareTemplate', className: 'ShopFareTlp', fields: { tlpName: `冒烟运费-${fareSuffix}`, priceMode: 1, express: JSON.stringify(fareRules), remind: '临时运费模板', remind2: '' } }); temporaryShopFareTemplateId = String(createdFare.objectId || ''); if (!temporaryShopFareTemplateId || !Number(createdFare.id) || createdFare.sourceKey !== `[["ID",${Number(createdFare.id)}]]`) throw new Error(`商城运费模板原子编号创建异常:${JSON.stringify(createdFare)}`);
   const fareList = await callFunction('xiaoshu/admin/gateway', login.sessionToken, { operation: 'shopFareTemplates', className: 'ShopFareTlp', page: 1, pageSize: 20, search: fareSuffix }); if (fareList.total !== 1 || fareList.results?.[0]?.objectId !== temporaryShopFareTemplateId) throw new Error('商城运费模板专用列表失败');
   const updatedFare = await callFunction('xiaoshu/admin/gateway', login.sessionToken, { operation: 'saveShopFareTemplate', className: 'ShopFareTlp', objectId: temporaryShopFareTemplateId, fields: { tlpName: `更新运费-${fareSuffix}`, priceMode: 1, express: JSON.stringify(fareRules), remind: '已更新', remind2: '' } }); if (updatedFare.tlpName !== `更新运费-${fareSuffix}` || Number(updatedFare.id) !== Number(createdFare.id)) throw new Error('商城运费模板专用编辑失败');

+ 24 - 24
src/app/admin/legacy-admin-coverage.generated.ts

@@ -2729,85 +2729,85 @@ export const LEGACY_ADMIN_COVERAGE: readonly LegacyAdminCoverageEntry[] = [
     "module": "Helper",
     "action": "Index",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-infrastructure",
+    "reason": "旧动作读取服务器 DBConfig.json 并执行本地 SQL 脚本建表;云函数没有该文件运行时,目标数据类已由受控迁移预置"
   },
   {
     "module": "Helper",
     "action": "HelpInfo",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-infrastructure",
+    "reason": "目标缺少 Notification 帮助数据与旧 SignalR 管理员分组 HelpInfo 推送协议"
   },
   {
     "module": "Hms",
     "action": "UList",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标用户数据未保留旧 ZL_UserBase.cardType 与 ICQ 到期时间"
   },
   {
     "module": "Hms",
     "action": "VipAdd",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有旧 cardType 空值状态,不能判定可开通主题大师 VIP 卡的用户"
   },
   {
     "module": "Hms",
     "action": "VipAddJump",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "旧七档主题大师卡与当前小树 VIP 合伙人等级不是同一业务,不能复用 VIP 字段"
   },
   {
     "module": "Hms",
     "action": "VipUpdate",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标未保留旧 cardType 与 ICQ 卡片到期时间,无法还原编辑页"
   },
   {
     "module": "Hms",
     "action": "VipUpdate_Submit",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标未保留旧 cardType/ICQ,不能安全重算卡种与期限"
   },
   {
     "module": "Hms",
     "action": "VipRenewal",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有旧主题大师卡到期时间,无法展示续约上下文"
   },
   {
     "module": "Hms",
     "action": "VipRenewal_Submit",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有旧 cardType/ICQ,不能按卡种天数或指定日期续约"
   },
   {
     "module": "Hms",
     "action": "VipOverdue",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有旧主题大师卡到期时间,无法展示强制过期上下文"
   },
   {
     "module": "Hms",
     "action": "VipOverdue_Submit",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标没有旧 ICQ 到期时间,不能误写当前 VIP 合伙人字段"
   },
   {
     "module": "Hms",
     "action": "OList",
     "occurrence": 1,
-    "status": "pending",
-    "reason": "尚未逐项迁移或完成等价性核验"
+    "status": "blocked-data",
+    "reason": "目标 Schema 缺少旧 ZL_Orderinfo 与 ZL_CartPro,无法查询主题大师 VIP 卡订单"
   },
   {
     "module": "Index",