admin.models.ts 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. export interface ParsePointer {
  2. __type: 'Pointer';
  3. className: string;
  4. objectId: string;
  5. }
  6. export interface AdminIdentity {
  7. storeId?: number;
  8. storeObjectId?: string;
  9. passwordResetRequired?: boolean;
  10. objectId: string;
  11. username: string;
  12. displayName: string;
  13. company?: ParsePointer;
  14. roles: string[];
  15. isSuperAdmin: boolean;
  16. operationsRole: OperationsRole;
  17. }
  18. export interface AdminSession {
  19. storeId?: number;
  20. storeObjectId?: string;
  21. passwordResetRequired?: boolean;
  22. objectId: string;
  23. username: string;
  24. sessionToken: string;
  25. company?: ParsePointer;
  26. displayName?: string;
  27. roles?: string[];
  28. isSuperAdmin?: boolean;
  29. operationsRole?: OperationsRole;
  30. }
  31. export type OperationsRole = 'store-admin' | 'ops-manager' | 'ops-staff' | 'ops-auditor';
  32. export interface CloudEnvelope<T> {
  33. success: boolean;
  34. data: T;
  35. error?: string;
  36. message?: string;
  37. }
  38. export interface LegacyCloudEnvelope<T> {
  39. retcode: number;
  40. retmsg?: string;
  41. result: T;
  42. }
  43. export interface AppMigrationStatus {
  44. implemented: string[];
  45. blocked: Record<string, string>;
  46. }
  47. export interface AdminResourceCatalogItem {
  48. className: string;
  49. label: string;
  50. writable: boolean;
  51. route: string;
  52. }
  53. export interface AdminResourceCatalog {
  54. total: number;
  55. resources: AdminResourceCatalogItem[];
  56. }
  57. export interface AdminFieldSchema {
  58. name: string;
  59. type: string;
  60. targetClass?: string;
  61. required?: boolean;
  62. writable: boolean;
  63. }
  64. export interface AdminClassSchema {
  65. className: string;
  66. label: string;
  67. fields: AdminFieldSchema[];
  68. writable: boolean;
  69. creatable?: boolean;
  70. supportsSoftDelete: boolean;
  71. }
  72. export interface ResourcePage {
  73. className: string;
  74. page: number;
  75. pageSize: number;
  76. total: number;
  77. results: Record<string, unknown>[];
  78. }
  79. export interface DashboardMetric {
  80. className: string;
  81. label: string;
  82. count: number;
  83. route: string;
  84. }
  85. export interface DashboardData {
  86. identity: AdminIdentity;
  87. metrics: DashboardMetric[];
  88. migration: {
  89. sourceObjects: number;
  90. migratedClasses: number;
  91. registeredRecords: number;
  92. cloudFunctions: number;
  93. };
  94. }
  95. export interface OperationsPage<T extends Record<string, unknown> = Record<string, unknown>> {
  96. page: number;
  97. pageSize: number;
  98. total: number;
  99. items: T[];
  100. refreshedAt?: string;
  101. }
  102. export type LegacySyncHealth = 'healthy' | 'delayed' | 'error' | 'not_configured' | 'never_synced';
  103. export interface LegacySyncDataset extends Record<string, unknown> {
  104. key: string;
  105. label: string;
  106. legacyCount: number | null;
  107. parseCount: number;
  108. difference: number | null;
  109. cursor: string;
  110. lastSuccessAt: string;
  111. lastAttemptAt: string;
  112. lagSeconds: number | null;
  113. health: LegacySyncHealth;
  114. healthLabel: string;
  115. pendingFailures: number;
  116. conflicts: number;
  117. checksum?: string;
  118. }
  119. export interface LegacySyncStatus {
  120. configured: boolean;
  121. bridgeReachable: boolean;
  122. bridgeUrl: string;
  123. manifestTime: string;
  124. serverTime: string;
  125. refreshedAt: string;
  126. overallHealth: LegacySyncHealth;
  127. overallHealthLabel: string;
  128. totalFailures: number;
  129. totalConflicts: number;
  130. datasets: LegacySyncDataset[];
  131. message: string;
  132. }
  133. export interface LegacySyncRunResult {
  134. dataset: string;
  135. processed: number;
  136. created: number;
  137. updated: number;
  138. deleted: number;
  139. conflicts: number;
  140. failures: number;
  141. hasMore: boolean;
  142. cursor: string;
  143. startedAt: string;
  144. finishedAt: string;
  145. pages?: number;
  146. }
  147. export interface OperationsSyncIndicator {
  148. state: 'synced' | 'delayed' | 'not_configured';
  149. label: string;
  150. lastSuccessAt: string;
  151. lagSeconds: number | null;
  152. }
  153. export interface MemberSummary {
  154. total: number;
  155. todayNew: number;
  156. sevenDayNew: number;
  157. studying: number;
  158. followUp: number;
  159. }
  160. export interface MemberAgentOption extends Record<string, unknown> {
  161. objectId: string;
  162. userId: number;
  163. displayName: string;
  164. }
  165. export interface MemberPage extends OperationsPage<OperationsUser> {
  166. summary: MemberSummary;
  167. agents: MemberAgentOption[];
  168. sync?: OperationsSyncIndicator;
  169. sourceStatus?: {
  170. available: boolean;
  171. message: string;
  172. total: number;
  173. memberCount: number;
  174. coachEvidenceCount: number;
  175. storeCount: number;
  176. unknownCount: number;
  177. refreshedAt: string;
  178. };
  179. }
  180. export interface MemberImportPreviewRow {
  181. rowNumber: number;
  182. displayName: string;
  183. nickname: string;
  184. mobileMasked: string;
  185. gender: string;
  186. grade: string;
  187. parentUserId: number;
  188. action: 'create' | 'resumed' | 'error';
  189. issues: string[];
  190. }
  191. export interface MemberImportPreview {
  192. batchId: string;
  193. fileName: string;
  194. expiresAt: string;
  195. summary: { total: number; create: number; resumed: number; error: number };
  196. rows: MemberImportPreviewRow[];
  197. }
  198. export interface MemberImportResult {
  199. batchId: string;
  200. total: number;
  201. created: number;
  202. resumed: number;
  203. items: Array<{ objectId: string; userId: number; displayName: string; mobileMasked: string; resumed: boolean }>;
  204. }
  205. export interface MemberListSort {
  206. field: 'registeredAt' | 'lastLoginAt' | 'lastLessonAt' | 'nextAppointmentAt' | 'remainingPeriods';
  207. direction: 'asc' | 'desc';
  208. }
  209. export interface OperationsDashboardMetric {
  210. key: string;
  211. label: string;
  212. value: number;
  213. hint: string;
  214. route: string;
  215. tone: 'blue' | 'green' | 'amber' | 'rose';
  216. }
  217. export type ScheduleTrendPeriod = 'week' | 'month' | 'year';
  218. export interface ScheduleTrendPoint {
  219. key: string;
  220. label: string;
  221. shortLabel: string;
  222. courseCount: number;
  223. studentCount: number;
  224. completedCount: number;
  225. isToday: boolean;
  226. }
  227. export interface ScheduleTrendData {
  228. period: ScheduleTrendPeriod;
  229. periodLabel: string;
  230. dateFrom: string;
  231. dateTo: string;
  232. points: ScheduleTrendPoint[];
  233. todayCourseCount: number;
  234. todayStudentCount: number;
  235. totalCourseCount: number;
  236. totalStudentCount: number;
  237. maxValue: number;
  238. refreshedAt: string;
  239. liveSourceAvailable: boolean;
  240. liveSourceMessage: string;
  241. }
  242. export interface OperationsDashboardData {
  243. identity: AdminIdentity;
  244. date: string;
  245. metrics: OperationsDashboardMetric[];
  246. alerts: { key: string; label: string; value: number; route: string }[];
  247. refreshedAt?: string;
  248. liveSourceAvailable?: boolean;
  249. liveSourceMessage?: string;
  250. scheduleTrend?: ScheduleTrendData;
  251. sync?: OperationsSyncIndicator;
  252. payroll?: {
  253. month: string;
  254. coachCount: number;
  255. lessonCount: number;
  256. payableAmount: number;
  257. sourceStatus: PayrollSourceStatus;
  258. refreshedAt: string;
  259. };
  260. }
  261. export interface OperationsUser extends Record<string, unknown> {
  262. objectId: string;
  263. userId: number;
  264. displayName: string;
  265. username: string;
  266. mobile: string;
  267. mobileMasked: string;
  268. groupId: number;
  269. groupName: string;
  270. parentUserId: number;
  271. parentDisplayName: string;
  272. periods30: number;
  273. periods60: number;
  274. trialPeriods: number;
  275. activeCourseCount: number;
  276. completedCourseCount: number;
  277. studyRecordCount: number;
  278. appointmentCount: number;
  279. learningStatus: 'trial' | 'studying' | 'completed' | 'not_started';
  280. learningStatusLabel: string;
  281. learningStatusReason: string;
  282. registeredAt: string;
  283. lastLoginAt: string;
  284. loginCount: number;
  285. status: 'active' | 'disabled';
  286. statusLabel: string;
  287. canRevealMobile: boolean;
  288. updatedAt: string;
  289. nickname?: string;
  290. lastLessonAt?: string;
  291. nextAppointmentAt?: string;
  292. riskFlags?: string[];
  293. identityType?: 'member';
  294. gender?: string;
  295. grade?: string;
  296. noteCount?: number;
  297. liveOnly?: boolean;
  298. writeReady?: boolean;
  299. dataSource?: 'parse' | 'legacy-live';
  300. readOnlyReason?: string;
  301. }
  302. export interface OperationsUserDetail extends OperationsUser {
  303. email: string;
  304. balances: {
  305. purse: number;
  306. silverCoin: number;
  307. userExp: number;
  308. userPoint: number;
  309. dummyPurse: number;
  310. credit: number;
  311. };
  312. relation: { parentUserId: number; parentDisplayName: string };
  313. permissions: { canManage: boolean; canEdit: boolean; canAdjustBalance: boolean; canResetPassword: boolean; canRevealMobile: boolean };
  314. agents: MemberAgentOption[];
  315. }
  316. export type LearningReportStatus = 'available' | 'incomplete' | 'missing' | 'historical';
  317. export interface LearningReportSummary extends Record<string, unknown> {
  318. reportCount: number;
  319. studentCount: number;
  320. learnedCount: number;
  321. masteredCount: number;
  322. reviewDueCount: number;
  323. masteryRate: number;
  324. }
  325. export interface LearningReportSourceStatus extends Record<string, unknown> {
  326. state: 'synced' | 'delayed' | 'not_configured';
  327. label: string;
  328. message: string;
  329. lastSuccessAt: string;
  330. lagSeconds: number | null;
  331. }
  332. export interface LearningReportListItem extends Record<string, unknown> {
  333. targetId: string;
  334. generalId: number;
  335. studentObjectId: string;
  336. studentId: number;
  337. studentName: string;
  338. studentMobileMasked: string;
  339. coachId: number;
  340. coachName: string;
  341. courseId: number;
  342. courseName: string;
  343. appointmentId: number;
  344. appointmentAt: string;
  345. studyAt: string;
  346. studyDate: string;
  347. classTypeLabel: string;
  348. learnedCount: number;
  349. masteredCount: number;
  350. reviewDueCount: number;
  351. unmarkedCount: number;
  352. masteryRate: number;
  353. status: LearningReportStatus;
  354. statusLabel: string;
  355. canExport: boolean;
  356. updatedAt: string;
  357. }
  358. export interface LearningReportWord extends Record<string, unknown> {
  359. index: number;
  360. generalId: number;
  361. word: string;
  362. phonetic: string;
  363. meaning: string;
  364. example: string;
  365. result: 'mastered' | 'review' | 'unmarked';
  366. resultLabel: string;
  367. }
  368. export interface LearningReportDetail extends LearningReportListItem {
  369. words: LearningReportWord[];
  370. reviewSchedule: Array<{ day: number; date: string }>;
  371. score: number;
  372. studentComment: string;
  373. coachComment: string;
  374. generatedAt: string;
  375. sourceStatus: LearningReportSourceStatus;
  376. countMismatch: boolean;
  377. }
  378. export interface LearningReportPage extends OperationsPage<LearningReportListItem> {
  379. summary: LearningReportSummary;
  380. sourceStatus: LearningReportSourceStatus;
  381. }
  382. export interface CoachSummary extends Record<string, unknown> {
  383. objectId: string;
  384. userId: number;
  385. username: string;
  386. displayName: string;
  387. mobile: string;
  388. mobileMasked: string;
  389. parentUserId: number;
  390. storeName: string;
  391. status: 'active' | 'disabled';
  392. statusLabel: string;
  393. todayAppointments: number;
  394. pendingAppointments: number;
  395. totalAppointments: number;
  396. lastAppointmentAt: string;
  397. nextAppointmentAt?: string;
  398. monthLessons: number;
  399. monthHours: number;
  400. estimatedPay: number;
  401. averageScore: number;
  402. lastLessonAt: string;
  403. availabilityConfigured: boolean;
  404. updatedAt: string;
  405. source?: 'hybrid' | 'legacy-live';
  406. sourceLabel?: string;
  407. readOnly?: boolean;
  408. registeredAt?: string;
  409. hasCurrentBusiness?: boolean;
  410. }
  411. export interface CoachPageSummary extends Record<string, unknown> {
  412. total: number;
  413. active: number;
  414. disabled: number;
  415. monthActive: number;
  416. liveOnly: number;
  417. }
  418. export interface StoreSummary extends Record<string, unknown> {
  419. objectId: string;
  420. userId: number;
  421. username: string;
  422. storeName: string;
  423. contactName: string;
  424. mobile: string;
  425. mobileMasked: string;
  426. parentStoreId: number;
  427. parentStoreName: string;
  428. memberCount: number;
  429. activeStudyingMemberCount?: number;
  430. coachCount: number;
  431. todayAppointments: number;
  432. totalAppointments: number;
  433. lastAppointmentAt: string;
  434. registeredAt: string;
  435. lastLoginAt: string;
  436. loginCount: number;
  437. status: 'active' | 'disabled';
  438. statusLabel: string;
  439. updatedAt: string;
  440. source?: 'parse' | 'hybrid' | 'legacy-live';
  441. sourceLabel?: string;
  442. readOnly?: boolean;
  443. }
  444. export interface StoreOption extends Record<string, unknown> {
  445. objectId: string;
  446. userId: number;
  447. displayName: string;
  448. }
  449. export interface StorePage extends OperationsPage<StoreSummary> {
  450. canFund?: boolean;
  451. canGrantCredit?: boolean;
  452. canManage: boolean;
  453. activeStudyingMemberCountError?: string;
  454. summary: {
  455. total: number;
  456. active: number;
  457. disabled: number;
  458. members: number;
  459. coaches: number;
  460. todayAppointments: number;
  461. liveOnly?: number;
  462. };
  463. parents: StoreOption[];
  464. liveSourceAvailable?: boolean;
  465. liveSourceMessage?: string;
  466. legacyStoreCount?: number;
  467. sync?: OperationsSyncIndicator;
  468. }
  469. export interface StoreCourseSummary extends Record<string, unknown> {
  470. courseId: number;
  471. courseName: string;
  472. appointmentCount: number;
  473. completedCount: number;
  474. studentCount: number;
  475. completionRate: number;
  476. lastAppointmentAt: string;
  477. }
  478. export interface StoreDetailData extends Record<string, unknown> {
  479. store: StoreSummary;
  480. month: string;
  481. summary: {
  482. memberCount: number;
  483. coachCount: number;
  484. monthAppointments: number;
  485. completedAppointments: number;
  486. activeStudents: number;
  487. lessonCount: number;
  488. totalHours: number;
  489. estimatedCoachPay: number;
  490. };
  491. members: OperationsUser[];
  492. coaches: CoachSummary[];
  493. appointments: ScheduleAppointment[];
  494. courses: StoreCourseSummary[];
  495. commissionStatus: {
  496. available: boolean;
  497. message: string;
  498. };
  499. payrollSourceStatus: PayrollSourceStatus;
  500. refreshedAt: string;
  501. sync?: OperationsSyncIndicator;
  502. }
  503. export interface CoachStoreOption extends Record<string, unknown> {
  504. objectId: string;
  505. userId: number;
  506. displayName: string;
  507. }
  508. export interface CoachPage extends OperationsPage<CoachSummary> {
  509. summary: CoachPageSummary;
  510. canManage: boolean;
  511. stores: CoachStoreOption[];
  512. liveSourceAvailable?: boolean;
  513. liveSourceMessage?: string;
  514. liveSourceCount?: number;
  515. dataDateFrom?: string;
  516. dataDateTo?: string;
  517. payrollMonth?: string;
  518. payrollSourceStatus?: PayrollSourceStatus;
  519. payrollRefreshedAt?: string;
  520. sync?: OperationsSyncIndicator;
  521. }
  522. export interface ScheduleAppointment extends Record<string, unknown> {
  523. bindingId?: string;
  524. sourceKey?: string;
  525. statusUnverified?: boolean;
  526. targetId: string;
  527. generalId: number;
  528. studentObjectId: string;
  529. studentId: number;
  530. studentName: string;
  531. coachObjectId: string;
  532. coachId: number;
  533. coachName: string;
  534. courseId: number;
  535. courseName: string;
  536. classType: number;
  537. classTypeLabel: string;
  538. deliveryMethod: string;
  539. courseCategoryKey?: string;
  540. courseCategoryName?: string;
  541. durationMinutes?: number;
  542. deliveryMode?: TeachingDeliveryMode;
  543. teachingRuleId?: string;
  544. teacherPaySnapshot?: number;
  545. creditCostsSnapshot?: TeachingCreditCost[];
  546. creditHoldStatus?: string;
  547. creditHoldReason?: string;
  548. startsAt: string;
  549. endsAt: string;
  550. statusCode: number;
  551. statusLabel: string;
  552. cancelled: boolean;
  553. cancellationReason: string;
  554. coachAccountMissing: boolean;
  555. availabilityWarning: boolean;
  556. source: 'parse' | 'legacy-live';
  557. sourceLabel: string;
  558. readOnly: boolean;
  559. updatedAt: string;
  560. }
  561. export interface ScheduleCalendarData {
  562. source?: string;
  563. workflowIssues?: Array<{appointmentGeneralId:string;message:string;targetId:string;updatedAt:string}>;
  564. dateFrom: string;
  565. dateTo: string;
  566. refreshedAt: string;
  567. sourceRefreshedAt?: string;
  568. totalAppointments: number;
  569. dataDateFrom: string;
  570. dataDateTo: string;
  571. liveSourceAvailable: boolean;
  572. liveSourceMessage: string;
  573. liveSourceCount: number;
  574. coaches: CoachSummary[];
  575. appointments: ScheduleAppointment[];
  576. unassigned: ScheduleAppointment[];
  577. members: Array<{ objectId: string; userId: number; displayName: string; mobileMasked: string; periods30: number; periods60: number; trialPeriods: number; userPoint: number; learningStatusLabel: string; liveOnly?: boolean; writeReady?: boolean; readOnly?: boolean; readOnlyReason?: string }>;
  578. courses: Array<{ bindingId?: string; courseId: number; courseName: string; memberIds: number[]; availableToAll?: boolean; stageKey?: TeachingStageKey | ''; stageName?: string; categoryKey?: string; categoryName?: string; mappingConfirmed?: boolean; suggestedCategoryName?: string; supportedDurations?: number[] }>;
  579. sync?: OperationsSyncIndicator;
  580. }
  581. export interface CoachAvailability extends Record<string, unknown> {
  582. objectId: string;
  583. coachId: number;
  584. ruleType: 'weekly' | 'date';
  585. weekday: number;
  586. date: string;
  587. startTime: string;
  588. endTime: string;
  589. available: boolean;
  590. reason: string;
  591. updatedAt: string;
  592. }
  593. export interface CoachDetailData {
  594. coach: CoachSummary;
  595. profile: {
  596. username: string;
  597. mobile: string;
  598. storeName: string;
  599. statusLabel: string;
  600. sourceLabel: string;
  601. };
  602. currentPayroll: PayrollCoachSummary;
  603. availability: CoachAvailability[];
  604. appointments: ScheduleAppointment[];
  605. lessons: PayrollLine[];
  606. reviews: PayrollLine[];
  607. payrolls: PayrollSummary[];
  608. reviewTaskCount: number;
  609. payrollMonth: string;
  610. payrollSourceStatus: PayrollSourceStatus;
  611. refreshedAt: string;
  612. dataDateFrom: string;
  613. dataDateTo: string;
  614. }
  615. export interface PayRate extends Record<string, unknown> {
  616. objectId: string;
  617. classType: number;
  618. classTypeLabel: string;
  619. amount: number;
  620. durationHours: number;
  621. effectiveFrom: string;
  622. active: boolean;
  623. isDefault?: boolean;
  624. updatedAt: string;
  625. }
  626. export type TeachingDeliveryMode = 'online' | 'offline';
  627. export type TeachingCreditAccount = 'Purse' | 'SilverCoin' | 'UserExp' | 'UserPoint';
  628. export type TeachingStageKey = 'primary' | 'middle' | 'high' | 'other';
  629. export interface TeachingCourseCategory extends Record<string, unknown> {
  630. objectId: string;
  631. key: string;
  632. name: string;
  633. sortOrder: number;
  634. active: boolean;
  635. system: boolean;
  636. updatedAt: string;
  637. }
  638. export interface TeachingCreditCost extends Record<string, unknown> {
  639. account: TeachingCreditAccount;
  640. label: string;
  641. amount: number;
  642. }
  643. export interface TeachingRule extends Record<string, unknown> {
  644. objectId: string;
  645. categoryKey: string;
  646. categoryName: string;
  647. durationMinutes: number;
  648. deliveryMode: TeachingDeliveryMode;
  649. deliveryLabel: string;
  650. teacherPay: number;
  651. creditCosts: TeachingCreditCost[];
  652. effectiveFrom: string;
  653. active: boolean;
  654. isDefault?: boolean;
  655. updatedAt: string;
  656. }
  657. export interface CourseTeachingMapping extends Record<string, unknown> {
  658. objectId: string;
  659. courseId: number;
  660. courseName: string;
  661. stageKey: TeachingStageKey | '';
  662. stageName: string;
  663. categoryKey: string;
  664. categoryName: string;
  665. suggestedCategoryKey: string;
  666. suggestedCategoryName: string;
  667. categoryConfirmed: boolean;
  668. stageConfirmed: boolean;
  669. confirmed: boolean;
  670. updatedAt: string;
  671. }
  672. export interface TeachingStandardsData {
  673. categories: TeachingCourseCategory[];
  674. rules: TeachingRule[];
  675. mappings: CourseTeachingMapping[];
  676. summary: {
  677. courseCount: number;
  678. confirmedCount: number;
  679. unmappedCount: number;
  680. activeRuleCount: number;
  681. };
  682. refreshedAt: string;
  683. }
  684. export interface CreditReservationReconciliation {
  685. items: Array<{
  686. appointmentId: string;
  687. appointmentGeneralId: number;
  688. studentId: number;
  689. studentName: string;
  690. courseId: number;
  691. courseName: string;
  692. startsAt: string;
  693. categoryName: string;
  694. durationMinutes: number;
  695. deliveryLabel: string;
  696. creditCosts: TeachingCreditCost[];
  697. status: 'ready' | 'insufficient' | 'unmapped' | 'missing_rule' | 'missing_student';
  698. statusLabel: string;
  699. reason: string;
  700. }>;
  701. summary: {
  702. total: number;
  703. ready: number;
  704. insufficient: number;
  705. exceptions: number;
  706. };
  707. refreshedAt: string;
  708. }
  709. export type PayrollStatus = 'draft' | 'reviewed' | 'paid';
  710. export interface PayrollSummary extends Record<string, unknown> {
  711. version?: number;
  712. objectId: string;
  713. month: string;
  714. status: PayrollStatus;
  715. statusLabel: string;
  716. coachCount: number;
  717. lessonCount: number;
  718. totalHours: number;
  719. baseAmount: number;
  720. reviewCount?: number;
  721. reviewAmount?: number;
  722. adjustmentAmount: number;
  723. payableAmount: number;
  724. exceptionCount: number;
  725. type4Count?: number;
  726. reviewedAt: string;
  727. paidAt: string;
  728. paymentMethod: string;
  729. paymentReference: string;
  730. updatedAt: string;
  731. }
  732. export interface PayrollLine extends Record<string, unknown> {
  733. objectId: string;
  734. kind: 'lesson' | 'adjustment' | 'review_reward';
  735. reviewRewardId?: string;
  736. reviewTaskId?: string;
  737. plannedDate?: string;
  738. coachId: number;
  739. coachName: string;
  740. coachObjectId?: string;
  741. storeId: number;
  742. storeName: string;
  743. studentName: string;
  744. courseName: string;
  745. legacyGeneralId: number;
  746. lessonId: string;
  747. lessonAt: string;
  748. originalMonth: string;
  749. settlementMonth: string;
  750. classType: number;
  751. classTypeLabel: string;
  752. courseCategoryKey?: string;
  753. courseCategoryName?: string;
  754. deliveryMode?: TeachingDeliveryMode | '';
  755. deliveryLabel?: string;
  756. durationMinutes?: number;
  757. teachingRuleId?: string;
  758. durationHours: number;
  759. rateAmount: number;
  760. amount: number;
  761. score?: number;
  762. comment?: string;
  763. description: string;
  764. exception: string;
  765. excluded?: boolean;
  766. exclusionReason?: string;
  767. }
  768. export interface PayrollDetail extends PayrollSummary {
  769. lines: PayrollLine[];
  770. exceptions: PayrollLine[];
  771. coachItems?: PayrollCoachSummary[];
  772. sourceStatus?: PayrollSourceStatus;
  773. refreshedAt?: string;
  774. }
  775. export interface PayrollCoachSummary extends Record<string, unknown> {
  776. coachId: number;
  777. coachName: string;
  778. coachObjectId?: string;
  779. storeId: number;
  780. storeName: string;
  781. type30Count: number;
  782. type60Count: number;
  783. trialCount: number;
  784. type4Count: number;
  785. lessonCount: number;
  786. totalHours: number;
  787. baseAmount: number;
  788. reviewCount?: number;
  789. reviewAmount?: number;
  790. adjustmentAmount: number;
  791. payableAmount: number;
  792. averageScore?: number;
  793. latestLessonAt: string;
  794. }
  795. export interface PayrollSourceStatus extends Record<string, unknown> {
  796. available: boolean;
  797. mode: 'live' | 'cache' | 'parse';
  798. message: string;
  799. total: number;
  800. refreshedAt: string;
  801. sync?: {
  802. state: 'not_configured' | 'synced' | 'delayed' | 'inconsistent' | 'error' | 'batch_verified';
  803. settlementReady: boolean; message: string; checkedAt: string;
  804. lastSuccessAt: string; lagSeconds: number | null;
  805. datasets: Array<{ key: string; state: string; lastSuccessAt: string; lagSeconds: number | null; pendingFailures: number; conflicts: number; legacyCount: number | null; localCount: number | null; complete?: boolean }>;
  806. };
  807. }
  808. export interface PayrollOverview extends PayrollDetail {
  809. incomeAmount?: number; settlementAmount?: number | null; settlementAdjustmentAmount?: number; settlementCarryAmount?: number;
  810. coachItems: PayrollCoachSummary[];
  811. sourceStatus: PayrollSourceStatus;
  812. batches: PayrollSummary[];
  813. stores: Array<{ storeId: number; storeName: string }>;
  814. }
  815. export interface PayrollExportData extends Record<string, unknown> {
  816. excluded?: PayrollLine[];
  817. fileName: string;
  818. summary: PayrollSummary;
  819. coachItems: PayrollCoachSummary[];
  820. lines: PayrollLine[];
  821. exceptions: PayrollLine[];
  822. }
  823. export interface VocabularyNode extends Record<string, unknown> {
  824. objectId: string;
  825. nodeId: number;
  826. name: string;
  827. parentId: number;
  828. depth: number;
  829. orderId: number;
  830. directWordCount: number;
  831. wordCount: number;
  832. childCount: number;
  833. status: number;
  834. canContainWords: boolean;
  835. }
  836. export interface VocabularyWord extends Record<string, unknown> {
  837. targetId: string;
  838. generalId: number;
  839. itemId: number;
  840. word: string;
  841. meaning: string;
  842. phonetic: string;
  843. example: string;
  844. audioUrl: string;
  845. nodeId: number;
  846. unitName: string;
  847. bookName: string;
  848. stageName: string;
  849. orderId: number;
  850. status: number;
  851. statusLabel: string;
  852. createdAt: string;
  853. updatedAt: string;
  854. }
  855. export interface VocabularyTree {
  856. items: VocabularyNode[];
  857. total: number;
  858. totalWords: number;
  859. refreshedAt: string;
  860. }
  861. export interface VocabularyImportResult {
  862. batchId: string;
  863. fileName?: string;
  864. reason?: string;
  865. previewSummary?: VocabularyImportPreview['summary'];
  866. status: string;
  867. busy?: boolean;
  868. summary: { total: number; created: number; updated: number; skipped: number; failed: number; processed: number };
  869. items: Array<{ row: number; word?: string; status?: string; message?: string }>;
  870. }
  871. export interface VocabularyImportPreview {
  872. batchId: string;
  873. status: string;
  874. summary: { total: number; create: number; update: number; unchanged: number; duplicate: number; error: number; missingMedia: number };
  875. items: Array<Record<string, unknown>>;
  876. createdAt: string;
  877. }
  878. export interface SpecialTrainingItem extends Record<string, unknown> {
  879. targetId: string;
  880. generalId: number;
  881. itemId: number;
  882. title: string;
  883. synopsis: string;
  884. coverUrl: string;
  885. tags: string[];
  886. contentHtml: string;
  887. videoUrl: string;
  888. author: string;
  889. source: string;
  890. orderId: number;
  891. hits: number;
  892. status: number;
  893. statusLabel: string;
  894. createdAt: string;
  895. updatedAt: string;
  896. }
  897. export interface SpecialTrainingPage extends OperationsPage<SpecialTrainingItem> {
  898. sourceStatus?: {
  899. available: boolean;
  900. message: string;
  901. legacyTotal: number;
  902. created: number;
  903. updated: number;
  904. refreshedAt: string;
  905. } | null;
  906. }
  907. export interface MediaUploadTicket {
  908. ticketId: string;
  909. method: 'PUT';
  910. uploadUrl: string;
  911. publicUrl: string;
  912. objectKey: string;
  913. headers: Record<string, string>;
  914. expiresAt: string;
  915. }
  916. export interface ReviewReward {
  917. objectId: string; taskId: string; coachId: number; coachName: string; studentName: string;
  918. courseName: string; storeId: number; storeName: string; plannedDate: string; completedAt: string;
  919. incomeMonth: string; amount: number; status: 'earned' | 'historical' | 'exception' | 'ineligible';
  920. statusLabel: string; exception: string; ineligibleReason?: string; batchId?: string; settlementMonth?: string;
  921. canConfirm?: boolean; canCorrect?: boolean; batchStatus?: string; originalAmount?: number; correctionAmount?: number; correctionReason?: string; correctionId?: string; adjustmentBatchId?: string;
  922. }
  923. export interface ReviewRewardPage {
  924. snapshot?: string; nextCursor?: string;
  925. items: ReviewReward[]; total: number; page: number; pageSize: number;
  926. summary: { reviewCount: number; reviewAmount: number; historicalCount: number; exceptionCount: number };
  927. }
  928. export interface ReviewRewardFilters { month: string; storeId: number; search: string; coachId: number; revision?: number; resetRevision?: number; snapshot?: string; afterId?: string; exportMode?: boolean; }