admin.models.ts 25 KB

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