store-credit.js 4.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. async function storeCreditAllowed(context,input) {
  2. return Boolean((await Psql.one('SELECT xs_store_credit_allowed($1,$2) allowed',[companyIdOf(context,input),context.current.id])).allowed);
  3. }
  4. async function storeCreditTarget(context,input) {
  5. return scopedStoreObject(context,{...input,targetId:text(input.objectId||input.targetId)});
  6. }
  7. async function storeCreditAccounts(context,input) {
  8. const target=await storeCreditTarget(context,input),uid=number(target.get('legacyUserId')),legacy=target.get('legacyUserData')||{},reserved=await reservedCreditsFor(context,input,uid);
  9. 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]);
  10. 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};
  11. }
  12. async function storeCreditPreview(context,input,payload) {
  13. if(!await storeCreditAllowed(context,input))fail(403,'当前账号未获门店额度充值权限');
  14. const target=await storeCreditTarget(context,input);
  15. 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;
  16. }
  17. async function storeCreditPost(context,input,payload) {
  18. if(!await storeCreditAllowed(context,input))fail(403,'当前账号未获门店额度充值权限');
  19. const target=await storeCreditTarget(context,input),reason=reasonOf(input),co=pointerId(target.get('company'));
  20. // Existing single-account clients remain compatible, through the same transaction.
  21. 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;
  22. if(legacy){
  23. 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)]);
  24. 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;}
  25. }
  26. const expected=legacy?(await storeCreditPreview(context,input,p)).version:text(input.expectedUpdatedAt);
  27. 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;
  28. }
  29. async function storeCreditLedger(context,input) {
  30. const result=await memberLedger(context,input,true),refs=result.items.flatMap(item=>item.lines.map(line=>line.reference)).filter(Boolean);
  31. 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]));
  32. for(const item of result.items)for(const line of item.lines)line.sourceTransactionId=byId.get(line.reference)||'';
  33. return result;
  34. }
  35. async function storeCreditGrants(context,input,payload,save=false) {
  36. if(!context.isSuperAdmin)fail(403,'只有超级管理员可以管理门店充值授权');
  37. const co=companyIdOf(context,input);if(!co)fail(400,'请选择帐套');
  38. 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;}
  39. 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]);
  40. return {items};
  41. }