admin.models.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  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' | 'writing';
  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 | null;
  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 | null;
  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. writingFeedback?: {
  378. contentSummary: string;
  379. reviewSummary: string;
  380. trainingSummary: string;
  381. homework: string;
  382. coachComment: string;
  383. notes: string;
  384. } | null;
  385. }
  386. export interface LearningReportPage extends OperationsPage<LearningReportListItem> {
  387. summary: LearningReportSummary;
  388. sourceStatus: LearningReportSourceStatus;
  389. }
  390. export interface CoachSummary extends Record<string, unknown> {
  391. objectId: string;
  392. userId: number;
  393. username: string;
  394. displayName: string;
  395. mobile: string;
  396. mobileMasked: string;
  397. parentUserId: number;
  398. storeName: string;
  399. status: 'active' | 'disabled';
  400. statusLabel: string;
  401. todayAppointments: number;
  402. pendingAppointments: number;
  403. totalAppointments: number;
  404. lastAppointmentAt: string;
  405. nextAppointmentAt?: string;
  406. monthLessons: number;
  407. monthHours: number;
  408. estimatedPay: number;
  409. averageScore: number;
  410. lastLessonAt: string;
  411. availabilityConfigured: boolean;
  412. updatedAt: string;
  413. source?: 'hybrid' | 'legacy-live';
  414. sourceLabel?: string;
  415. readOnly?: boolean;
  416. registeredAt?: string;
  417. hasCurrentBusiness?: boolean;
  418. }
  419. export interface CoachPageSummary extends Record<string, unknown> {
  420. total: number;
  421. active: number;
  422. disabled: number;
  423. monthActive: number;
  424. liveOnly: number;
  425. }
  426. export interface StoreSummary extends Record<string, unknown> {
  427. objectId: string;
  428. userId: number;
  429. username: string;
  430. storeName: string;
  431. contactName: string;
  432. mobile: string;
  433. mobileMasked: string;
  434. parentStoreId: number;
  435. parentStoreName: string;
  436. memberCount: number;
  437. activeStudyingMemberCount?: number;
  438. coachCount: number;
  439. todayAppointments: number;
  440. totalAppointments: number;
  441. lastAppointmentAt: string;
  442. registeredAt: string;
  443. lastLoginAt: string;
  444. loginCount: number;
  445. status: 'active' | 'disabled';
  446. statusLabel: string;
  447. updatedAt: string;
  448. source?: 'parse' | 'hybrid' | 'legacy-live';
  449. sourceLabel?: string;
  450. readOnly?: boolean;
  451. }
  452. export interface StoreOption extends Record<string, unknown> {
  453. objectId: string;
  454. userId: number;
  455. displayName: string;
  456. }
  457. export interface StorePage extends OperationsPage<StoreSummary> {
  458. canFund?: boolean;
  459. canGrantCredit?: boolean;
  460. canManage: boolean;
  461. activeStudyingMemberCountError?: string;
  462. summary: {
  463. total: number;
  464. active: number;
  465. disabled: number;
  466. members: number;
  467. coaches: number;
  468. todayAppointments: number;
  469. liveOnly?: number;
  470. };
  471. parents: StoreOption[];
  472. liveSourceAvailable?: boolean;
  473. liveSourceMessage?: string;
  474. legacyStoreCount?: number;
  475. sync?: OperationsSyncIndicator;
  476. }
  477. export interface StoreCourseSummary extends Record<string, unknown> {
  478. courseId: number;
  479. courseName: string;
  480. appointmentCount: number;
  481. completedCount: number;
  482. studentCount: number;
  483. completionRate: number;
  484. lastAppointmentAt: string;
  485. }
  486. export interface StoreDetailData extends Record<string, unknown> {
  487. store: StoreSummary;
  488. month: string;
  489. summary: {
  490. memberCount: number;
  491. coachCount: number;
  492. monthAppointments: number;
  493. completedAppointments: number;
  494. activeStudents: number;
  495. lessonCount: number;
  496. totalHours: number;
  497. estimatedCoachPay: number;
  498. };
  499. members: OperationsUser[];
  500. coaches: CoachSummary[];
  501. appointments: ScheduleAppointment[];
  502. courses: StoreCourseSummary[];
  503. commissionStatus: {
  504. available: boolean;
  505. message: string;
  506. };
  507. payrollSourceStatus: PayrollSourceStatus;
  508. refreshedAt: string;
  509. sync?: OperationsSyncIndicator;
  510. }
  511. export interface CoachStoreOption extends Record<string, unknown> {
  512. objectId: string;
  513. userId: number;
  514. displayName: string;
  515. }
  516. export interface CoachPage extends OperationsPage<CoachSummary> {
  517. summary: CoachPageSummary;
  518. canManage: boolean;
  519. stores: CoachStoreOption[];
  520. liveSourceAvailable?: boolean;
  521. liveSourceMessage?: string;
  522. liveSourceCount?: number;
  523. dataDateFrom?: string;
  524. dataDateTo?: string;
  525. payrollMonth?: string;
  526. payrollSourceStatus?: PayrollSourceStatus;
  527. payrollRefreshedAt?: string;
  528. sync?: OperationsSyncIndicator;
  529. }
  530. export interface ScheduleAppointment extends Record<string, unknown> {
  531. bindingId?: string;
  532. sourceKey?: string;
  533. statusUnverified?: boolean;
  534. targetId: string;
  535. generalId: number;
  536. studentObjectId: string;
  537. studentId: number;
  538. studentName: string;
  539. coachObjectId: string;
  540. coachId: number;
  541. coachName: string;
  542. courseId: number;
  543. courseName: string;
  544. classType: number;
  545. classTypeLabel: string;
  546. deliveryMethod: string;
  547. courseCategoryKey?: string;
  548. courseCategoryName?: string;
  549. durationMinutes?: number;
  550. deliveryMode?: TeachingDeliveryMode;
  551. teachingRuleId?: string;
  552. teacherPaySnapshot?: number;
  553. creditCostsSnapshot?: TeachingCreditCost[];
  554. creditHoldStatus?: string;
  555. creditHoldReason?: string;
  556. startsAt: string;
  557. endsAt: string;
  558. statusCode: number;
  559. statusLabel: string;
  560. cancelled: boolean;
  561. cancellationReason: string;
  562. coachAccountMissing: boolean;
  563. availabilityWarning: boolean;
  564. source: 'parse' | 'legacy-live';
  565. sourceLabel: string;
  566. readOnly: boolean;
  567. updatedAt: string;
  568. }
  569. export interface ScheduleCalendarData {
  570. source?: string;
  571. workflowIssues?: Array<{appointmentGeneralId:string;message:string;targetId:string;updatedAt:string}>;
  572. dateFrom: string;
  573. dateTo: string;
  574. refreshedAt: string;
  575. sourceRefreshedAt?: string;
  576. totalAppointments: number;
  577. dataDateFrom: string;
  578. dataDateTo: string;
  579. liveSourceAvailable: boolean;
  580. liveSourceMessage: string;
  581. liveSourceCount: number;
  582. coaches: CoachSummary[];
  583. appointments: ScheduleAppointment[];
  584. unassigned: ScheduleAppointment[];
  585. 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 }>;
  586. 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[] }>;
  587. sync?: OperationsSyncIndicator;
  588. }
  589. export interface CoachAvailability extends Record<string, unknown> {
  590. objectId: string;
  591. coachId: number;
  592. ruleType: 'weekly' | 'date';
  593. weekday: number;
  594. date: string;
  595. startTime: string;
  596. endTime: string;
  597. available: boolean;
  598. reason: string;
  599. updatedAt: string;
  600. }
  601. export interface CoachDetailData {
  602. coach: CoachSummary;
  603. profile: {
  604. username: string;
  605. mobile: string;
  606. storeName: string;
  607. statusLabel: string;
  608. sourceLabel: string;
  609. };
  610. currentPayroll: PayrollCoachSummary;
  611. availability: CoachAvailability[];
  612. appointments: ScheduleAppointment[];
  613. lessons: PayrollLine[];
  614. reviews: PayrollLine[];
  615. payrolls: PayrollSummary[];
  616. reviewTaskCount: number;
  617. payrollMonth: string;
  618. payrollSourceStatus: PayrollSourceStatus;
  619. refreshedAt: string;
  620. dataDateFrom: string;
  621. dataDateTo: string;
  622. }
  623. export interface PayRate extends Record<string, unknown> {
  624. objectId: string;
  625. classType: number;
  626. classTypeLabel: string;
  627. amount: number;
  628. durationHours: number;
  629. effectiveFrom: string;
  630. active: boolean;
  631. isDefault?: boolean;
  632. updatedAt: string;
  633. }
  634. export type TeachingDeliveryMode = 'online' | 'offline';
  635. export type TeachingCreditAccount = 'Purse' | 'SilverCoin' | 'UserExp' | 'UserPoint';
  636. export type TeachingStageKey = 'primary' | 'middle' | 'high' | 'other';
  637. export interface TeachingCourseCategory extends Record<string, unknown> {
  638. objectId: string;
  639. key: string;
  640. name: string;
  641. sortOrder: number;
  642. active: boolean;
  643. system: boolean;
  644. updatedAt: string;
  645. }
  646. export interface TeachingCreditCost extends Record<string, unknown> {
  647. account: TeachingCreditAccount;
  648. label: string;
  649. amount: number;
  650. }
  651. export interface TeachingRule extends Record<string, unknown> {
  652. objectId: string;
  653. categoryKey: string;
  654. categoryName: string;
  655. durationMinutes: number;
  656. deliveryMode: TeachingDeliveryMode;
  657. deliveryLabel: string;
  658. teacherPay: number;
  659. creditCosts: TeachingCreditCost[];
  660. effectiveFrom: string;
  661. active: boolean;
  662. isDefault?: boolean;
  663. updatedAt: string;
  664. }
  665. export interface CourseTeachingMapping extends Record<string, unknown> {
  666. objectId: string;
  667. courseId: number;
  668. courseName: string;
  669. stageKey: TeachingStageKey | '';
  670. stageName: string;
  671. categoryKey: string;
  672. categoryName: string;
  673. suggestedCategoryKey: string;
  674. suggestedCategoryName: string;
  675. categoryConfirmed: boolean;
  676. stageConfirmed: boolean;
  677. confirmed: boolean;
  678. updatedAt: string;
  679. }
  680. export interface TeachingStandardsData {
  681. categories: TeachingCourseCategory[];
  682. rules: TeachingRule[];
  683. mappings: CourseTeachingMapping[];
  684. summary: {
  685. courseCount: number;
  686. confirmedCount: number;
  687. unmappedCount: number;
  688. activeRuleCount: number;
  689. };
  690. refreshedAt: string;
  691. }
  692. export interface CreditReservationReconciliation {
  693. items: Array<{
  694. appointmentId: string;
  695. appointmentGeneralId: number;
  696. studentId: number;
  697. studentName: string;
  698. courseId: number;
  699. courseName: string;
  700. startsAt: string;
  701. categoryName: string;
  702. durationMinutes: number;
  703. deliveryLabel: string;
  704. creditCosts: TeachingCreditCost[];
  705. status: 'ready' | 'insufficient' | 'unmapped' | 'missing_rule' | 'missing_student';
  706. statusLabel: string;
  707. reason: string;
  708. }>;
  709. summary: {
  710. total: number;
  711. ready: number;
  712. insufficient: number;
  713. exceptions: number;
  714. };
  715. refreshedAt: string;
  716. }
  717. export type PayrollStatus = 'draft' | 'reviewed' | 'paid';
  718. export interface PayrollSummary extends Record<string, unknown> {
  719. version?: number;
  720. objectId: string;
  721. month: string;
  722. status: PayrollStatus;
  723. statusLabel: string;
  724. coachCount: number;
  725. lessonCount: number;
  726. totalHours: number;
  727. baseAmount: number;
  728. reviewCount?: number;
  729. reviewAmount?: number;
  730. adjustmentAmount: number;
  731. payableAmount: number;
  732. exceptionCount: number;
  733. type4Count?: number;
  734. reviewedAt: string;
  735. paidAt: string;
  736. paymentMethod: string;
  737. paymentReference: string;
  738. updatedAt: string;
  739. }
  740. export interface PayrollLine extends Record<string, unknown> {
  741. objectId: string;
  742. kind: 'lesson' | 'adjustment' | 'review_reward';
  743. reviewRewardId?: string;
  744. reviewTaskId?: string;
  745. plannedDate?: string;
  746. coachId: number;
  747. coachName: string;
  748. coachObjectId?: string;
  749. storeId: number;
  750. storeName: string;
  751. studentName: string;
  752. courseName: string;
  753. legacyGeneralId: number;
  754. lessonId: string;
  755. lessonAt: string;
  756. originalMonth: string;
  757. settlementMonth: string;
  758. classType: number;
  759. classTypeLabel: string;
  760. courseCategoryKey?: string;
  761. courseCategoryName?: string;
  762. deliveryMode?: TeachingDeliveryMode | '';
  763. deliveryLabel?: string;
  764. durationMinutes?: number;
  765. teachingRuleId?: string;
  766. durationHours: number;
  767. rateAmount: number;
  768. amount: number;
  769. score?: number;
  770. comment?: string;
  771. description: string;
  772. exception: string;
  773. excluded?: boolean;
  774. exclusionReason?: string;
  775. }
  776. export interface PayrollDetail extends PayrollSummary {
  777. lines: PayrollLine[];
  778. exceptions: PayrollLine[];
  779. coachItems?: PayrollCoachSummary[];
  780. sourceStatus?: PayrollSourceStatus;
  781. refreshedAt?: string;
  782. }
  783. export interface PayrollCoachSummary extends Record<string, unknown> {
  784. coachId: number;
  785. coachName: string;
  786. coachObjectId?: string;
  787. storeId: number;
  788. storeName: string;
  789. type30Count: number;
  790. type60Count: number;
  791. trialCount: number;
  792. type4Count: number;
  793. lessonCount: number;
  794. totalHours: number;
  795. baseAmount: number;
  796. reviewCount?: number;
  797. reviewAmount?: number;
  798. adjustmentAmount: number;
  799. payableAmount: number;
  800. averageScore?: number;
  801. latestLessonAt: string;
  802. }
  803. export interface PayrollSourceStatus extends Record<string, unknown> {
  804. available: boolean;
  805. mode: 'live' | 'cache' | 'parse';
  806. message: string;
  807. total: number;
  808. refreshedAt: string;
  809. sync?: {
  810. state: 'not_configured' | 'synced' | 'delayed' | 'inconsistent' | 'error' | 'batch_verified';
  811. settlementReady: boolean; message: string; checkedAt: string;
  812. lastSuccessAt: string; lagSeconds: number | null;
  813. datasets: Array<{ key: string; state: string; lastSuccessAt: string; lagSeconds: number | null; pendingFailures: number; conflicts: number; legacyCount: number | null; localCount: number | null; complete?: boolean }>;
  814. };
  815. }
  816. export interface PayrollOverview extends PayrollDetail {
  817. incomeAmount?: number; settlementAmount?: number | null; settlementAdjustmentAmount?: number; settlementCarryAmount?: number;
  818. coachItems: PayrollCoachSummary[];
  819. sourceStatus: PayrollSourceStatus;
  820. batches: PayrollSummary[];
  821. stores: Array<{ storeId: number; storeName: string }>;
  822. }
  823. export interface PayrollExportData extends Record<string, unknown> {
  824. excluded?: PayrollLine[];
  825. fileName: string;
  826. summary: PayrollSummary;
  827. coachItems: PayrollCoachSummary[];
  828. lines: PayrollLine[];
  829. exceptions: PayrollLine[];
  830. }
  831. export interface VocabularyNode extends Record<string, unknown> {
  832. objectId: string;
  833. nodeId: number;
  834. name: string;
  835. parentId: number;
  836. depth: number;
  837. orderId: number;
  838. directWordCount: number;
  839. wordCount: number;
  840. childCount: number;
  841. status: number;
  842. canContainWords: boolean;
  843. }
  844. export interface VocabularyWord extends Record<string, unknown> {
  845. targetId: string;
  846. generalId: number;
  847. itemId: number;
  848. word: string;
  849. meaning: string;
  850. phonetic: string;
  851. example: string;
  852. audioUrl: string;
  853. nodeId: number;
  854. unitName: string;
  855. bookName: string;
  856. stageName: string;
  857. orderId: number;
  858. status: number;
  859. statusLabel: string;
  860. createdAt: string;
  861. updatedAt: string;
  862. }
  863. export interface VocabularyTree {
  864. items: VocabularyNode[];
  865. total: number;
  866. totalWords: number;
  867. refreshedAt: string;
  868. }
  869. export interface VocabularyImportResult {
  870. batchId: string;
  871. fileName?: string;
  872. reason?: string;
  873. previewSummary?: VocabularyImportPreview['summary'];
  874. status: string;
  875. busy?: boolean;
  876. summary: { total: number; created: number; updated: number; skipped: number; failed: number; processed: number };
  877. items: Array<{ row: number; word?: string; status?: string; message?: string }>;
  878. }
  879. export interface VocabularyImportPreview {
  880. batchId: string;
  881. status: string;
  882. summary: { total: number; create: number; update: number; unchanged: number; duplicate: number; error: number; missingMedia: number };
  883. items: Array<Record<string, unknown>>;
  884. createdAt: string;
  885. }
  886. export interface SpecialTrainingItem extends Record<string, unknown> {
  887. targetId: string;
  888. generalId: number;
  889. itemId: number;
  890. title: string;
  891. synopsis: string;
  892. coverUrl: string;
  893. tags: string[];
  894. contentHtml: string;
  895. videoUrl: string;
  896. author: string;
  897. source: string;
  898. orderId: number;
  899. hits: number;
  900. status: number;
  901. statusLabel: string;
  902. createdAt: string;
  903. updatedAt: string;
  904. }
  905. export interface SpecialTrainingPage extends OperationsPage<SpecialTrainingItem> {
  906. sourceStatus?: {
  907. available: boolean;
  908. message: string;
  909. legacyTotal: number;
  910. created: number;
  911. updated: number;
  912. refreshedAt: string;
  913. } | null;
  914. }
  915. export interface MediaUploadTicket {
  916. ticketId: string;
  917. method: 'PUT';
  918. uploadUrl: string;
  919. publicUrl: string;
  920. objectKey: string;
  921. headers: Record<string, string>;
  922. expiresAt: string;
  923. }
  924. export interface ReviewReward {
  925. objectId: string; taskId: string; coachId: number; coachName: string; studentName: string;
  926. courseName: string; storeId: number; storeName: string; plannedDate: string; completedAt: string;
  927. incomeMonth: string; amount: number; status: 'earned' | 'historical' | 'exception' | 'ineligible';
  928. statusLabel: string; exception: string; ineligibleReason?: string; batchId?: string; settlementMonth?: string;
  929. canConfirm?: boolean; canCorrect?: boolean; batchStatus?: string; originalAmount?: number; correctionAmount?: number; correctionReason?: string; correctionId?: string; adjustmentBatchId?: string;
  930. }
  931. export interface ReviewRewardPage {
  932. snapshot?: string; nextCursor?: string;
  933. items: ReviewReward[]; total: number; page: number; pageSize: number;
  934. summary: { reviewCount: number; reviewAmount: number; historicalCount: number; exceptionCount: number };
  935. }
  936. export interface ReviewRewardFilters { month: string; storeId: number; search: string; coachId: number; revision?: number; resetRevision?: number; snapshot?: string; afterId?: string; exportMode?: boolean; }