|
|
@@ -39,7 +39,7 @@ test('规则同日修订保留旧版本,禁止直接覆盖',async()=>{const re
|
|
|
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,'已处理');});
|
|
|
test('历史预留同事务、重复提交不重复预留,异常时整体回滚',async()=>{await db.exec(`INSERT INTO "CourseAppointment" ("objectId",company,id,szyh,dszt,kcid,yysj,"updatedAt") VALUES ('future','co',8,'20','0','100','2099-01-01 10:00',now());INSERT INTO "CommonModel" ("objectId",company,"modelId","itemId","generalId",status) VALUES ('future-common','co',54,8,8,99);`);const a=await one(`SELECT "updatedAt" FROM "CourseAppointment" WHERE "objectId"='future'`);const i={appointmentId:'future-common',expectedUpdatedAt:a.updatedAt,status:'ready',reason:'',categoryKey:'word',categoryName:'单词课',durationMinutes:30,deliveryMode:'online',teachingRuleId:'saved',teacherPay:15,creditCosts:[{account:'Purse',amount:1}]};const run=async(items)=>(await one('SELECT xs_payroll_reservations(\'co\',\'teacher\',$1,\'补建预留\',$2) r',[JSON.stringify(items),await version()])).r;await assert.rejects(run([i,{...i,appointmentId:'missing'}]),/不存在/);assert.equal((await one(`SELECT count(*)::int n FROM "CourseCreditReservation"`)).n,0);assert.equal((await run([i])).reserved,1);const updated=await one(`SELECT "updatedAt" FROM "CourseAppointment" WHERE "objectId"='future'`);assert.equal((await run([{...i,expectedUpdatedAt:updated.updatedAt}])).reserved,0);});
|
|
|
-test('阅读写作只修改指定预约,必须有证据并保留其他学生分类',async()=>{await db.exec(`UPDATE "CommonModel" SET subtitle='阅读写作' WHERE "objectId"='future-common';`);const a=await one(`SELECT "updatedAt" FROM "CourseAppointment" WHERE "objectId"='future'`);await assert.rejects(q('SELECT xs_payroll_writing_mapping(\'co\',\'teacher\',\'future-common\',\'primary_writing\',\'小学五年级\',$1,\'核实年级\')',[a.updatedAt]),/快照/);await db.exec(`INSERT INTO "CourseAppointment" ("objectId",company,id,szyh,dszt,kcid,yysj,"updatedAt") VALUES ('writing','co',9,'20','0','398','2099-01-02 10:00',now());INSERT INTO "CommonModel" ("objectId",company,"modelId","itemId","generalId",status,subtitle) VALUES ('writing-common','co',54,9,9,99,'阅读写作');`);const w=await one(`SELECT "updatedAt" FROM "CourseAppointment" WHERE "objectId"='writing'`);await q('SELECT xs_payroll_writing_mapping(\'co\',\'teacher\',\'writing-common\',\'primary_writing\',\'用户核实小学\',$1,\'核实年级\')',[w.updatedAt]);assert.equal((await one(`SELECT "courseCategoryKey" k FROM "CourseAppointment" WHERE "objectId"='writing'`)).k,'primary_writing');});
|
|
|
+test('阅读写作只修改指定预约,确认后不再返回待核对列表',async()=>{await db.exec(`UPDATE "CommonModel" SET subtitle='阅读写作' WHERE "objectId"='future-common';`);const a=await one(`SELECT "updatedAt" FROM "CourseAppointment" WHERE "objectId"='future'`);await assert.rejects(q('SELECT xs_payroll_writing_mapping(\'co\',\'teacher\',\'future-common\',\'primary_writing\',\'小学五年级\',$1,\'核实年级\')',[a.updatedAt]),/快照/);await db.exec(`INSERT INTO "CourseAppointment" ("objectId",company,id,szyh,dszt,kcid,yysj,"updatedAt") VALUES ('writing','co',9,'20','0','398','2099-01-02 10:00',now());INSERT INTO "CommonModel" ("objectId",company,"modelId","itemId","generalId",status,subtitle) VALUES ('writing-common','co',54,9,9,99,'阅读写作');`);assert.equal((await context.writingMappingPage(manager,{})).items.some(row=>row.appointmentId==='writing-common'),true);const w=await one(`SELECT "updatedAt" FROM "CourseAppointment" WHERE "objectId"='writing'`);await q('SELECT xs_payroll_writing_mapping(\'co\',\'teacher\',\'writing-common\',\'primary_writing\',\'用户核实小学\',$1,\'核实年级\')',[w.updatedAt]);assert.equal((await one(`SELECT "courseCategoryKey" k FROM "CourseAppointment" WHERE "objectId"='writing'`)).k,'primary_writing');assert.equal((await context.writingMappingPage(manager,{})).items.some(row=>row.appointmentId==='writing-common'),false);});
|
|
|
test('奖励导出快照及游标不受分页期间新增记录影响',async()=>{for(let i=0;i<3;i++)await q('INSERT INTO "ReviewReward" ("objectId","company","taskId","status","amount") VALUES ($1,\'co\',$1,\'historical\',0)',['receipt-'+i]);const page=await context.reviewRewardPage('co',{exportMode:true,pageSize:2});await q(`INSERT INTO "ReviewReward" ("objectId",company,"taskId",status,amount) VALUES ('receipt-new','co','receipt-new','historical',0)`);const next=await context.reviewRewardPage('co',{exportMode:true,pageSize:2,snapshot:page.snapshot,afterId:page.nextCursor});assert.equal(page.total,3);assert.equal(next.total,3);assert.equal(next.items.length,1);assert.notEqual(next.items[0].objectId,'receipt-new');});
|
|
|
test('工资表禁止直接绕过事务写入,迁移重跑不改变已发放金额',async()=>{await assert.rejects(q(`INSERT INTO "PayrollBatch" ("objectId",company,month,status) VALUES ('bypass','co','2099-01','paid')`),/业务事务/);const before=await one(`SELECT "payableAmount",status FROM "PayrollBatch" WHERE "objectId"=$1`,[batch.objectId]);await db.exec(migration);assert.deepEqual(await one(`SELECT "payableAmount",status FROM "PayrollBatch" WHERE "objectId"=$1`,[batch.objectId]),before);});
|
|
|
test('历史课次门店名称跟随保存的门店编号而不是教师现门店',async()=>{const ctx=vm.createContext({Date,Psql:{one:async()=>({rows:[{objectId:'history',jsmz:30,szmdid:11}],version:'v'}),query:async()=>[{objectId:'teacher',legacyUserId:30,legacyUserData:{ParentUserID:10},realName:'老师'},{objectId:'old-store',legacyUserId:11,realName:'历史门店'},{objectId:'new-store',legacyUserId:10,realName:'现门店'}]}});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.companyIdOf=()=> 'co';const result=await ctx.payrollCanonicalSnapshot(manager,{});assert.equal(result.items[0].storeId,11);assert.equal(result.items[0].storeName,'历史门店');});
|