|
@@ -2778,7 +2778,7 @@ async function coachRows(context,input) {
|
|
|
const lessonMap=new Map();
|
|
const lessonMap=new Map();
|
|
|
for(const row of lessonFacts){const uid=text(row.uid);if(!uid)continue;const lessonAt=firstDateIso(row.appointmentEnd,row.kzsj,row.appointmentStart,row.plpjsj),type=number(row.kclx),score=number(row.pf),entry=lessonMap.get(uid)||{month_count:0,month_hours:0,estimated_pay:0,score_total:0,score_count:0,average_score:0,last_at:''};if(lessonAt&&lessonAt.slice(0,7)===month){entry.month_count++;entry.month_hours+=durationHours(type);entry.estimated_pay+=type===1?20:[2,3].includes(type)?40:0;}if(score>0){entry.score_total+=score;entry.score_count++;entry.average_score=entry.score_total/entry.score_count;}if(lessonAt&&lessonAt>entry.last_at)entry.last_at=lessonAt;lessonMap.set(uid,entry);}
|
|
for(const row of lessonFacts){const uid=text(row.uid);if(!uid)continue;const lessonAt=firstDateIso(row.appointmentEnd,row.kzsj,row.appointmentStart,row.plpjsj),type=number(row.kclx),score=number(row.pf),entry=lessonMap.get(uid)||{month_count:0,month_hours:0,estimated_pay:0,score_total:0,score_count:0,average_score:0,last_at:''};if(lessonAt&&lessonAt.slice(0,7)===month){entry.month_count++;entry.month_hours+=durationHours(type);entry.estimated_pay+=type===1?20:[2,3].includes(type)?40:0;}if(score>0){entry.score_total+=score;entry.score_count++;entry.average_score=entry.score_total/entry.score_count;}if(lessonAt&&lessonAt>entry.last_at)entry.last_at=lessonAt;lessonMap.set(uid,entry);}
|
|
|
const availability=new Set((await availabilityRows(context,input)).map((row)=>row.coachId)),apptMap=new Map(appts.map((row)=>[text(row.uid),row]));
|
|
const availability=new Set((await availabilityRows(context,input)).map((row)=>row.coachId)),apptMap=new Map(appts.map((row)=>[text(row.uid),row]));
|
|
|
- let normalized=users.map((row)=>{const legacy=legacyOf(row),uid=number(row.legacyUserId||legacy.UserID),storeLegacy=legacyOf({legacyUserData:row.storeLegacy}),a=apptMap.get(String(uid))||{},l=lessonMap.get(String(uid))||{},mobile=mobileOf(row);return{objectId:text(row.objectId),userId:uid,displayName:displayName(row),mobile:roleOf(context)==='ops-manager'?mobile:'',mobileMasked:maskMobile(mobile),storeName:text(row.storeRealName||row.storeNickname||storeLegacy.HoneyName||row.storeUsername)||'未绑定',status:row.isDisabled===true?'disabled':'active',statusLabel:row.isDisabled===true?'已停用':'正常',todayAppointments:number(a.today_count),pendingAppointments:number(a.pending_count),monthLessons:number(l.month_count),monthHours:number(l.month_hours),estimatedPay:number(l.estimated_pay),averageScore:number(l.average_score),lastLessonAt:dateIso(l.last_at),availabilityConfigured:availability.has(uid),updatedAt:dateIso(row.updatedAt)}});
|
|
|
|
|
|
|
+ let normalized=users.map((row)=>{const legacy=legacyOf(row),uid=number(row.legacyUserId||legacy.UserID),storeLegacy=legacyOf({legacyUserData:row.storeLegacy}),a=apptMap.get(String(uid))||{},l=lessonMap.get(String(uid))||{},mobile=mobileOf(row);return{objectId:text(row.objectId),userId:uid,username:text(row.username),displayName:displayName(row),mobile:roleOf(context)==='ops-manager'?mobile:'',mobileMasked:maskMobile(mobile),parentUserId:number(legacy.ParentUserID),storeName:text(row.storeRealName||row.storeNickname||storeLegacy.HoneyName||row.storeUsername)||'未绑定',status:row.isDisabled===true?'disabled':'active',statusLabel:row.isDisabled===true?'已停用':'正常',todayAppointments:number(a.today_count),pendingAppointments:number(a.pending_count),monthLessons:number(l.month_count),monthHours:number(l.month_hours),estimatedPay:number(l.estimated_pay),averageScore:number(l.average_score),lastLessonAt:dateIso(l.last_at),availabilityConfigured:availability.has(uid),updatedAt:dateIso(row.updatedAt)}});
|
|
|
const paging=pageInput(input),status=text(paging.filters.status||'all');if(status!=='all')normalized=normalized.filter((row)=>row.status===status);return pageRows(searchRows(normalized,input),input);
|
|
const paging=pageInput(input),status=text(paging.filters.status||'all');if(status!=='all')normalized=normalized.filter((row)=>row.status===status);return pageRows(searchRows(normalized,input),input);
|
|
|
}
|
|
}
|
|
|
async function scheduleData(context,input) { const from=text(input.dateFrom)||new Date().toISOString().slice(0,10),to=text(input.dateTo)||from,coachId=number((input.filters||{}).coachId); const coachPage=await coachRows(context,{...input,page:1,pageSize:100,search:'',filters:{status:'active'}}),all=await allAppointmentRows(context,input),appointments=all.filter((row)=>row.startsAt.slice(0,10)>=from&&row.startsAt.slice(0,10)<=to&&(!coachId||row.coachId===coachId)),unassigned=appointments.filter((row)=>!row.coachId&&!row.cancelled); return {dateFrom:from,dateTo:to,refreshedAt:new Date().toISOString(),coaches:coachPage.items,appointments,unassigned}; }
|
|
async function scheduleData(context,input) { const from=text(input.dateFrom)||new Date().toISOString().slice(0,10),to=text(input.dateTo)||from,coachId=number((input.filters||{}).coachId); const coachPage=await coachRows(context,{...input,page:1,pageSize:100,search:'',filters:{status:'active'}}),all=await allAppointmentRows(context,input),appointments=all.filter((row)=>row.startsAt.slice(0,10)>=from&&row.startsAt.slice(0,10)<=to&&(!coachId||row.coachId===coachId)),unassigned=appointments.filter((row)=>!row.coachId&&!row.cancelled); return {dateFrom:from,dateTo:to,refreshedAt:new Date().toISOString(),coaches:coachPage.items,appointments,unassigned}; }
|
|
@@ -2858,10 +2858,24 @@ async function ensureCompletionLedger(context,input,pair) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
async function completeAppointmentSafe(context,input) { const pair=await appointmentPair(context,input,text(input.targetId)),result=await completeAppointment(context,input);await ensureCompletionLedger(context,input,pair);return result; }
|
|
async function completeAppointmentSafe(context,input) { const pair=await appointmentPair(context,input,text(input.targetId)),result=await completeAppointment(context,input);await ensureCompletionLedger(context,input,pair);return result; }
|
|
|
|
|
+async function coachStore(context,input,parentUserId) { if(!parentUserId)return null;const companyId=companyIdOf(context,input),row=await Psql.oneOrNone('SELECT "objectId","username","realName","nickname","legacyUserData" FROM "_User" WHERE ($1::text IS NULL OR "company"=$1) AND COALESCE(CAST("legacyUserId" AS text),"legacyUserData"->>\'UserID\')=$2 AND COALESCE(CAST("legacyGroupId" AS text),"legacyUserData"->>\'GroupID\')=\'2\' AND ("isDeleted" IS NULL OR "isDeleted"=FALSE) LIMIT 1',[companyId,String(parentUserId)]);if(!row)fail(404,'所属门店不存在');return row; }
|
|
|
|
|
+function coachPayload(payload,creating=false) { const username=text(payload.username),password=text(payload.password),displayName=text(payload.displayName),mobile=text(payload.mobile),parentUserId=number(payload.parentUserId);if(username.length<3||username.length>64)fail(400,'登录账号长度应为 3 至 64 位');if(!displayName||displayName.length>100)fail(400,'老师姓名为必填项且不能超过 100 位');if(mobile&&!/^1\d{10}$/.test(mobile))fail(400,'手机号格式不正确');if((creating||password)&&(password.length<8||password.length>72))fail(400,'密码长度应为 8 至 72 位');if(parentUserId<0||!Number.isInteger(parentUserId))fail(400,'所属门店无效');return{username,password,displayName,mobile,parentUserId,disabled:payload.disabled===true}; }
|
|
|
|
|
+async function createCoach(context,input,payload) {
|
|
|
|
|
+ requireRole(context,['ops-manager']);const reason=reasonOf(input),data=coachPayload(payload,true),companyId=companyIdOf(context,input);if(!companyId)fail(400,'新增陪练必须指定帐套');const company=await new Parse.Query('Company').get(companyId,{useMasterKey:true});await coachStore(context,input,data.parentUserId);const duplicate=await Psql.oneOrNone('SELECT "objectId" FROM "_User" WHERE "company"=$1 AND "username"=$2 AND ("isDeleted" IS NULL OR "isDeleted"=FALSE) LIMIT 1',[companyId,data.username]);if(duplicate)fail(409,'此登录账号已被使用');
|
|
|
|
|
+ const fields=await fieldsOf('_User'),created=new Parse.User();created.setUsername(data.username);created.setPassword(data.password);created.set('company',company);if(fields.type)created.set('type','user');if(fields.isDisabled)created.set('isDisabled',data.disabled);if(fields.isDeleted)created.set('isDeleted',false);if(fields.legacyGroupId)created.set('legacyGroupId',3);if(fields.nickname)created.set('nickname',data.displayName);if(fields.realName)created.set('realName',data.displayName);if(fields.mobile)created.set('mobile',data.mobile);if(fields.isAdmin)created.set('isAdmin',false);if(fields.roles)created.set('roles',[]);try{await created.signUp(null,{useMasterKey:true});}catch(error){if(number(error&&error.code)===202)fail(409,'此登录账号已被使用');fail(422,'陪练账号创建失败:'+text(error&&error.message));}
|
|
|
|
|
+ try{const legacy={UserID:0,UserName:data.username,HoneyName:data.displayName,GroupID:3,ParentUserID:data.parentUserId,VIP:0,RegTime:new Date().toISOString(),Purse:0,SilverCoin:0,UserExp:0,UserPoint:0,boffExp:0,State:data.disabled?0:1,OpsCreatedAt:new Date().toISOString()},rows=await Psql.query('WITH lock_row AS MATERIALIZED (SELECT pg_advisory_xact_lock(hashtext(\'xiaoshu-legacy-user-id\'))), next_id AS MATERIALIZED (SELECT COALESCE(MAX("legacyUserId"),0)+1 AS id FROM "_User",lock_row WHERE "company"=$1 AND COALESCE("legacyUserId",0)>0) UPDATE "_User" SET "legacyUserId"=next_id.id,"legacyGroupId"=3,"legacyUserData"=jsonb_set($3::jsonb,\'{UserID}\',to_jsonb(next_id.id),true),"updatedAt"=NOW() FROM next_id WHERE "objectId"=$2 RETURNING next_id.id',[companyId,created.id,JSON.stringify(legacy)]),userId=number(rows[0]&&rows[0].id);if(!userId)throw new Error('无法分配陪练业务 ID');await audit(context,'create-coach','_User',created.id,reason,null,{userId,username:data.username,displayName:data.displayName,parentUserId:data.parentUserId,disabled:data.disabled});return{objectId:created.id,userId};}catch(error){await created.destroy({useMasterKey:true}).catch(()=>undefined);if(error&&error.status)throw error;fail(422,'陪练业务资料初始化失败:'+text(error&&error.message));}
|
|
|
|
|
+}
|
|
|
|
|
+async function scopedCoachObject(context,input) { const objectId=text(input.targetId);if(!objectId)fail(400,'缺少陪练记录');const target=await new Parse.Query('_User').get(objectId,{useMasterKey:true}),companyId=companyIdOf(context,input),targetCompanyId=pointerId(target.get('company'));if(companyId&&targetCompanyId!==companyId)fail(404,'陪练不存在或不属于当前帐套');return target; }
|
|
|
|
|
+async function updateCoach(context,input,payload) {
|
|
|
|
|
+ requireRole(context,['ops-manager']);const reason=reasonOf(input),data=coachPayload(payload,false),target=await scopedCoachObject(context,input);assertVersion(target,input.expectedUpdatedAt);const legacy={...(target.get('legacyUserData')||{})};if(number(target.get('legacyGroupId')||legacy.GroupID)!==3)fail(400,'目标账号不是陪练老师');await coachStore(context,input,data.parentUserId);const companyId=pointerId(target.get('company')||context.company),duplicate=await Psql.oneOrNone('SELECT "objectId" FROM "_User" WHERE "company"=$1 AND "username"=$2 AND "objectId"<>$3 AND ("isDeleted" IS NULL OR "isDeleted"=FALSE) LIMIT 1',[companyId,data.username,target.id]);if(duplicate)fail(409,'此登录账号已被使用');const before={username:text(target.get('username')),displayName:displayName(target.toJSON()),mobile:mobileOf(target.toJSON()),parentUserId:number(legacy.ParentUserID),disabled:target.get('isDisabled')===true};target.set('username',data.username);target.set('nickname',data.displayName);target.set('realName',data.displayName);target.set('mobile',data.mobile);target.set('isDisabled',data.disabled);if(data.password)target.set('password',data.password);legacy.UserName=data.username;legacy.HoneyName=data.displayName;legacy.GroupID=3;legacy.ParentUserID=data.parentUserId;legacy.State=data.disabled?0:1;target.set('legacyGroupId',3);target.set('legacyUserData',legacy);await target.save(null,{useMasterKey:true});if(data.password||data.disabled){const query=new Parse.Query('_Session');query.equalTo('user',target);query.limit(1000);const sessions=await query.find({useMasterKey:true});if(sessions.length)await Parse.Object.destroyAll(sessions,{useMasterKey:true});}await audit(context,'update-coach','_User',target.id,reason,before,{username:data.username,displayName:data.displayName,mobile:data.mobile,parentUserId:data.parentUserId,disabled:data.disabled});return{objectId:target.id};
|
|
|
|
|
+}
|
|
|
|
|
+async function deleteCoach(context,input) {
|
|
|
|
|
+ requireRole(context,['ops-manager']);const reason=reasonOf(input),target=await scopedCoachObject(context,input);assertVersion(target,input.expectedUpdatedAt);const legacy={...(target.get('legacyUserData')||{})},coachId=number(target.get('legacyUserId')||legacy.UserID);if(number(target.get('legacyGroupId')||legacy.GroupID)!==3)fail(400,'目标账号不是陪练老师');const today=businessDate(),stored=await allAppointmentRows(context,input),live=await liveAppointmentRows({dateFrom:today}),active=stored.concat(live.allItems).filter((row)=>row.coachId===coachId&&!row.cancelled&&row.statusCode<30&&businessDate(row.startsAt)>=today);if(active.length)fail(409,'该老师还有 '+active.length+' 节未完成排课,请先改派或取消后再删除');const before={displayName:displayName(target.toJSON()),coachId,isDeleted:target.get('isDeleted')===true,isDisabled:target.get('isDisabled')===true};target.set('isDisabled',true);target.set('isDeleted',true);legacy.State=0;legacy.OpsDeletedAt=new Date().toISOString();target.set('legacyUserData',legacy);await target.save(null,{useMasterKey:true});const query=new Parse.Query('_Session');query.equalTo('user',target);query.limit(1000);const sessions=await query.find({useMasterKey:true});if(sessions.length)await Parse.Object.destroyAll(sessions,{useMasterKey:true});await audit(context,'delete-coach','_User',target.id,reason,before,{isDeleted:true,isDisabled:true});return{objectId:target.id,deleted:true};
|
|
|
|
|
+}
|
|
|
function businessDate(value=new Date()) { return new Date(value).toLocaleDateString('sv-SE',{timeZone:'Asia/Shanghai'}); }
|
|
function businessDate(value=new Date()) { return new Date(value).toLocaleDateString('sv-SE',{timeZone:'Asia/Shanghai'}); }
|
|
|
async function appointmentCountsForBusinessDate(context,input,date=businessDate()) { const appointments=await allAppointmentRows(context,input),active=appointments.filter((row)=>!row.cancelled&&businessDate(row.startsAt)===date),byCoach=new Map();for(const row of active)if(row.coachId)byCoach.set(row.coachId,(byCoach.get(row.coachId)||0)+1);return{active,byCoach}; }
|
|
async function appointmentCountsForBusinessDate(context,input,date=businessDate()) { const appointments=await allAppointmentRows(context,input),active=appointments.filter((row)=>!row.cancelled&&businessDate(row.startsAt)===date),byCoach=new Map();for(const row of active)if(row.coachId)byCoach.set(row.coachId,(byCoach.get(row.coachId)||0)+1);return{active,byCoach}; }
|
|
|
function appointmentHistory(rows) { const byCoach=new Map(),dates=[];for(const row of rows){const date=row.startsAt?businessDate(row.startsAt):'';if(/^\d{4}-\d{2}-\d{2}$/.test(date))dates.push(date);if(!row.coachId)continue;const current=byCoach.get(row.coachId)||{totalAppointments:0,lastAppointmentAt:''};current.totalAppointments+=1;if(row.startsAt>current.lastAppointmentAt)current.lastAppointmentAt=row.startsAt;byCoach.set(row.coachId,current);}dates.sort();return{byCoach,totalAppointments:rows.length,dataDateFrom:dates[0]||'',dataDateTo:dates[dates.length-1]||''};}
|
|
function appointmentHistory(rows) { const byCoach=new Map(),dates=[];for(const row of rows){const date=row.startsAt?businessDate(row.startsAt):'';if(/^\d{4}-\d{2}-\d{2}$/.test(date))dates.push(date);if(!row.coachId)continue;const current=byCoach.get(row.coachId)||{totalAppointments:0,lastAppointmentAt:''};current.totalAppointments+=1;if(row.startsAt>current.lastAppointmentAt)current.lastAppointmentAt=row.startsAt;byCoach.set(row.coachId,current);}dates.sort();return{byCoach,totalAppointments:rows.length,dataDateFrom:dates[0]||'',dataDateTo:dates[dates.length-1]||''};}
|
|
|
-async function professionalCoachRows(context,input) { const page=await coachRows(context,input),all=await allAppointmentRows(context,input),counts=await appointmentCountsForBusinessDate(context,input),history=appointmentHistory(all);return{...page,items:page.items.map((row)=>({...row,todayAppointments:counts.byCoach.get(row.userId)||0,...(history.byCoach.get(row.userId)||{totalAppointments:0,lastAppointmentAt:''})}))}; }
|
|
|
|
|
|
|
+async function professionalCoachRows(context,input) { const page=await coachRows(context,input),all=await allAppointmentRows(context,input),counts=await appointmentCountsForBusinessDate(context,input),history=appointmentHistory(all),companyId=companyIdOf(context,input),storeRows=await Psql.query('SELECT "objectId","username","realName","nickname","legacyUserId","legacyUserData" FROM "_User" WHERE ($1::text IS NULL OR "company"=$1) AND COALESCE(CAST("legacyGroupId" AS text),"legacyUserData"->>\'GroupID\')=\'2\' AND ("isDeleted" IS NULL OR "isDeleted"=FALSE) ORDER BY COALESCE(NULLIF("realName",\'\'),NULLIF("nickname",\'\'),"username")',[companyId]);return{...page,canManage:roleOf(context)==='ops-manager',stores:storeRows.map((row)=>({objectId:text(row.objectId),userId:number(row.legacyUserId||legacyOf(row).UserID),displayName:displayName(row)})).filter((row)=>row.userId),items:page.items.map((row)=>({...row,todayAppointments:counts.byCoach.get(row.userId)||0,...(history.byCoach.get(row.userId)||{totalAppointments:0,lastAppointmentAt:''})}))}; }
|
|
|
async function professionalCoachDetail(context,input,objectId) { const detail=await coachDetailData(context,input,objectId),all=await allAppointmentRows(context,input),counts=await appointmentCountsForBusinessDate(context,input),history=appointmentHistory(all),coachHistory=history.byCoach.get(detail.coach.userId)||{totalAppointments:0,lastAppointmentAt:''};return{...detail,coach:{...detail.coach,todayAppointments:counts.byCoach.get(detail.coach.userId)||0,...coachHistory},appointments:all.filter((row)=>row.coachId===detail.coach.userId).slice(0,500)}; }
|
|
async function professionalCoachDetail(context,input,objectId) { const detail=await coachDetailData(context,input,objectId),all=await allAppointmentRows(context,input),counts=await appointmentCountsForBusinessDate(context,input),history=appointmentHistory(all),coachHistory=history.byCoach.get(detail.coach.userId)||{totalAppointments:0,lastAppointmentAt:''};return{...detail,coach:{...detail.coach,todayAppointments:counts.byCoach.get(detail.coach.userId)||0,...coachHistory},appointments:all.filter((row)=>row.coachId===detail.coach.userId).slice(0,500)}; }
|
|
|
async function professionalScheduleDataLocal(context,input) {
|
|
async function professionalScheduleDataLocal(context,input) {
|
|
|
const data=await professionalScheduleData(context,input),stored=await allAppointmentRows(context,input),live=await liveAppointmentRows(input),coachId=number((input.filters||{}).coachId),memberMap=new Map(data.members.map((row)=>[row.userId,row])),baseCoachMap=new Map(data.coaches.map((row)=>[row.userId,row]));
|
|
const data=await professionalScheduleData(context,input),stored=await allAppointmentRows(context,input),live=await liveAppointmentRows(input),coachId=number((input.filters||{}).coachId),memberMap=new Map(data.members.map((row)=>[row.userId,row])),baseCoachMap=new Map(data.coaches.map((row)=>[row.userId,row]));
|
|
@@ -2879,6 +2893,9 @@ async function handler(request,response) {
|
|
|
if(operation==='ops/dashboard/summary') return response.json({success:true,data:await professionalDashboard(context,input)});
|
|
if(operation==='ops/dashboard/summary') return response.json({success:true,data:await professionalDashboard(context,input)});
|
|
|
if(operation==='ops/coaches/list') return response.json({success:true,data:await professionalCoachRows(context,input)});
|
|
if(operation==='ops/coaches/list') return response.json({success:true,data:await professionalCoachRows(context,input)});
|
|
|
if(operation==='ops/coaches/detail') return response.json({success:true,data:await professionalCoachDetail(context,input,text(input.objectId))});
|
|
if(operation==='ops/coaches/detail') return response.json({success:true,data:await professionalCoachDetail(context,input,text(input.objectId))});
|
|
|
|
|
+ if(operation==='ops/coaches/create') return response.json({success:true,data:await createCoach(context,input,payload)});
|
|
|
|
|
+ if(operation==='ops/coaches/update') return response.json({success:true,data:await updateCoach(context,input,payload)});
|
|
|
|
|
+ if(operation==='ops/coaches/delete') return response.json({success:true,data:await deleteCoach(context,input)});
|
|
|
if(operation==='ops/schedule/calendar') return response.json({success:true,data:await professionalScheduleDataLocal(context,input)});
|
|
if(operation==='ops/schedule/calendar') return response.json({success:true,data:await professionalScheduleDataLocal(context,input)});
|
|
|
if(operation==='ops/coach-availability/list') return response.json({success:true,data:pageRows(await availabilityRows(context,input,number((input.filters||{}).coachId)),input)});
|
|
if(operation==='ops/coach-availability/list') return response.json({success:true,data:pageRows(await availabilityRows(context,input,number((input.filters||{}).coachId)),input)});
|
|
|
if(operation==='ops/coach-availability/save') { requireRole(context,['ops-manager','ops-staff']);const reason=reasonOf(input),coachId=number(payload.coachId),ruleType=text(payload.ruleType),start=text(payload.startTime),end=text(payload.endTime);if(!coachId||!['weekly','date'].includes(ruleType)||!/^\d{2}:\d{2}$/.test(start)||!/^\d{2}:\d{2}$/.test(end)||start>=end)fail(400,'陪练、规则类型和有效时段必填');if(!(await userByLegacyId(context,input,coachId,3)))fail(404,'陪练不存在');if(ruleType==='date'&&!/^\d{4}-\d{2}-\d{2}$/.test(text(payload.date)))fail(400,'指定日期无效');const company=companyPointer(context,input),rule=new Parse.Object('CoachAvailability');rule.set('company',company);rule.set('sourceKey','cloud:ops-availability:'+companyIdOf(context,input)+':'+coachId+':'+randomObjectId());rule.set('coachId',coachId);rule.set('ruleType',ruleType);rule.set('weekday',number(payload.weekday));rule.set('date',text(payload.date));rule.set('startTime',start);rule.set('endTime',end);rule.set('available',payload.available!==false);rule.set('reason',reason);await rule.save(null,{useMasterKey:true});await audit(context,'save-coach-availability','CoachAvailability',rule.id,reason,null,safe(rule));return response.json({success:true,data:{objectId:rule.id}}); }
|
|
if(operation==='ops/coach-availability/save') { requireRole(context,['ops-manager','ops-staff']);const reason=reasonOf(input),coachId=number(payload.coachId),ruleType=text(payload.ruleType),start=text(payload.startTime),end=text(payload.endTime);if(!coachId||!['weekly','date'].includes(ruleType)||!/^\d{2}:\d{2}$/.test(start)||!/^\d{2}:\d{2}$/.test(end)||start>=end)fail(400,'陪练、规则类型和有效时段必填');if(!(await userByLegacyId(context,input,coachId,3)))fail(404,'陪练不存在');if(ruleType==='date'&&!/^\d{4}-\d{2}-\d{2}$/.test(text(payload.date)))fail(400,'指定日期无效');const company=companyPointer(context,input),rule=new Parse.Object('CoachAvailability');rule.set('company',company);rule.set('sourceKey','cloud:ops-availability:'+companyIdOf(context,input)+':'+coachId+':'+randomObjectId());rule.set('coachId',coachId);rule.set('ruleType',ruleType);rule.set('weekday',number(payload.weekday));rule.set('date',text(payload.date));rule.set('startTime',start);rule.set('endTime',end);rule.set('available',payload.available!==false);rule.set('reason',reason);await rule.save(null,{useMasterKey:true});await audit(context,'save-coach-availability','CoachAvailability',rule.id,reason,null,safe(rule));return response.json({success:true,data:{objectId:rule.id}}); }
|