소스 검색

fix(review): recognize verified legacy completion for stale bookings

彭峰 6 시간 전
부모
커밋
90595c9f6d

+ 9 - 5
scripts/cloud/mobile-teaching.js

@@ -25,13 +25,17 @@ function reviewStudentNameSql() {
   return `COALESCE(NULLIF(BTRIM(u."realName"),''),NULLIF(BTRIM(u."nickname"),''),NULLIF(BTRIM(u."legacyUserData"->>'HoneyName'),''),CASE WHEN POSITION('@' IN c."title")>0 THEN NULLIF(BTRIM(SPLIT_PART(c."title",'@',1)),'') END,NULLIF(BTRIM(u."username"),''),'学员 '||m."yhid"::text)`;
 }
 function notesReviewSourceSql() {
-  // Completed imports of legacy 阅读写作 (398) need no inferred billing stage to review notes.
+  // Some classified legacy bookings retain dszt=0; an exact legacy lesson proves completion without changing billing state.
   return `EXISTS (SELECT 1 FROM "CommonModel" ac JOIN "CourseAppointment" a ON a.company=ac.company AND a.id=ac."itemId"
     WHERE ac.company=d.company AND ac."modelId"=54 AND ac."generalId"::text=d.dsid::text AND COALESCE(ac.status,99)<>-2
-    AND a.szyh::text=d."userId"::text AND a.dszt IN ('11','20','30','40')
-    AND (a."courseCategoryKey" IN ('primary_writing','middle_writing','high_writing')
-      OR (NULLIF(BTRIM(a."courseCategoryKey"),'') IS NULL AND a.kcid::text='398' AND a.dszt IN ('30','40')
-        AND a."sourceKey"='legacy:model:54:general:'||ac."generalId"::text AND ac."sourceKey"=a."sourceKey")))`;
+    AND a.szyh::text=d."userId"::text
+    AND ((a."courseCategoryKey" IN ('primary_writing','middle_writing','high_writing') AND a.dszt IN ('11','20','30','40'))
+      OR ((NULLIF(BTRIM(a."courseCategoryKey"),'') IS NULL OR a."courseCategoryKey" IN ('primary_writing','middle_writing','high_writing'))
+        AND a.kcid::text='398' AND a."sourceKey"='legacy:model:54:general:'||ac."generalId"::text AND ac."sourceKey"=a."sourceKey"
+        AND (a.dszt IN ('30','40') OR (a.dszt='0' AND EXISTS (
+          SELECT 1 FROM "LessonRecord" l JOIN "CommonModel" lc ON lc.company=l.company AND lc."modelId"=59 AND lc."itemId"=l.id AND lc.status=99
+          WHERE l.company=a.company AND l.yyds::text=ac."generalId"::text AND l.xymz::text=d."userId"::text AND l.kcid::text='398'
+          AND l."sourceKey"='legacy:model:59:general:'||lc."generalId"::text AND lc."sourceKey"=l."sourceKey"))))))`;
 }
 async function appReviewPage(input,current) {
   const uid=await authorizeRequestedUser(current,input.uid),coach=number(current.get('legacyGroupId'))===3,p=mobilePage(input),includeMetadata=input.includeMetadata!==false,today=appOverviewToday();

+ 1 - 1
scripts/deploy-admin-functions.mjs

@@ -2642,7 +2642,7 @@ async function recordDetailData(input, current) {
   const order = recordAddon.dsid ? await orderDetailRow(recordAddon.dsid, current, false) : null;
   let storedWords = parseArray(recordAddon.xxqs);
   const notesReview = !storedWords.length && number(recordAddon.learned)===0 && order
-    && !order.cancelled && number(order.szyh)===number(record.userId ?? record.UserID) && [11,20,30,40].includes(number(order.dszt))
+    && !order.cancelled && number(order.szyh)===number(record.userId ?? record.UserID) && ([11,20,30,40].includes(number(order.dszt)) || notesSource)
     && (['primary_writing','middle_writing','high_writing'].includes(String(order.courseCategoryKey)) || notesSource);
   if (!storedWords.length && !notesReview) storedWords = await legacyStudyRecordWords(id,number(record.userId ?? record.UserID)); const ids = [...new Set(storedWords.map((item) => String(item && (item.GeneralID ?? item.generalId ?? item.id) || '')).filter(Boolean))]; let wordRows = [];
   if (ids.length) wordRows = await Psql.query('SELECT c.*, row_to_json(v) AS "__addon" FROM "CommonModel" c LEFT JOIN "VocabularyWord" v ON v."company" = $1 AND CAST(v."id" AS text) = CAST(c."itemId" AS text) WHERE c."company" = $1 AND CAST(c."modelId" AS text) = \'52\' AND CAST(c."generalId" AS text) = ANY($2::text[])', [DEFAULT_COMPANY_ID, ids]);

+ 1 - 0
scripts/tests/fixtures/teacher-review-schema.sql

@@ -11,3 +11,4 @@ ALTER TABLE "CommonModel" ADD COLUMN status numeric;
 ALTER TABLE "CommonModel" ADD COLUMN "sourceKey" text;
 ALTER TABLE "DailyStudyRecord" ADD COLUMN dsid text, ADD COLUMN "userId" numeric;
 CREATE TABLE "CourseAppointment" (company text,id numeric,szyh text,dszt text,"courseCategoryKey" text,kcid text,"sourceKey" text);
+CREATE TABLE "LessonRecord" (company text,id numeric,yyds text,xymz text,kcid text,"sourceKey" text);

+ 21 - 0
scripts/tests/legacy-writing-review.test.mjs

@@ -48,3 +48,24 @@ test('普通空词表、错误学生、未完课、回收和无可信来源的39
   const detail=await ctx.recordDetailData({id:100},coach);assert.equal(detail.addon[0].reviewContentMode,undefined,patch);
  }
 }));
+
+test('旧预约状态滞后但已有同一学生同一课次的真实旧完课凭据时恢复笔记复习',()=>fixture(async({db,ctx,coach})=>{
+ await db.exec(`UPDATE "CourseAppointment" SET dszt='0',"courseCategoryKey"='high_writing';
+  INSERT INTO "CommonModel"(company,"modelId","generalId","itemId",status,"sourceKey") VALUES('co',59,300,301,99,'legacy:model:59:general:300');
+  INSERT INTO "LessonRecord" VALUES('co',301,'200','20','398','legacy:model:59:general:300');`);
+ const page=()=>ctx.appReviewPage({scope:'today',includeIncome:false},coach);
+ assert.equal((await page()).page.itemCount,2);
+ assert.equal((await ctx.recordDetailData({id:100},coach)).addon[0].reviewContentMode,'notes');
+ assert.equal((await db.query('SELECT dszt FROM "CourseAppointment"')).rows[0].dszt,'0');
+ for(const patch of [
+  `UPDATE "LessonRecord" SET xymz='21'`,
+  `UPDATE "LessonRecord" SET yyds='999'`,
+  `UPDATE "LessonRecord" SET kcid='203'`,
+  `UPDATE "LessonRecord" SET "sourceKey"='cloud:complete:new'`,
+  `UPDATE "CommonModel" SET status=-2 WHERE "modelId"=59`,
+ ]){
+  await db.exec(`UPDATE "LessonRecord" SET xymz='20',yyds='200',kcid='398',"sourceKey"='legacy:model:59:general:300';UPDATE "CommonModel" SET status=99 WHERE "modelId"=59;`+patch);
+  assert.equal((await page()).page.itemCount,0,patch);
+  assert.equal((await ctx.recordDetailData({id:100},coach)).addon[0].reviewContentMode,undefined,patch);
+ }
+}));