|
@@ -40,7 +40,7 @@ async function payrollCanonicalSnapshot(context,input,options={}) {
|
|
|
studentName:student?displayName(student):item.studentName,storeId,storeName:store?displayName(store):item.storeName||'未绑定门店',
|
|
studentName:student?displayName(student):item.studentName,storeId,storeName:store?displayName(store):item.storeName||'未绑定门店',
|
|
|
teacherPaySnapshot:row.teacherPaySnapshot==null?null:Number(row.teacherPaySnapshot)};
|
|
teacherPaySnapshot:row.teacherPaySnapshot==null?null:Number(row.teacherPaySnapshot)};
|
|
|
});
|
|
});
|
|
|
- return {items,version:snapshot.version,sourceStatus:{available:true,mode:'parse',message:'新数据库有效完课记录(含历史补充)',total:items.length,refreshedAt:new Date().toISOString(),complete:sync.settlementReady,stale:!sync.settlementReady,cacheHit:false,sync}};
|
|
|
|
|
|
|
+ const scopedItems=context.storeId?items.filter(i=>i.storeId===context.storeId):items;return {items:scopedItems,version:snapshot.version,sourceStatus:{available:true,mode:'parse',message:'新数据库有效完课记录(含历史补充)',total:scopedItems.length,refreshedAt:new Date().toISOString(),complete:sync.settlementReady,stale:!sync.settlementReady,cacheHit:false,sync}};
|
|
|
}
|
|
}
|
|
|
async function payrollSourceRows(context,input,month,requireLive=false){const data=await payrollCanonicalSnapshot(context,input);return {...data,items:data.items.filter(i=>payrollBusinessDate(i.lessonAt).slice(0,7)===month)};}
|
|
async function payrollSourceRows(context,input,month,requireLive=false){const data=await payrollCanonicalSnapshot(context,input);return {...data,items:data.items.filter(i=>payrollBusinessDate(i.lessonAt).slice(0,7)===month)};}
|
|
|
function payrollCalculatedLine(item,rates,settlementMonth,rules,mappings){
|
|
function payrollCalculatedLine(item,rates,settlementMonth,rules,mappings){
|
|
@@ -50,10 +50,11 @@ function payrollCalculatedLine(item,rates,settlementMonth,rules,mappings){
|
|
|
}
|
|
}
|
|
|
async function payrollPreviewData(context,input,month,includeUsed=false,requireLive=false,options={}){
|
|
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(!/^\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}),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 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 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 "status" IN (\'reviewed\',\'paid\')) AS value',[co,previous])).value);
|
|
|
|
|
|
|
+ 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);
|
|
|
const grouped=new Map();for(const item of source.items){const key=payrollIdentity(item);if(grouped.has(key))grouped.get(key).duplicate=true;else grouped.set(key,item);}
|
|
const grouped=new Map();for(const item of source.items){const key=payrollIdentity(item);if(grouped.has(key))grouped.get(key).duplicate=true;else grouped.set(key,item);}
|
|
|
const lines=Array.from(grouped.values()).filter(item=>{const m=payrollBusinessDate(item.lessonAt).slice(0,7);return (m===month||!m||allowLate&&m===previous)&& (includeUsed||!other.has(payrollIdentity(item)));}).map(item=>payrollCalculatedLine(item,rates,month,rules,mappingMap));
|
|
const lines=Array.from(grouped.values()).filter(item=>{const m=payrollBusinessDate(item.lessonAt).slice(0,7);return (m===month||!m||allowLate&&m===previous)&& (includeUsed||!other.has(payrollIdentity(item)));}).map(item=>payrollCalculatedLine(item,rates,month,rules,mappingMap));
|
|
|
const rewards=await Psql.query('SELECT r.* FROM "ReviewRewardIncome" r WHERE r."company"=$1 AND r."status"=\'earned\' AND '+(includeUsed?'r."incomeMonth"=$2':'r."incomeMonth"<=$2 AND NOT EXISTS(SELECT 1 FROM "PayrollLine" l WHERE l."company"=r."company" AND l."reviewRewardId"=r."objectId" AND NOT COALESCE(l."excluded",false) AND l."batchId"<>$3)')+' ORDER BY r."objectId"',includeUsed?[co,month]:[co,month,text(input.targetId)]);
|
|
const rewards=await Psql.query('SELECT r.* FROM "ReviewRewardIncome" r WHERE r."company"=$1 AND r."status"=\'earned\' AND '+(includeUsed?'r."incomeMonth"=$2':'r."incomeMonth"<=$2 AND NOT EXISTS(SELECT 1 FROM "PayrollLine" l WHERE l."company"=r."company" AND l."reviewRewardId"=r."objectId" AND NOT COALESCE(l."excluded",false) AND l."batchId"<>$3)')+' ORDER BY r."objectId"',includeUsed?[co,month]:[co,month,text(input.targetId)]);
|
|
@@ -61,13 +62,14 @@ async function payrollPreviewData(context,input,month,includeUsed=false,requireL
|
|
|
if(!includeUsed&&input.targetId){const existing=await Psql.query('SELECT * FROM "PayrollLine" WHERE "company"=$1 AND "batchId"=$2 AND "kind"=\'lesson\' AND NOT COALESCE("excluded",false)',[co,input.targetId]),present=new Set(lines.map(payrollIdentity));for(const row of existing)if(!present.has(row.standardLessonKey))lines.push({...payrollLine(row),standardLessonKey:row.standardLessonKey,exception:'原计薪课次已缺失、跨月或被其他批次占用,请核对',amount:0});}
|
|
if(!includeUsed&&input.targetId){const existing=await Psql.query('SELECT * FROM "PayrollLine" WHERE "company"=$1 AND "batchId"=$2 AND "kind"=\'lesson\' AND NOT COALESCE("excluded",false)',[co,input.targetId]),present=new Set(lines.map(payrollIdentity));for(const row of existing)if(!present.has(row.standardLessonKey))lines.push({...payrollLine(row),standardLessonKey:row.standardLessonKey,exception:'原计薪课次已缺失、跨月或被其他批次占用,请核对',amount:0});}
|
|
|
return {...payrollResult(month,lines,source.sourceStatus,input),sourceVersion:source.version};
|
|
return {...payrollResult(month,lines,source.sourceStatus,input),sourceVersion:source.version};
|
|
|
}
|
|
}
|
|
|
-async function payrollPage(context,input){const co=payrollTenant(context,input),paging=pageInput(input),month=text(input.month),where='"company"=$1 AND ($2=\'\' OR "month"=$2)',rows=await Psql.query('SELECT * FROM "PayrollBatch" WHERE '+where+' ORDER BY "month" DESC,"objectId" LIMIT $3 OFFSET $4',[co,month,paging.pageSize,(paging.page-1)*paging.pageSize]),total=await Psql.one('SELECT count(*)::int AS total FROM "PayrollBatch" WHERE '+where,[co,month]);return{items:rows.map(r=>({...payrollSummary(r),version:r.version})),total:total.total,page:paging.page,pageSize:paging.pageSize};}
|
|
|
|
|
-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)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 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){
|
|
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),batches=(await payrollPage(context,{...input,month,page:1,pageSize:100})).items;
|
|
|
const batch=batches[0],settlement=batch?await payrollDetailData(context,{},batch.objectId):null;
|
|
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 visible=payrollResult(month,income.lines,income.sourceStatus,input),settlementTotals=settlement?payrollResult(month,settlement.lines,income.sourceStatus,input):null;
|
|
|
- const stores=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]);
|
|
|
|
|
|
|
+ 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]);
|
|
|
return {...visible,batches,stores,incomeAmount:visible.payableAmount,settlementAmount:settlementTotals?.payableAmount??null,settlementAdjustmentAmount:settlementTotals?.adjustmentAmount??0,settlementCarryAmount:settlementTotals?settlementTotals.lines.filter(l=>l.kind!=='adjustment'&&l.originalMonth!==month&&!l.exception).reduce((a,l)=>a+l.amount,0):0};
|
|
return {...visible,batches,stores,incomeAmount:visible.payableAmount,settlementAmount:settlementTotals?.payableAmount??null,settlementAdjustmentAmount:settlementTotals?.adjustmentAmount??0,settlementCarryAmount:settlementTotals?settlementTotals.lines.filter(l=>l.kind!=='adjustment'&&l.originalMonth!==month&&!l.exception).reduce((a,l)=>a+l.amount,0):0};
|
|
|
}
|
|
}
|
|
|
async function payrollExportData(context,input){const data=input.targetId?await payrollDetailData(context,input,input.targetId):await payrollOverviewData(context,input),filtered=payrollVisibleLines(data.lines,input).filter(l=>!number(input.coachId)||l.coachId===number(input.coachId)),included=filtered.filter(l=>!l.excluded),excluded=filtered.filter(l=>l.excluded),totals=payrollResult(data.month,included,data.sourceStatus||{refreshedAt:new Date().toISOString()},{});return{fileName:'小树陪练工资_'+data.month+'.xlsx',summary:totals,coachItems:totals.coachItems,lines:included.filter(l=>!l.exception),exceptions:included.filter(l=>l.exception),excluded};}
|
|
async function payrollExportData(context,input){const data=input.targetId?await payrollDetailData(context,input,input.targetId):await payrollOverviewData(context,input),filtered=payrollVisibleLines(data.lines,input).filter(l=>!number(input.coachId)||l.coachId===number(input.coachId)),included=filtered.filter(l=>!l.excluded),excluded=filtered.filter(l=>l.excluded),totals=payrollResult(data.month,included,data.sourceStatus||{refreshedAt:new Date().toISOString()},{});return{fileName:'小树陪练工资_'+data.month+'.xlsx',summary:totals,coachItems:totals.coachItems,lines:included.filter(l=>!l.exception),exceptions:included.filter(l=>l.exception),excluded};}
|
|
@@ -79,7 +81,9 @@ async function confirmLegacyReviewRewards(context,input,payload){
|
|
|
const result=await payrollSql('SELECT xs_review_reward_confirm($1,$2,$3::jsonb,$4) AS result',[payrollTenant(context,input),context.current.id,JSON.stringify(ids),reasonOf(input)]);return result.result;
|
|
const result=await payrollSql('SELECT xs_review_reward_confirm($1,$2,$3::jsonb,$4) AS result',[payrollTenant(context,input),context.current.id,JSON.stringify(ids),reasonOf(input)]);return result.result;
|
|
|
}
|
|
}
|
|
|
async function payrollExecute(context,input,op,payload={}){
|
|
async function payrollExecute(context,input,op,payload={}){
|
|
|
- requireRole(context,['ops-manager']);const co=payrollTenant(context,input),reason=reasonOf(input),target=text(input.targetId),req={reason,version:input.version??payload.version,...payload};
|
|
|
|
|
|
|
+ requireRole(context,['ops-manager']);const co=payrollTenant(context,input),reason=reasonOf(input),target=text(input.targetId),req={reason,version:input.version??payload.version,...payload,scopeStoreId:context.storeId||0};
|
|
|
|
|
+ if(context.storeId&&target)await payrollDetailData(context,input,target);
|
|
|
|
|
+ if(context.storeId&&op==='adjustment')await storeAssertCoach(context,payload.coachId);
|
|
|
if(!text(input.idempotencyKey))fail(400,'缺少工资操作请求编号');
|
|
if(!text(input.idempotencyKey))fail(400,'缺少工资操作请求编号');
|
|
|
if(['create','refresh','review'].includes(op)){const month=op==='create'?text(payload.month):(await payrollDetailData(context,input,target)).month;const preview=await payrollPreviewData(context,{...input,storeId:0,search:'',targetId:target},month,false);if(!preview.sourceStatus.sync?.settlementReady)fail(503,'工资来源尚未追平,暂不能生成或审核:'+text(preview.sourceStatus.sync?.message));req.month=month;req.lines=preview.lines.map(l=>({...l,lessonAt:l.lessonAt||null}));req.sourceVersion=preview.sourceVersion;req.cutoffVersion=preview.sourceStatus.sync.cutoffVersion;}
|
|
if(['create','refresh','review'].includes(op)){const month=op==='create'?text(payload.month):(await payrollDetailData(context,input,target)).month;const preview=await payrollPreviewData(context,{...input,storeId:0,search:'',targetId:target},month,false);if(!preview.sourceStatus.sync?.settlementReady)fail(503,'工资来源尚未追平,暂不能生成或审核:'+text(preview.sourceStatus.sync?.message));req.month=month;req.lines=preview.lines.map(l=>({...l,lessonAt:l.lessonAt||null}));req.sourceVersion=preview.sourceVersion;req.cutoffVersion=preview.sourceStatus.sync.cutoffVersion;}
|
|
|
const result=await payrollSql('SELECT xs_payroll_command($1,$2,$3,$4,$5::jsonb,$6) AS result',[co,context.current.id,op,target,JSON.stringify(req),input.idempotencyKey]);
|
|
const result=await payrollSql('SELECT xs_payroll_command($1,$2,$3,$4,$5::jsonb,$6) AS result',[co,context.current.id,op,target,JSON.stringify(req),input.idempotencyKey]);
|
|
@@ -89,8 +93,8 @@ async function createPayroll(context,input,payload){return payrollExecute(contex
|
|
|
async function refreshPayrollDraft(context,input){await payrollExecute(context,input,'refresh');return payrollDetailData(context,input,input.targetId);}
|
|
async function refreshPayrollDraft(context,input){await payrollExecute(context,input,'refresh');return payrollDetailData(context,input,input.targetId);}
|
|
|
async function payrollWorkflow(context,input,operation,payload){if(operation==='reopen')fail(409,'工资单审核后永久锁定,差异请进入下期调整单');return payrollExecute(context,input,operation,payload);}
|
|
async function payrollWorkflow(context,input,operation,payload){if(operation==='reopen')fail(409,'工资单审核后永久锁定,差异请进入下期调整单');return payrollExecute(context,input,operation,payload);}
|
|
|
async function payrollAdjustment(context,input,payload){return payrollExecute(context,input,'adjustment',{coachId:payload.coachId,amount:payload.amount});}
|
|
async function payrollAdjustment(context,input,payload){return payrollExecute(context,input,'adjustment',{coachId:payload.coachId,amount:payload.amount});}
|
|
|
-async function payrollExclude(context,input){const co=payrollTenant(context,input),line=await Psql.oneOrNone('SELECT "batchId" FROM "PayrollLine" WHERE "company"=$1 AND "objectId"=$2',[co,input.targetId]);if(!line)fail(404,'明细不存在');return payrollExecute(context,{...input,targetId:line.batchId},'exclude',{lineId:input.targetId});}
|
|
|
|
|
-async function payrollCoaches(context,input){const co=payrollTenant(context,input),rows=await Psql.query('SELECT * FROM "_User" WHERE "company"=$1 AND ("identityType"=\'coach\' OR "identityType" IS NULL AND "legacyGroupId"=3) AND NOT COALESCE("isDeleted",false) ORDER BY "legacyUserId"',[co]);return{items:rows.map(u=>({coachId:number(u.legacyUserId||legacyOf(u).UserID),coachName:displayName(u),coachObjectId:u.objectId}))};}
|
|
|
|
|
|
|
+async function payrollExclude(context,input){const co=payrollTenant(context,input),line=await Psql.oneOrNone('SELECT "batchId" FROM "PayrollLine" WHERE "company"=$1 AND "objectId"=$2',[co,input.targetId]);if(!line)fail(404,'明细不存在');if(context.storeId)await payrollDetailData(context,input,line.batchId);return payrollExecute(context,{...input,targetId:line.batchId},'exclude',{lineId:input.targetId});}
|
|
|
|
|
+async function payrollCoaches(context,input){if(context.storeId){const rows=await Psql.query('SELECT "objectId","legacyUserId","realName",nickname,username FROM "_User" WHERE company=$1 AND "identityType"=\'coach\' AND "legacyUserData"->>\'ParentUserID\'=$2 AND NOT COALESCE("isDeleted",false)',[companyIdOf(context,input),String(context.storeId)]);return{items:rows.map(u=>({coachId:number(u.legacyUserId),coachName:displayName(u),coachObjectId:u.objectId}))};}const co=payrollTenant(context,input),rows=await Psql.query('SELECT * FROM "_User" WHERE "company"=$1 AND ("identityType"=\'coach\' OR "identityType" IS NULL AND "legacyGroupId"=3) AND NOT COALESCE("isDeleted",false) ORDER BY "legacyUserId"',[co]);return{items:rows.map(u=>({coachId:number(u.legacyUserId||legacyOf(u).UserID),coachName:displayName(u),coachObjectId:u.objectId}))};}
|
|
|
async function teachingRuleRows(context,input){
|
|
async function teachingRuleRows(context,input){
|
|
|
const co=payrollTenant(context,input),stored=await Psql.query('SELECT * FROM "TeachingCompensationRule" WHERE "company"=$1 ORDER BY "revision" DESC,"createdAt" DESC,"objectId"',[co]),names=new Map(TEACHING_CATEGORY_DEFAULTS.map(c=>[c.key,c.name])),latest=new Set(),rows=[];
|
|
const co=payrollTenant(context,input),stored=await Psql.query('SELECT * FROM "TeachingCompensationRule" WHERE "company"=$1 ORDER BY "revision" DESC,"createdAt" DESC,"objectId"',[co]),names=new Map(TEACHING_CATEGORY_DEFAULTS.map(c=>[c.key,c.name])),latest=new Set(),rows=[];
|
|
|
for(const r of stored){const key=[r.categoryKey,r.durationMinutes,r.deliveryMode,r.effectiveFrom].join(':');const superseded=latest.has(key);latest.add(key);rows.push({...teachingRuleProjection(r,names),revision:r.revision,supersedesId:r.supersedesId,superseded,active:r.active!==false&&!superseded});}
|
|
for(const r of stored){const key=[r.categoryKey,r.durationMinutes,r.deliveryMode,r.effectiveFrom].join(':');const superseded=latest.has(key);latest.add(key);rows.push({...teachingRuleProjection(r,names),revision:r.revision,supersedesId:r.supersedesId,superseded,active:r.active!==false&&!superseded});}
|
|
@@ -176,3 +180,9 @@ async function correctReviewReward(context,input,payload){
|
|
|
requireRole(context,['ops-manager']);
|
|
requireRole(context,['ops-manager']);
|
|
|
return (await payrollSql('SELECT xs_review_reward_correct($1,$2,$3,$4,$5,$6::int) AS result',[payrollTenant(context,input),context.current.id,text(input.targetId),reasonOf(input),text(payload.batchId),payload.version??null])).result;
|
|
return (await payrollSql('SELECT xs_review_reward_correct($1,$2,$3,$4,$5,$6::int) AS result',[payrollTenant(context,input),context.current.id,text(input.targetId),reasonOf(input),text(payload.batchId),payload.version??null])).result;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+async function storePayrollPage(context,input){
|
|
|
|
|
+ const co=payrollTenant(context,input),paging=pageInput(input),month=text(input.month),args=[co,month,context.storeId||0],where='company=$1 AND ($2=\'\' OR month=$2) AND "scopeStoreId"=$3';
|
|
|
|
|
+ const [rows,total]=await Promise.all([Psql.query('SELECT * FROM "PayrollBatch" WHERE '+where+' ORDER BY month DESC,"objectId" LIMIT $4 OFFSET $5',[...args,paging.pageSize,(paging.page-1)*paging.pageSize]),Psql.one('SELECT count(*)::int total FROM "PayrollBatch" WHERE '+where,args)]);
|
|
|
|
|
+ return{items:rows.map(r=>({...payrollSummary(r),version:r.version,scopeStoreId:r.scopeStoreId})),total:total.total,page:paging.page,pageSize:paging.pageSize};
|
|
|
|
|
+}
|