Quellcode durchsuchen

fix(admin): sort cross-date appointments by scheduled time descending

彭峰 vor 2 Stunden
Ursprung
Commit
7dd2d01194

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

@@ -3420,7 +3420,8 @@ async function businessRecordSqlPage(kind,sql,args,input){
  let where='';if(paging.search){values.push('%'+paging.search.replace(/[\\%_]/g,'\\$&')+'%');where=" WHERE LOWER(CONCAT_WS(' ',"+terms.join(',')+')) LIKE $'+values.length+" ESCAPE E'\\\\'";}
  if(input._storeId){values.push(String(input._storeId));where+=(where?' AND ':' WHERE ')+"record.\"studentLegacy\"->>'ParentUserID'=$"+values.length;}
  values.push(paging.pageSize,(paging.page-1)*paging.pageSize);
- const query='WITH filtered AS MATERIALIZED (SELECT record.* FROM ('+base+') record'+where+') SELECT (SELECT count(*)::int FROM filtered) AS total,COALESCE((SELECT jsonb_agg(page) FROM (SELECT * FROM filtered ORDER BY "commonUpdatedAt" DESC NULLS LAST,"targetId" DESC NULLS LAST,"objectId" DESC LIMIT $'+(values.length-1)+' OFFSET $'+values.length+") page),'[]'::jsonb) AS items";
+ const order=kind==='appointments'?"COALESCE(CASE WHEN \"yysj\" ~ '[ T][0-9]{1,2}:[0-9]{2}' THEN xs_payroll_time(\"yysj\") END,xs_payroll_time(NULLIF(\"bxrq\",'')||' '||COALESCE(NULLIF(\"sdsd\",''),'00:00'))) DESC NULLS LAST,\"targetId\" DESC NULLS LAST,\"objectId\" DESC":'"commonUpdatedAt" DESC NULLS LAST,"targetId" DESC NULLS LAST,"objectId" DESC';
+ const query='WITH filtered AS MATERIALIZED (SELECT record.* FROM ('+base+') record'+where+') SELECT (SELECT count(*)::int FROM filtered) AS total,COALESCE((SELECT jsonb_agg(page) FROM (SELECT * FROM filtered ORDER BY '+order+' LIMIT $'+(values.length-1)+' OFFSET $'+values.length+") page),'[]'::jsonb) AS items";
  const result=(await Psql.query(query,values))[0];return {total:number(result?.total),items:Array.isArray(result?.items)?result.items:[]};
 }
 async function businessRows(kind, context, input) {

+ 26 - 0
scripts/tests/business-record-pagination.test.mjs

@@ -13,3 +13,29 @@ test('完整来源超过5000条仍可分页、搜索较早记录,空末页保
   for(const search of ['其他帐套','%','_'])assert.equal((await ctx.businessRecordSqlPage('learning-records',sql,['co',''],{search})).total,0);
  }finally{await db.close();}
 });
+
+test('跨日期预约按实际预约时间倒序后分页,忽略更新时间并兼容分开的日期时间',async()=>{
+ const db=new PGlite();try{
+  const payrollSql=readFileSync(new URL('../sql/payroll-integrity.sql',import.meta.url),'utf8');
+  await db.exec(payrollSql.match(/CREATE OR REPLACE FUNCTION xs_payroll_time\([\s\S]*?\$\$;/)[0]);
+  await db.exec(`CREATE TABLE appointments(company text,"objectId" text,"targetId" text,"generalId" int,"commonUpdatedAt" timestamptz,
+   "studentObjectId" text,"studentRealName" text,"studentNickname" text,"studentUsername" text,"studentLegacy" jsonb,"courseTitle" text,
+   szyh text,kcid text,"coachObjectId" text,"coachRealName" text,"coachNickname" text,"coachUsername" text,"coachLegacy" jsonb,"courseCategoryKey" text,"courseCategoryName" text,dszt text,dslx text,yysj text,bxrq text,sdsd text)`);
+  const records=[
+   ['december','2026-12-05 19:30','',''],['february','2027-02-06 19:30','',''],
+   ['january','2027-01-02 19:30','',''],['later-time','2027-01-02 20:30','',''],
+   ['separate-date','','2027-01-23','19:30'],['fallback-date','invalid','2027-01-09','19:30'],
+   ['date-only','2027-01-02','2027-01-02','21:00'],['utc','2027-01-02T12:00:00Z','',''],['unknown','invalid','',''],['z-tie','2027-02-06 19:30','',''],
+  ];
+  for(const [index,[id,scheduled,date,time]] of records.entries())await db.query(`INSERT INTO appointments(company,"objectId","targetId","generalId","commonUpdatedAt","studentRealName","studentLegacy","coachLegacy",yysj,bxrq,sdsd,dszt)
+   VALUES('co',$1,$1,$2::int,'2026-11-01'::timestamptz+$2::int*interval '1 hour','目标学员','{}','{}',$3,$4,$5,$6)`,[id,index,scheduled,date,time,index%2?'0':'40']);
+  await db.exec(`INSERT INTO appointments(company,"objectId","targetId","commonUpdatedAt","studentRealName",yysj) VALUES('other','private','private',now(),'目标学员','2050-01-01 00:00'),('co','different','different',now(),'其他学员','2040-01-01 00:00')`);
+  const ctx=vm.createContext({Date,Psql:{query:async(sql,args)=>(await db.query(sql,args)).rows}});vm.runInContext(source,ctx);
+  const items=[];
+  for(let page=1;page<=Math.ceil(records.length/3);page++){
+   const result=await ctx.businessRecordSqlPage('appointments','SELECT * FROM appointments WHERE company=$1',['co'],{page,pageSize:3,search:'目标学员'});
+   assert.equal(result.total,records.length);items.push(...result.items);
+  }
+  assert.deepEqual(items.map(row=>row.targetId),['z-tie','february','separate-date','fallback-date','date-only','later-time','utc','january','december','unknown']);
+ }finally{await db.close();}
+});