Explorar o código

Show verified legacy trial reviews as non-payable

彭峰 hai 5 horas
pai
achega
951c2a06b5
Modificáronse 2 ficheiros con 18 adicións e 1 borrados
  1. 5 1
      scripts/cloud/review-rewards.js
  2. 13 0
      scripts/tests/review-rewards.test.mjs

+ 5 - 1
scripts/cloud/review-rewards.js

@@ -39,5 +39,9 @@ async function reviewRewardPage(companyId, input) {
   const where = clauses.join(' AND ');
   const summary = await Psql.one('SELECT COUNT(*)::int total, COUNT(*) FILTER (WHERE r."status"=\'earned\')::int "reviewCount", COALESCE(SUM(r."amount"),0) "reviewAmount",COUNT(*) FILTER (WHERE r."status"=\'historical\')::int "historicalCount",COUNT(*) FILTER (WHERE r."status"=\'exception\')::int "exceptionCount" FROM "ReviewRewardIncome" r WHERE '+summaryWhere, summaryValues);
   const rows = await Psql.query('SELECT r.*,m."sourceKey" AS "taskSourceKey",m."fxzt" AS "taskStatus",m."rewardDuplicate" AS "taskDuplicate",l."batchId",b."month" AS "settlementMonth",b."status" AS "batchStatus" FROM "ReviewRewardIncome" r LEFT JOIN "MemoryPracticeRecord" m ON m."company"=r."company" AND m."objectId"=r."taskId" LEFT JOIN "PayrollLine" l ON l."company"=r."company" AND l."reviewRewardId"=r."objectId" AND l."kind"=\'review_reward\' AND COALESCE(l."excluded",false)=false LEFT JOIN "PayrollBatch" b ON b."company"=l."company" AND b."objectId"=l."batchId" WHERE '+where+(input.exportMode?' ORDER BY r."objectId"':' ORDER BY r."completedAt" DESC NULLS FIRST,r."objectId"')+' LIMIT $'+(values.length+1)+' OFFSET $'+(values.length+2), [...values,pageSize,input.exportMode?0:(page-1)*pageSize]);
-  return { ruleVersion:'beijing-day-v1',snapshot,nextCursor:input.exportMode&&rows.length===pageSize?rows[rows.length-1].objectId:'',items: rows.map((row)=>{const completedAt=row.completedAt?new Date(row.completedAt).toISOString():'',businessDay=completedAt?new Date(completedAt).toLocaleDateString('sv-SE',{timeZone:'Asia/Shanghai'}):'',canConfirm=row.status==='historical'&&!row.exception&&businessDay>='2026-09-01'&&businessDay===row.plannedDate&&row.taskStatus==='1'&&row.taskDuplicate!==true&&(/^(legacy:model:60:|legacy-sync:memory-records:addon:)/.test(row.taskSourceKey||''));const {taskSourceKey,taskStatus,taskDuplicate,...visible}=row;return {...visible,amount:Number(row.amount),completedAt,canConfirm,canCorrect:row.status==='earned'&&businessDay>row.plannedDate&&Boolean(row.plannedDate),statusLabel:row.correctionId?(Number(row.amount)>0?'已按课时时长纠正':'已纠正,净收入 ¥0'):row.status==='ineligible'?(row.ineligibleReason==='self_study'?'学生自习,不计薪':row.ineligibleReason==='duplicate_review'?'重复复习,不重复计薪':'逾期补做,不计薪'):row.status==='historical'?'历史待核对':row.status==='exception'?'异常待核对':({draft:'已入草稿',reviewed:'已审核',paid:'已发放'}[row.batchStatus]||'待结算')};}), total:Number(summary.total), page, pageSize, summary:{...summary,reviewAmount:Number(summary.reviewAmount)} };
+  return { ruleVersion:'beijing-day-v1',snapshot,nextCursor:input.exportMode&&rows.length===pageSize?rows[rows.length-1].objectId:'',items: rows.map((row)=>{const completedAt=row.completedAt?new Date(row.completedAt).toISOString():'',businessDay=completedAt?new Date(completedAt).toLocaleDateString('sv-SE',{timeZone:'Asia/Shanghai'}):'',canConfirm=row.status==='historical'&&!row.exception&&businessDay>='2026-09-01'&&businessDay===row.plannedDate&&row.taskStatus==='1'&&row.taskDuplicate!==true&&(/^(legacy:model:60:|legacy-sync:memory-records:addon:)/.test(row.taskSourceKey||''));const {taskSourceKey,taskStatus,taskDuplicate,...visible}=row;
+    const verifiedTrial = row.status==='ineligible' && row.ineligibleReason==='legacy_trial_no_fee';
+    const trialLabel = '旧系统体验课复习,不计薪';
+    // Existing operations clients read the explanation column from exception.
+    return {...visible,exception:verifiedTrial?trialLabel:visible.exception,amount:Number(row.amount),completedAt,canConfirm,canCorrect:row.status==='earned'&&businessDay>row.plannedDate&&Boolean(row.plannedDate),statusLabel:verifiedTrial?trialLabel:row.correctionId?(Number(row.amount)>0?'已按课时时长纠正':'已纠正,净收入 ¥0'):row.status==='ineligible'?(row.ineligibleReason==='self_study'?'学生自习,不计薪':row.ineligibleReason==='duplicate_review'?'重复复习,不重复计薪':'逾期补做,不计薪'):row.status==='historical'?'历史待核对':row.status==='exception'?'异常待核对':({draft:'已入草稿',reviewed:'已审核',paid:'已发放'}[row.batchStatus]||'待结算')};}), total:Number(summary.total), page, pageSize, summary:{...summary,reviewAmount:Number(summary.reviewAmount)} };
 }

+ 13 - 0
scripts/tests/review-rewards.test.mjs

@@ -87,6 +87,19 @@ test('老师端收入、奖励列表和工资来源同一凭据;分页筛选
  assert.equal(await ctx.reviewIncomeTotal('other',10),0);
 });
 
+test('旧体验课零费用核定在后台显示真实原因',async()=>{
+ const result={objectId:'trial',status:'ineligible',amount:'0.00',ineligibleReason:'legacy_trial_no_fee',exception:'',correctionReason:'原预约为体验课'};
+ const ctx={Psql:{query:async()=>[result],one:async()=>({total:1,reviewAmount:0})}};
+ vm.createContext(ctx);
+ vm.runInContext(readFileSync(new URL('../cloud/review-rewards.js',import.meta.url),'utf8'),ctx);
+ const {items:[row]}=await ctx.reviewRewardPage('c',{status:'ineligible'});
+ assert.equal(row.statusLabel,'旧系统体验课复习,不计薪');
+ assert.equal(row.exception,row.statusLabel);
+ assert.equal(row.correctionReason,result.correctionReason);
+ assert.equal(row.amount,0);
+ assert.equal(row.canConfirm,false);
+});
+
 test('工资汇总分开课酬、奖励与调整;筛选和导出不泄漏其他老师或排除项',()=>{
  const source=readFileSync(new URL('../deploy-admin-functions.mjs',import.meta.url),'utf8');
  const code=source.match(/const operationsGatewayCode = String.raw`([\s\S]*?)\n`;/)[1];