|
|
@@ -35,6 +35,23 @@ test('重算已有明细保留调整,异常禁止审核,审计失败全部
|
|
|
test('审核永久锁定、登记发放校验凭据',async()=>{batch=await command('review',batch.objectId,{version:batch.version,month:'2026-09',lines:[line],sourceVersion:await version()},'review-good');await assert.rejects(command('adjustment',batch.objectId,{version:batch.version,coachId:30,amount:1},'locked-adjust'),/草稿/);await assert.rejects(command('mark-paid',batch.objectId,{version:batch.version},'paid-missing'),/凭证/);batch=await command('mark-paid',batch.objectId,{version:batch.version,paymentMethod:'转账',paymentReference:'test-001'},'paid-good');assert.equal(batch.status,'paid');});
|
|
|
test('新完课入工资、事实不覆盖快照、预览和结算来源一致',async()=>{await db.exec(`INSERT INTO "CourseAppointment" ("objectId",company,id,szyh,kcid,pl,dszt,"teacherPaySnapshot","courseCategoryKey","durationMinutes",jffs) VALUES ('appointment','co',5,'20','100','30','11',40,'word',60,'线上'); INSERT INTO "CommonModel" ("objectId",company,"modelId","itemId","generalId",title,subtitle,status) VALUES ('ac','co',54,5,5,'学员','单词',99),('lc','co',59,6,6,'学员','单词',99); INSERT INTO "LessonRecord" ("objectId",company,id,jsmz,xymz,yyds,kcid,kclx,"lessonAt","teacherPaySnapshot") VALUES ('lesson','co',6,'30','20','5','100','2','2026-09-01 07:30+08',40); INSERT INTO "LessonRecordFact" ("objectId",company,"legacyGeneralId","teacherPaySnapshot") VALUES ('f','co',6,99);` .replace(',"teacherPaySnapshot") VALUES (\'f\',\'co\',6,99)',') VALUES (\'f\',\'co\',6)'));
|
|
|
const snapshot=await context.payrollCanonicalSnapshot(manager,{});assert.equal(snapshot.items.length,1);assert.equal(snapshot.items[0].teacherPaySnapshot,40);const preview=await context.payrollPreviewData(manager,{},'2026-09',true);assert.equal(preview.baseAmount,40);});
|
|
|
+test('作废课次留在核对来源中,但不进入收益、工资预览或异常数',async()=>{
|
|
|
+ await db.exec(`INSERT INTO "CommonModel" ("objectId",company,"modelId","itemId","generalId",status) VALUES ('void-common','co',59,999,999,-2);
|
|
|
+ INSERT INTO "LessonRecord" ("objectId",company,id,jsmz,xymz,yyds,kcid,kclx,"lessonAt","teacherPaySnapshot") VALUES ('void-lesson','co',999,'30','20','5','100','2','2026-09-01 16:06+08',40);`);
|
|
|
+ try{
|
|
|
+ const snapshot=await context.payrollCanonicalSnapshot(manager,{});
|
|
|
+ assert.equal(snapshot.items.find(row=>row.objectId==='void-lesson').commonStatus,-2);
|
|
|
+ for(const includeUsed of [true,false]){
|
|
|
+ const preview=await context.payrollPreviewData(manager,{},'2026-09',includeUsed);
|
|
|
+ assert.equal(preview.baseAmount,40);
|
|
|
+ assert.equal(preview.lessonCount,1);
|
|
|
+ assert.equal(preview.exceptionCount,0);
|
|
|
+ assert.ok(!preview.lines.some(row=>row.standardLessonKey==='legacy:999'));
|
|
|
+ }
|
|
|
+ }finally{
|
|
|
+ await db.exec(`DELETE FROM "LessonRecord" WHERE "objectId"='void-lesson'; DELETE FROM "CommonModel" WHERE "objectId"='void-common';`);
|
|
|
+ }
|
|
|
+});
|
|
|
test('规则同日修订保留旧版本,禁止直接覆盖',async()=>{const req={categoryKey:'word',durationMinutes:60,deliveryMode:'online',teacherPay:40,effectiveFrom:'2026-09-01',creditCosts:[{account:'SilverCoin',amount:1}],active:true,supersedesId:'',reason:'调整规则'};const a=(await one('SELECT xs_payroll_rule(\'co\',\'teacher\',$1) r',[JSON.stringify(req)])).r;const b=(await one('SELECT xs_payroll_rule(\'co\',\'teacher\',$1) r',[JSON.stringify({...req,teacherPay:99,supersedesId:a.objectId})])).r;assert.equal(b.revision,2);await assert.rejects(q('UPDATE "TeachingCompensationRule" SET "teacherPay"=1 WHERE "objectId"=$1',[a.objectId]),/不可改写/);const rows=await context.teachingRuleRows(manager,{});assert.equal(rows.filter(r=>r.categoryKey==='word'&&r.durationMinutes===60&&r.effectiveFrom==='2026-09-01'&&r.deliveryMode==='online'&&r.active).length,1);});
|
|
|
test('按门店和老师导出,排除记录单列,汇总明细一致',async()=>{const ctx=vm.createContext({Date,Psql:{query:q,one,oneOrNone:one}});vm.runInContext(source.match(/const operationsGatewayCode = String.raw`([\s\S]*?)\n`;/)[1]+readFileSync(new URL('../cloud/payroll-integrity.js',import.meta.url),'utf8'),ctx);ctx.payrollDetailData=async()=>({month:'2026-09',lines:[{...line,storeId:10},{...line,coachId:31,storeId:11},{...line,storeId:10,excluded:true,exclusionReason:'作废'}]});const data=await ctx.payrollExportData({}, {targetId:'batch',storeId:10,coachId:30});assert.equal(data.lines.length,1);assert.equal(data.excluded.length,1);assert.equal(data.summary.payableAmount,40);});
|
|
|
test('课时差额通过账户事务退回一次,预览扣除已退金额',async()=>{await db.exec(`INSERT INTO "CourseAppointment" ("objectId",company,id,szyh,dszt,"courseCategoryKey","durationMinutes",yysj) VALUES ('review-appointment','co',7,'20','11','review',30,'2026-09-02 09:00');INSERT INTO "CommonModel" ("objectId",company,"modelId","itemId","generalId",status) VALUES ('review-common','co',54,7,7,99);INSERT INTO "UserUserPoint" ("objectId",company,"sourceKey",score) VALUES ('charge','co','cloud:e_order_update:7:period',-1);`);const before=Number((await one(`SELECT "legacyUserData"->>'UserPoint' b FROM "_User" WHERE "objectId"='student'`)).b);const run=async()=>(await one('SELECT xs_payroll_refunds(\'co\',\'teacher\',\'["review-common"]\',\'核对差额\') r')).r;assert.equal((await run()).refundTotal,.5);assert.equal((await run()).refundTotal,0);assert.equal(Number((await one(`SELECT "legacyUserData"->>'UserPoint' b FROM "_User" WHERE "objectId"='student'`)).b),before+.5);const rows=await context.creditReconciliationPreview(manager,{});assert.equal(rows.items[0].adjustment,0);assert.equal(rows.items[0].status,'已处理');});
|