async function storeCreditAllowed(context,input) { return Boolean((await Psql.one('SELECT xs_store_credit_allowed($1,$2) allowed',[companyIdOf(context,input),context.current.id])).allowed); } async function storeCreditTarget(context,input) { return scopedStoreObject(context,{...input,targetId:text(input.objectId||input.targetId)}); } async function storeCreditAccounts(context,input) { const target=await storeCreditTarget(context,input),uid=number(target.get('legacyUserId')),legacy=target.get('legacyUserData')||{},reserved=await reservedCreditsFor(context,input,uid); const transactions=input.summaryOnly===true?[]:await Psql.query('SELECT t."objectId",t.kind,t.account,t.amount,t."storeId",t."studentId",t."sourceTransactionId",t."operatorId",t.reason,t."payMethod",t.balances,t."createdAt",t."operationId" FROM "CreditTransaction" t WHERE t.company=$1 AND t."storeId"=$2 ORDER BY t."createdAt" DESC',[pointerId(target.get('company')),uid]); return {storeId:uid,storeName:storeNameOf(target.toJSON()),canPost:await storeCreditAllowed(context,input),canGrant:context.isSuperAdmin,accounts:Object.entries(TEACHING_CREDIT_LABELS).map(([account,label])=>({account,label,balance:number(legacy[account]),reserved:number(reserved[account]),available:number(legacy[account])-number(reserved[account])})),transactions}; } async function storeCreditPreview(context,input,payload) { if(!await storeCreditAllowed(context,input))fail(403,'当前账号未获门店额度充值权限'); const target=await storeCreditTarget(context,input); return(await enrollmentSql('SELECT xs_store_credit_plan($1,$2,$3,$4::jsonb) result',[pointerId(target.get('company')),context.current.id,target.id,JSON.stringify(payload)])).result; } async function storeCreditPost(context,input,payload) { if(!await storeCreditAllowed(context,input))fail(403,'当前账号未获门店额度充值权限'); const target=await storeCreditTarget(context,input),reason=reasonOf(input),co=pointerId(target.get('company')); // Existing single-account clients remain compatible, through the same transaction. const legacy=!Array.isArray(payload.lines),p=legacy?{direction:'add',payMethod:text(payload.payMethod),lines:[{account:text(payload.account)||({1:'Purse',2:'SilverCoin',3:'UserExp',4:'UserPoint'})[number(payload.type)],amount:payload.amount}]}:payload; if(legacy){ const prior=await Psql.oneOrNone('SELECT request,to_jsonb(t)-\'request\' receipt FROM "CreditTransaction" t WHERE company=$1 AND "idempotencyKey"=$2',[co,text(input.idempotencyKey)]); if(prior){const req={actor:context.current.id,kind:'fund',target:target.id,account:p.lines[0].account,amount:number(p.lines[0].amount),original:text(payload.sourceTransactionId),reason,method:p.payMethod};const match=await Psql.one('SELECT $1::jsonb=$2::jsonb same',[JSON.stringify(prior.request),JSON.stringify(req)]);if(!match.same)fail(409,'请求编号已用于其他操作');return prior.receipt;} } const expected=legacy?(await storeCreditPreview(context,input,p)).version:text(input.expectedUpdatedAt); return(await enrollmentSql('SELECT xs_store_credit_post($1,$2,$3,$4::jsonb,$5,$6,$7) result',[co,context.current.id,target.id,JSON.stringify(p),reason,text(input.idempotencyKey),expected])).result; } async function storeCreditLedger(context,input) { const result=await memberLedger(context,input,true),refs=result.items.flatMap(item=>item.lines.map(line=>line.reference)).filter(Boolean); const sources=refs.length?await Psql.query('SELECT "objectId","sourceTransactionId" FROM "CreditTransaction" WHERE company=$1 AND "objectId"=ANY($2::text[])',[companyIdOf(context,input),refs]):[],byId=new Map(sources.map(row=>[row.objectId,row.sourceTransactionId])); for(const item of result.items)for(const line of item.lines)line.sourceTransactionId=byId.get(line.reference)||''; return result; } async function storeCreditGrants(context,input,payload,save=false) { if(!context.isSuperAdmin)fail(403,'只有超级管理员可以管理门店充值授权'); const co=companyIdOf(context,input);if(!co)fail(400,'请选择帐套'); if(save){if(typeof payload.enabled!=='boolean')fail(400,'授权状态无效');return(await enrollmentSql('SELECT xs_store_credit_grant($1,$2,$3,$4,$5) result',[co,context.current.id,text(input.targetId),payload.enabled,reasonOf(input)])).result;} const items=await Psql.query('SELECT u."objectId",COALESCE(NULLIF(u."realName",\'\'),NULLIF(u.nickname,\'\'),u.username) name,u.username,xs_store_credit_role(u."objectId") role,g."userId" IS NOT NULL enabled FROM "_User" u LEFT JOIN "StoreCreditGrant" g ON g.company=u.company AND g."userId"=u."objectId" WHERE u.company=$1 AND (xs_store_credit_role(u."objectId")=\'ops-manager\' OR g."userId" IS NOT NULL) ORDER BY name,u."objectId"',[co]); return {items}; }