Explorar o código

perf(stores): avoid unused payroll versions in read-only overview

彭峰 hai 2 días
pai
achega
d025c7b58c
Modificáronse 2 ficheiros con 8 adicións e 3 borrados
  1. 3 3
      scripts/cloud/payroll-integrity.js
  2. 5 0
      scripts/tests/store-payroll.test.mjs

+ 3 - 3
scripts/cloud/payroll-integrity.js

@@ -30,7 +30,7 @@ async function payrollCanonicalSnapshot(context,input,options={}) {
   // A display-only estimate cannot authorize settlement. Financial reads and all
   // commands keep the full source version and cutoff checks by default.
   const forDashboard=options.forDashboard===true;
-  const [snapshot,sync]=await Promise.all([payrollSql('SELECT COALESCE(jsonb_agg(s),\'[]\'::jsonb) AS rows,'+(forDashboard?"''::text":'xs_payroll_source_version($1)')+' AS version FROM xs_payroll_sources($1) s',[co]),forDashboard?Promise.resolve({state:'not_checked',settlementReady:false,datasets:[],message:'工作台仅展示实时预估金额;结算前须在工资模块核验来源'}):payrollSettlementSyncStatus(context,input)]);
+  const [snapshot,sync]=await Promise.all([payrollSql('SELECT COALESCE(jsonb_agg(s),\'[]\'::jsonb) AS rows,'+((forDashboard||options.omitSourceVersion===true)?"''::text":'xs_payroll_source_version($1)')+' AS version FROM xs_payroll_sources($1) s',[co]),forDashboard?Promise.resolve({state:'not_checked',settlementReady:false,datasets:[],message:'工作台仅展示实时预估金额;结算前须在工资模块核验来源'}):payrollSettlementSyncStatus(context,input)]);
   const users=await Psql.query('SELECT * FROM "_User" WHERE "company"=$1',[co]);
   const map=new Map(users.map(u=>[number(u.legacyUserId||legacyOf(u).UserID),u]));
   const items=(snapshot.rows||[]).map(row=>{
@@ -51,7 +51,7 @@ function payrollCalculatedLine(item,rates,settlementMonth,rules,mappings){
 async function payrollPreviewData(context,input,month,includeUsed=false,requireLive=false,options={}){
  if(!/^\d{4}-(0[1-9]|1[0-2])$/.test(month))fail(400,'工资月份无效');
  if(context.storeId)input={...input,storeId:context.storeId};
- const co=payrollTenant(context,input),[source,rates,rules,mappings,used]=await Promise.all([payrollCanonicalSnapshot(context,{...input,month},{forDashboard:options.forDashboard===true&&includeUsed&&!requireLive}),payRateRows(context,input),teachingRuleRows(context,input),courseTeachingRows(context,input),Psql.query('SELECT "standardLessonKey","batchId","excluded" FROM "PayrollLine" WHERE "company"=$1 AND "kind"=\'lesson\' AND COALESCE("excluded",false)=false',[co])]);
+ const co=payrollTenant(context,input),[source,rates,rules,mappings,used]=await Promise.all([payrollCanonicalSnapshot(context,{...input,month},{forDashboard:options.forDashboard===true&&includeUsed&&!requireLive,omitSourceVersion:options.omitSourceVersion===true&&includeUsed&&!requireLive}),payRateRows(context,input),teachingRuleRows(context,input),courseTeachingRows(context,input),Psql.query('SELECT "standardLessonKey","batchId","excluded" FROM "PayrollLine" WHERE "company"=$1 AND "kind"=\'lesson\' AND COALESCE("excluded",false)=false',[co])]);
  const other=new Set(used.filter(r=>r.batchId!==input.targetId).map(r=>r.standardLessonKey)),mappingMap=new Map(mappings.filter(r=>r.confirmed).map(r=>[r.courseId,r]));
  const previous=month.endsWith('-01')?(Number(month.slice(0,4))-1)+'-12':month.slice(0,5)+String(Number(month.slice(5))-1).padStart(2,'0');
  const allowLate=!includeUsed&&Boolean((await Psql.one('SELECT EXISTS(SELECT 1 FROM "PayrollBatch" WHERE "company"=$1 AND "month"=$2 AND COALESCE("scopeStoreId",0)=$3 AND "status" IN (\'reviewed\',\'paid\')) AS value',[co,previous,context.storeId||0])).value);
@@ -66,7 +66,7 @@ async function payrollPage(context,input){return storePayrollPage(context,input)
 async function payrollDetailData(context,input,targetId){const co=payrollTenant(context,input),batch=await Psql.oneOrNone('SELECT * FROM "PayrollBatch" WHERE "company"=$1 AND "objectId"=$2',[co,targetId]);if(!batch||context.storeId&&number(batch.scopeStoreId)!==context.storeId)fail(404,'工资单不存在或不属于本店');const rows=await Psql.query('SELECT * FROM "PayrollLine" WHERE "company"=$1 AND "batchId"=$2 ORDER BY "lessonAt" NULLS LAST,"objectId"',[co,targetId]),lines=rows.map(row=>({...payrollLine(row),standardLessonKey:row.standardLessonKey,exclusionReason:row.exclusionReason,coachMobile:row.coachMobile}));return{...payrollSummary(batch),version:batch.version,lines,exceptions:lines.filter(r=>r.exception&&!r.excluded),coachItems:payrollCoachItems(lines,{})};}
 async function payrollOverviewData(context,input){
  if(context.storeId)input={...input,storeId:context.storeId};
- const co=payrollTenant(context,input),month=text(input.month)||businessDate().slice(0,7),income=await payrollPreviewData(context,{...input,storeId:0,search:''},month,true),batches=(await payrollPage(context,{...input,month,page:1,pageSize:100})).items;
+ const co=payrollTenant(context,input),month=text(input.month)||businessDate().slice(0,7),income=await payrollPreviewData(context,{...input,storeId:0,search:''},month,true,false,{omitSourceVersion:Boolean(context.storeId)}),batches=(await payrollPage(context,{...input,month,page:1,pageSize:100})).items;
  const batch=batches[0],settlement=batch?await payrollDetailData(context,{},batch.objectId):null;
  const visible=payrollResult(month,income.lines,income.sourceStatus,input),settlementTotals=settlement?payrollResult(month,settlement.lines,income.sourceStatus,input):null;
  const stores=context.storeId?[{storeId:context.storeId,storeName:storeNameOf((context.store||context.current).toJSON())}]:await Psql.query('SELECT "legacyUserId" AS "storeId",COALESCE(NULLIF("realName",\'\'),NULLIF("nickname",\'\'),"username") AS "storeName" FROM "_User" WHERE "company"=$1 AND ("identityType"=\'store\' OR "legacyGroupId"=2) ORDER BY "legacyUserId"',[co]);

+ 5 - 0
scripts/tests/store-payroll.test.mjs

@@ -61,3 +61,8 @@ test('本店审核与登记发放,保持来源校验和审核锁定',async()=>
  await db.exec(`UPDATE "_User" SET "isDisabled"=true WHERE "objectId"='store'`);
  await assert.rejects(run('admin-a',10,'mark-paid',a.objectId,{version:a.version,paymentMethod:'测试转账',paymentReference:'测试凭据'},'paid-store-a'),/停用/);
 });
+test('工资展示可省去未使用的来源版本计算,仍保留结算状态;默认命令读取保留完整版本',async()=>{
+ const c=vm.createContext({Date,Psql:{query:async()=>[]}});vm.runInContext(source.match(/const operationsGatewayCode = String.raw`([\s\S]*?)\n`;/)[1]+readFileSync(new URL('../cloud/payroll-integrity.js',import.meta.url),'utf8'),c);c.companyIdOf=()=> 'co';let sql='',checks=0;c.payrollSql=async q=>{sql=q;return{rows:[],version:''};};c.payrollSettlementSyncStatus=async()=>{checks++;return{settlementReady:false,message:'待核验'};};
+ const display=await c.payrollCanonicalSnapshot(manager,{}, {omitSourceVersion:true});assert.equal(display.sourceStatus.sync.settlementReady,false);assert.ok(!sql.includes('xs_payroll_source_version'));
+ await c.payrollCanonicalSnapshot(manager,{});assert.ok(sql.includes('xs_payroll_source_version'));assert.equal(checks,2);
+});