|
@@ -3613,7 +3613,7 @@ function scheduleTrendFromSnapshot(snapshot,period,today=businessDate()) {
|
|
|
async function scheduleTrendData(context,input) { const period=text(input.period||'week'),today=businessDate(),range=scheduleTrendRange(period,today),snapshot=await appointmentSnapshot(context,input,range.dateFrom);return scheduleTrendFromSnapshot(snapshot,period,today); }
|
|
async function scheduleTrendData(context,input) { const period=text(input.period||'week'),today=businessDate(),range=scheduleTrendRange(period,today),snapshot=await appointmentSnapshot(context,input,range.dateFrom);return scheduleTrendFromSnapshot(snapshot,period,today); }
|
|
|
async function professionalDashboard(context,input) { const date=businessDate(),payroll=await payrollOverviewData(context,{...input,month:date.slice(0,7),storeId:0,search:'',refresh:true}),data=await dashboard(context,input,payroll),range=scheduleTrendRange('week',date),snapshot=await appointmentSnapshot(context,input,range.dateFrom),todayRows=snapshot.appointments.filter((row)=>!row.cancelled&&row.startsAt&&businessDate(row.startsAt)===date),currentLiveRows=snapshot.live.allItems.filter((row)=>row.startsAt&&businessDate(row.startsAt)>=date),coachSnapshot={...snapshot,live:{...snapshot.live,allItems:currentLiveRows},appointments:mergeAppointments(snapshot.stored,currentLiveRows)},coachPage=await professionalCoachRows(context,{...input,page:1,pageSize:100,search:'',filters:{status:'active'}},coachSnapshot,payroll),metrics=data.metrics.map((row)=>row.key==='today'?{...row,value:todayRows.length}:row.key==='coaches'?{...row,value:coachPage.total,hint:snapshot.live.available?'新系统与实时业务账号去重统计':row.hint}:row),alerts=data.alerts.map((row)=>row.key==='unassigned'?{...row,value:todayRows.filter((item)=>!item.coachId).length}:row);return{...data,date,metrics,alerts,scheduleTrend:scheduleTrendFromSnapshot(snapshot,'week',date),refreshedAt:new Date().toISOString(),liveSourceAvailable:snapshot.live.available,liveSourceMessage:snapshot.live.message,sync:await operationalSyncIndicator(context,input)}; }
|
|
async function professionalDashboard(context,input) { const date=businessDate(),payroll=await payrollOverviewData(context,{...input,month:date.slice(0,7),storeId:0,search:'',refresh:true}),data=await dashboard(context,input,payroll),range=scheduleTrendRange('week',date),snapshot=await appointmentSnapshot(context,input,range.dateFrom),todayRows=snapshot.appointments.filter((row)=>!row.cancelled&&row.startsAt&&businessDate(row.startsAt)===date),currentLiveRows=snapshot.live.allItems.filter((row)=>row.startsAt&&businessDate(row.startsAt)>=date),coachSnapshot={...snapshot,live:{...snapshot.live,allItems:currentLiveRows},appointments:mergeAppointments(snapshot.stored,currentLiveRows)},coachPage=await professionalCoachRows(context,{...input,page:1,pageSize:100,search:'',filters:{status:'active'}},coachSnapshot,payroll),metrics=data.metrics.map((row)=>row.key==='today'?{...row,value:todayRows.length}:row.key==='coaches'?{...row,value:coachPage.total,hint:snapshot.live.available?'新系统与实时业务账号去重统计':row.hint}:row),alerts=data.alerts.map((row)=>row.key==='unassigned'?{...row,value:todayRows.filter((item)=>!item.coachId).length}:row);return{...data,date,metrics,alerts,scheduleTrend:scheduleTrendFromSnapshot(snapshot,'week',date),refreshedAt:new Date().toISOString(),liveSourceAvailable:snapshot.live.available,liveSourceMessage:snapshot.live.message,sync:await operationalSyncIndicator(context,input)}; }
|
|
|
async function scheduleTrendFast(context,input,requestedPeriod=''){
|
|
async function scheduleTrendFast(context,input,requestedPeriod=''){
|
|
|
- const period=text(requestedPeriod||input.period||'week'),today=businessDate(),range=scheduleTrendRange(period,today),companyId=companyIdOf(context,input),rows=await Psql.query('SELECT LEFT(REPLACE(COALESCE(NULLIF(CAST(a."yysj" AS text),\'\'),NULLIF(CAST(a."bxrq" AS text),\'\')),\'/\',\'-\'),10) AS day,COUNT(*)::int AS course_count,COUNT(*) FILTER (WHERE CAST(COALESCE(a."dszt",\'0\') AS text) IN (\'30\',\'40\'))::int AS completed_count,ARRAY_AGG(DISTINCT CAST(a."szyh" AS text)) FILTER (WHERE COALESCE(CAST(a."szyh" AS text),\'\')<>\'\') AS students FROM "CommonModel" c JOIN "CourseAppointment" a ON a."company"=c."company" AND CAST(a."id" AS text)=CAST(c."itemId" AS text) WHERE ($1::text IS NULL OR c."company"=$1) AND CAST(c."modelId" AS text)=\'54\' AND COALESCE(CAST(c."status" AS text),\'99\')<>\'-2\' AND LEFT(REPLACE(COALESCE(NULLIF(CAST(a."yysj" AS text),\'\'),NULLIF(CAST(a."bxrq" AS text),\'\')),\'/\',\'-\'),10) BETWEEN $2 AND $3 GROUP BY day',[companyId,range.dateFrom,range.dateTo]),byDay=new Map(rows.map((row)=>[text(row.day),row])),points=[],weekdays=['周日','周一','周二','周三','周四','周五','周六'],allStudents=new Set();
|
|
|
|
|
|
|
+ const period=text(requestedPeriod||input.period||'week'),today=businessDate(),range=scheduleTrendRange(period,today),companyId=companyIdOf(context,input),rows=await Psql.query('SELECT LEFT(REPLACE(COALESCE(NULLIF(CAST(a."yysj" AS text),\'\'),NULLIF(CAST(a."bxrq" AS text),\'\')),\'/\',\'-\'),10) AS day,COUNT(*)::int AS course_count,COUNT(*) FILTER (WHERE CAST(COALESCE(a."dszt",\'0\') AS text) IN (\'30\',\'40\'))::int AS completed_count,ARRAY_AGG(DISTINCT CAST(a."szyh" AS text)) FILTER (WHERE COALESCE(CAST(a."szyh" AS text),\'\')<>\'\') AS students FROM "CommonModel" c JOIN "CourseAppointment" a ON a."company"=c."company" AND CAST(a."id" AS text)=CAST(c."itemId" AS text) WHERE ($1::text IS NULL OR c."company"=$1) AND CAST(c."modelId" AS text)=\'54\' AND COALESCE(CAST(c."status" AS text),\'99\')<>\'-2\' AND LEFT(REPLACE(COALESCE(NULLIF(CAST(a."yysj" AS text),\'\'),NULLIF(CAST(a."bxrq" AS text),\'\')),\'/\',\'-\'),10) BETWEEN $2 AND $3 AND ($4::text=\'\' OR EXISTS (SELECT 1 FROM "_User" s WHERE s.company=a.company AND s."legacyUserId"::text=a."szyh"::text AND s."identityType"=\'member\' AND s."legacyUserData"->>\'ParentUserID\'=$4 AND NOT COALESCE(s."isDeleted",false))) GROUP BY day',[companyId,range.dateFrom,range.dateTo,context.storeId?String(context.storeId):'']),byDay=new Map(rows.map((row)=>[text(row.day),row])),points=[],weekdays=['周日','周一','周二','周三','周四','周五','周六'],allStudents=new Set();
|
|
|
for(let key=range.dateFrom;key<=range.dateTo;key=addBusinessDays(key,1)){const row=byDay.get(key)||{},students=Array.isArray(row.students)?row.students.map(text).filter(Boolean):[];for(const id of students)allStudents.add(id);const month=number(key.slice(5,7)),day=number(key.slice(8,10)),weekday=new Date(key+'T12:00:00+08:00').getDay();points.push({key,label:month+'月'+day+'日',shortLabel:period==='week'?weekdays[weekday]:period==='year'?month+'/'+day:day+'日',courseCount:number(row.course_count),studentCount:new Set(students).size,completedCount:number(row.completed_count),isToday:key===today});}
|
|
for(let key=range.dateFrom;key<=range.dateTo;key=addBusinessDays(key,1)){const row=byDay.get(key)||{},students=Array.isArray(row.students)?row.students.map(text).filter(Boolean):[];for(const id of students)allStudents.add(id);const month=number(key.slice(5,7)),day=number(key.slice(8,10)),weekday=new Date(key+'T12:00:00+08:00').getDay();points.push({key,label:month+'月'+day+'日',shortLabel:period==='week'?weekdays[weekday]:period==='year'?month+'/'+day:day+'日',courseCount:number(row.course_count),studentCount:new Set(students).size,completedCount:number(row.completed_count),isToday:key===today});}
|
|
|
const todayPoint=points.find((point)=>point.isToday)||{courseCount:0,studentCount:0},totalCourseCount=points.reduce((sum,point)=>sum+point.courseCount,0);return{period,periodLabel:range.periodLabel,dateFrom:range.dateFrom,dateTo:range.dateTo,points,todayCourseCount:todayPoint.courseCount,todayStudentCount:todayPoint.studentCount,totalCourseCount,totalStudentCount:allStudents.size,maxValue:Math.max(1,...points.flatMap((point)=>[point.courseCount,point.studentCount])),refreshedAt:new Date().toISOString(),liveSourceAvailable:true,liveSourceMessage:'新数据库排课投影'};
|
|
const todayPoint=points.find((point)=>point.isToday)||{courseCount:0,studentCount:0},totalCourseCount=points.reduce((sum,point)=>sum+point.courseCount,0);return{period,periodLabel:range.periodLabel,dateFrom:range.dateFrom,dateTo:range.dateTo,points,todayCourseCount:todayPoint.courseCount,todayStudentCount:todayPoint.studentCount,totalCourseCount,totalStudentCount:allStudents.size,maxValue:Math.max(1,...points.flatMap((point)=>[point.courseCount,point.studentCount])),refreshedAt:new Date().toISOString(),liveSourceAvailable:true,liveSourceMessage:'新数据库排课投影'};
|
|
|
}
|
|
}
|