admin.models.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. export interface ParsePointer {
  2. __type: 'Pointer';
  3. className: string;
  4. objectId: string;
  5. }
  6. export interface AdminIdentity {
  7. objectId: string;
  8. username: string;
  9. displayName: string;
  10. company?: ParsePointer;
  11. roles: string[];
  12. isSuperAdmin: boolean;
  13. operationsRole: OperationsRole;
  14. }
  15. export interface AdminSession {
  16. objectId: string;
  17. username: string;
  18. sessionToken: string;
  19. company?: ParsePointer;
  20. displayName?: string;
  21. roles?: string[];
  22. isSuperAdmin?: boolean;
  23. operationsRole?: OperationsRole;
  24. }
  25. export type OperationsRole = 'ops-manager' | 'ops-staff' | 'ops-auditor';
  26. export interface CloudEnvelope<T> {
  27. success: boolean;
  28. data: T;
  29. error?: string;
  30. message?: string;
  31. }
  32. export interface LegacyCloudEnvelope<T> {
  33. retcode: number;
  34. retmsg?: string;
  35. result: T;
  36. }
  37. export interface AppMigrationStatus {
  38. implemented: string[];
  39. blocked: Record<string, string>;
  40. }
  41. export interface AdminResourceCatalogItem {
  42. className: string;
  43. label: string;
  44. writable: boolean;
  45. route: string;
  46. }
  47. export interface AdminResourceCatalog {
  48. total: number;
  49. resources: AdminResourceCatalogItem[];
  50. }
  51. export interface AdminFieldSchema {
  52. name: string;
  53. type: string;
  54. targetClass?: string;
  55. required?: boolean;
  56. writable: boolean;
  57. }
  58. export interface AdminClassSchema {
  59. className: string;
  60. label: string;
  61. fields: AdminFieldSchema[];
  62. writable: boolean;
  63. creatable?: boolean;
  64. supportsSoftDelete: boolean;
  65. }
  66. export interface ResourcePage {
  67. className: string;
  68. page: number;
  69. pageSize: number;
  70. total: number;
  71. results: Record<string, unknown>[];
  72. }
  73. export interface DashboardMetric {
  74. className: string;
  75. label: string;
  76. count: number;
  77. route: string;
  78. }
  79. export interface DashboardData {
  80. identity: AdminIdentity;
  81. metrics: DashboardMetric[];
  82. migration: {
  83. sourceObjects: number;
  84. migratedClasses: number;
  85. registeredRecords: number;
  86. cloudFunctions: number;
  87. };
  88. }
  89. export interface OperationsPage<T extends Record<string, unknown> = Record<string, unknown>> {
  90. page: number;
  91. pageSize: number;
  92. total: number;
  93. items: T[];
  94. refreshedAt?: string;
  95. }
  96. export type LegacySyncHealth = 'healthy' | 'delayed' | 'error' | 'not_configured' | 'never_synced';
  97. export interface LegacySyncDataset extends Record<string, unknown> {
  98. key: string;
  99. label: string;
  100. legacyCount: number | null;
  101. parseCount: number;
  102. difference: number | null;
  103. cursor: string;
  104. lastSuccessAt: string;
  105. lastAttemptAt: string;
  106. lagSeconds: number | null;
  107. health: LegacySyncHealth;
  108. healthLabel: string;
  109. pendingFailures: number;
  110. conflicts: number;
  111. checksum?: string;
  112. }
  113. export interface LegacySyncStatus {
  114. configured: boolean;
  115. bridgeReachable: boolean;
  116. bridgeUrl: string;
  117. manifestTime: string;
  118. serverTime: string;
  119. refreshedAt: string;
  120. overallHealth: LegacySyncHealth;
  121. overallHealthLabel: string;
  122. totalFailures: number;
  123. totalConflicts: number;
  124. datasets: LegacySyncDataset[];
  125. message: string;
  126. }
  127. export interface LegacySyncRunResult {
  128. dataset: string;
  129. processed: number;
  130. created: number;
  131. updated: number;
  132. deleted: number;
  133. conflicts: number;
  134. failures: number;
  135. hasMore: boolean;
  136. cursor: string;
  137. startedAt: string;
  138. finishedAt: string;
  139. pages?: number;
  140. }
  141. export interface OperationsSyncIndicator {
  142. state: 'synced' | 'delayed' | 'not_configured';
  143. label: string;
  144. lastSuccessAt: string;
  145. lagSeconds: number | null;
  146. }
  147. export interface MemberSummary {
  148. total: number;
  149. todayNew: number;
  150. sevenDayNew: number;
  151. studying: number;
  152. followUp: number;
  153. }
  154. export interface MemberAgentOption extends Record<string, unknown> {
  155. objectId: string;
  156. userId: number;
  157. displayName: string;
  158. }
  159. export interface MemberPage extends OperationsPage<OperationsUser> {
  160. summary: MemberSummary;
  161. agents: MemberAgentOption[];
  162. sync?: OperationsSyncIndicator;
  163. sourceStatus?: {
  164. available: boolean;
  165. message: string;
  166. total: number;
  167. memberCount: number;
  168. coachEvidenceCount: number;
  169. storeCount: number;
  170. unknownCount: number;
  171. refreshedAt: string;
  172. };
  173. }
  174. export interface MemberImportPreviewRow {
  175. rowNumber: number;
  176. displayName: string;
  177. nickname: string;
  178. mobileMasked: string;
  179. gender: string;
  180. grade: string;
  181. parentUserId: number;
  182. action: 'create' | 'resumed' | 'error';
  183. issues: string[];
  184. }
  185. export interface MemberImportPreview {
  186. batchId: string;
  187. fileName: string;
  188. expiresAt: string;
  189. summary: { total: number; create: number; resumed: number; error: number };
  190. rows: MemberImportPreviewRow[];
  191. }
  192. export interface MemberImportResult {
  193. batchId: string;
  194. total: number;
  195. created: number;
  196. resumed: number;
  197. items: Array<{ objectId: string; userId: number; displayName: string; mobileMasked: string; resumed: boolean }>;
  198. }
  199. export interface MemberListSort {
  200. field: 'registeredAt' | 'lastLoginAt' | 'lastLessonAt' | 'nextAppointmentAt' | 'remainingPeriods';
  201. direction: 'asc' | 'desc';
  202. }
  203. export interface OperationsDashboardMetric {
  204. key: string;
  205. label: string;
  206. value: number;
  207. hint: string;
  208. route: string;
  209. tone: 'blue' | 'green' | 'amber' | 'rose';
  210. }
  211. export type ScheduleTrendPeriod = 'week' | 'month' | 'year';
  212. export interface ScheduleTrendPoint {
  213. key: string;
  214. label: string;
  215. shortLabel: string;
  216. courseCount: number;
  217. studentCount: number;
  218. completedCount: number;
  219. isToday: boolean;
  220. }
  221. export interface ScheduleTrendData {
  222. period: ScheduleTrendPeriod;
  223. periodLabel: string;
  224. dateFrom: string;
  225. dateTo: string;
  226. points: ScheduleTrendPoint[];
  227. todayCourseCount: number;
  228. todayStudentCount: number;
  229. totalCourseCount: number;
  230. totalStudentCount: number;
  231. maxValue: number;
  232. refreshedAt: string;
  233. liveSourceAvailable: boolean;
  234. liveSourceMessage: string;
  235. }
  236. export interface OperationsDashboardData {
  237. identity: AdminIdentity;
  238. date: string;
  239. metrics: OperationsDashboardMetric[];
  240. alerts: { key: string; label: string; value: number; route: string }[];
  241. refreshedAt?: string;
  242. liveSourceAvailable?: boolean;
  243. liveSourceMessage?: string;
  244. scheduleTrend?: ScheduleTrendData;
  245. sync?: OperationsSyncIndicator;
  246. payroll?: {
  247. month: string;
  248. coachCount: number;
  249. lessonCount: number;
  250. payableAmount: number;
  251. sourceStatus: PayrollSourceStatus;
  252. refreshedAt: string;
  253. };
  254. }
  255. export interface OperationsUser extends Record<string, unknown> {
  256. objectId: string;
  257. userId: number;
  258. displayName: string;
  259. username: string;
  260. mobile: string;
  261. mobileMasked: string;
  262. groupId: number;
  263. groupName: string;
  264. parentUserId: number;
  265. parentDisplayName: string;
  266. periods30: number;
  267. periods60: number;
  268. trialPeriods: number;
  269. activeCourseCount: number;
  270. completedCourseCount: number;
  271. studyRecordCount: number;
  272. appointmentCount: number;
  273. learningStatus: 'trial' | 'studying' | 'completed' | 'not_started';
  274. learningStatusLabel: string;
  275. learningStatusReason: string;
  276. registeredAt: string;
  277. lastLoginAt: string;
  278. loginCount: number;
  279. status: 'active' | 'disabled';
  280. statusLabel: string;
  281. canRevealMobile: boolean;
  282. updatedAt: string;
  283. nickname?: string;
  284. lastLessonAt?: string;
  285. nextAppointmentAt?: string;
  286. riskFlags?: string[];
  287. identityType?: 'member';
  288. gender?: string;
  289. grade?: string;
  290. noteCount?: number;
  291. liveOnly?: boolean;
  292. writeReady?: boolean;
  293. dataSource?: 'parse' | 'legacy-live';
  294. readOnlyReason?: string;
  295. }
  296. export interface OperationsUserDetail extends OperationsUser {
  297. email: string;
  298. balances: {
  299. purse: number;
  300. silverCoin: number;
  301. userExp: number;
  302. userPoint: number;
  303. dummyPurse: number;
  304. credit: number;
  305. };
  306. relation: { parentUserId: number; parentDisplayName: string };
  307. permissions: { canManage: boolean; canEdit: boolean; canAdjustBalance: boolean; canResetPassword: boolean; canRevealMobile: boolean };
  308. agents: MemberAgentOption[];
  309. }
  310. export interface CoachSummary extends Record<string, unknown> {
  311. objectId: string;
  312. userId: number;
  313. username: string;
  314. displayName: string;
  315. mobile: string;
  316. mobileMasked: string;
  317. parentUserId: number;
  318. storeName: string;
  319. status: 'active' | 'disabled';
  320. statusLabel: string;
  321. todayAppointments: number;
  322. pendingAppointments: number;
  323. totalAppointments: number;
  324. lastAppointmentAt: string;
  325. nextAppointmentAt?: string;
  326. monthLessons: number;
  327. monthHours: number;
  328. estimatedPay: number;
  329. averageScore: number;
  330. lastLessonAt: string;
  331. availabilityConfigured: boolean;
  332. updatedAt: string;
  333. source?: 'hybrid' | 'legacy-live';
  334. sourceLabel?: string;
  335. readOnly?: boolean;
  336. registeredAt?: string;
  337. hasCurrentBusiness?: boolean;
  338. }
  339. export interface CoachPageSummary extends Record<string, unknown> {
  340. total: number;
  341. active: number;
  342. disabled: number;
  343. monthActive: number;
  344. liveOnly: number;
  345. }
  346. export interface StoreSummary extends Record<string, unknown> {
  347. objectId: string;
  348. userId: number;
  349. username: string;
  350. storeName: string;
  351. contactName: string;
  352. mobile: string;
  353. mobileMasked: string;
  354. parentStoreId: number;
  355. parentStoreName: string;
  356. memberCount: number;
  357. coachCount: number;
  358. todayAppointments: number;
  359. totalAppointments: number;
  360. lastAppointmentAt: string;
  361. registeredAt: string;
  362. lastLoginAt: string;
  363. loginCount: number;
  364. status: 'active' | 'disabled';
  365. statusLabel: string;
  366. updatedAt: string;
  367. source?: 'parse' | 'hybrid' | 'legacy-live';
  368. sourceLabel?: string;
  369. readOnly?: boolean;
  370. }
  371. export interface StoreOption extends Record<string, unknown> {
  372. objectId: string;
  373. userId: number;
  374. displayName: string;
  375. }
  376. export interface StorePage extends OperationsPage<StoreSummary> {
  377. canManage: boolean;
  378. summary: {
  379. total: number;
  380. active: number;
  381. disabled: number;
  382. members: number;
  383. coaches: number;
  384. todayAppointments: number;
  385. liveOnly?: number;
  386. };
  387. parents: StoreOption[];
  388. liveSourceAvailable?: boolean;
  389. liveSourceMessage?: string;
  390. legacyStoreCount?: number;
  391. sync?: OperationsSyncIndicator;
  392. }
  393. export interface StoreCourseSummary extends Record<string, unknown> {
  394. courseId: number;
  395. courseName: string;
  396. appointmentCount: number;
  397. completedCount: number;
  398. studentCount: number;
  399. completionRate: number;
  400. lastAppointmentAt: string;
  401. }
  402. export interface StoreDetailData extends Record<string, unknown> {
  403. store: StoreSummary;
  404. month: string;
  405. summary: {
  406. memberCount: number;
  407. coachCount: number;
  408. monthAppointments: number;
  409. completedAppointments: number;
  410. activeStudents: number;
  411. lessonCount: number;
  412. totalHours: number;
  413. estimatedCoachPay: number;
  414. };
  415. members: OperationsUser[];
  416. coaches: CoachSummary[];
  417. appointments: ScheduleAppointment[];
  418. courses: StoreCourseSummary[];
  419. commissionStatus: {
  420. available: boolean;
  421. message: string;
  422. };
  423. payrollSourceStatus: PayrollSourceStatus;
  424. refreshedAt: string;
  425. sync?: OperationsSyncIndicator;
  426. }
  427. export interface CoachStoreOption extends Record<string, unknown> {
  428. objectId: string;
  429. userId: number;
  430. displayName: string;
  431. }
  432. export interface CoachPage extends OperationsPage<CoachSummary> {
  433. summary: CoachPageSummary;
  434. canManage: boolean;
  435. stores: CoachStoreOption[];
  436. liveSourceAvailable?: boolean;
  437. liveSourceMessage?: string;
  438. liveSourceCount?: number;
  439. dataDateFrom?: string;
  440. dataDateTo?: string;
  441. payrollMonth?: string;
  442. payrollSourceStatus?: PayrollSourceStatus;
  443. payrollRefreshedAt?: string;
  444. sync?: OperationsSyncIndicator;
  445. }
  446. export interface ScheduleAppointment extends Record<string, unknown> {
  447. targetId: string;
  448. generalId: number;
  449. studentObjectId: string;
  450. studentId: number;
  451. studentName: string;
  452. coachObjectId: string;
  453. coachId: number;
  454. coachName: string;
  455. courseId: number;
  456. courseName: string;
  457. classType: number;
  458. classTypeLabel: string;
  459. deliveryMethod: string;
  460. startsAt: string;
  461. endsAt: string;
  462. statusCode: number;
  463. statusLabel: string;
  464. cancelled: boolean;
  465. cancellationReason: string;
  466. coachAccountMissing: boolean;
  467. availabilityWarning: boolean;
  468. source: 'parse' | 'legacy-live';
  469. sourceLabel: string;
  470. readOnly: boolean;
  471. updatedAt: string;
  472. }
  473. export interface ScheduleCalendarData {
  474. dateFrom: string;
  475. dateTo: string;
  476. refreshedAt: string;
  477. totalAppointments: number;
  478. dataDateFrom: string;
  479. dataDateTo: string;
  480. liveSourceAvailable: boolean;
  481. liveSourceMessage: string;
  482. liveSourceCount: number;
  483. coaches: CoachSummary[];
  484. appointments: ScheduleAppointment[];
  485. unassigned: ScheduleAppointment[];
  486. 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 }>;
  487. courses: Array<{ courseId: number; courseName: string; memberIds: number[]; availableToAll?: boolean }>;
  488. sync?: OperationsSyncIndicator;
  489. }
  490. export interface CoachAvailability extends Record<string, unknown> {
  491. objectId: string;
  492. coachId: number;
  493. ruleType: 'weekly' | 'date';
  494. weekday: number;
  495. date: string;
  496. startTime: string;
  497. endTime: string;
  498. available: boolean;
  499. reason: string;
  500. updatedAt: string;
  501. }
  502. export interface CoachDetailData {
  503. coach: CoachSummary;
  504. profile: {
  505. username: string;
  506. mobile: string;
  507. storeName: string;
  508. statusLabel: string;
  509. sourceLabel: string;
  510. };
  511. currentPayroll: PayrollCoachSummary;
  512. availability: CoachAvailability[];
  513. appointments: ScheduleAppointment[];
  514. lessons: PayrollLine[];
  515. reviews: PayrollLine[];
  516. payrolls: PayrollSummary[];
  517. reviewTaskCount: number;
  518. payrollMonth: string;
  519. payrollSourceStatus: PayrollSourceStatus;
  520. refreshedAt: string;
  521. dataDateFrom: string;
  522. dataDateTo: string;
  523. }
  524. export interface PayRate extends Record<string, unknown> {
  525. objectId: string;
  526. classType: number;
  527. classTypeLabel: string;
  528. amount: number;
  529. durationHours: number;
  530. effectiveFrom: string;
  531. active: boolean;
  532. isDefault?: boolean;
  533. updatedAt: string;
  534. }
  535. export type PayrollStatus = 'draft' | 'reviewed' | 'paid';
  536. export interface PayrollSummary extends Record<string, unknown> {
  537. objectId: string;
  538. month: string;
  539. status: PayrollStatus;
  540. statusLabel: string;
  541. coachCount: number;
  542. lessonCount: number;
  543. totalHours: number;
  544. baseAmount: number;
  545. adjustmentAmount: number;
  546. payableAmount: number;
  547. exceptionCount: number;
  548. type4Count?: number;
  549. reviewedAt: string;
  550. paidAt: string;
  551. paymentMethod: string;
  552. paymentReference: string;
  553. updatedAt: string;
  554. }
  555. export interface PayrollLine extends Record<string, unknown> {
  556. objectId: string;
  557. kind: 'lesson' | 'adjustment';
  558. coachId: number;
  559. coachName: string;
  560. coachObjectId?: string;
  561. storeId: number;
  562. storeName: string;
  563. studentName: string;
  564. courseName: string;
  565. legacyGeneralId: number;
  566. lessonId: string;
  567. lessonAt: string;
  568. originalMonth: string;
  569. settlementMonth: string;
  570. classType: number;
  571. classTypeLabel: string;
  572. durationHours: number;
  573. rateAmount: number;
  574. amount: number;
  575. score?: number;
  576. comment?: string;
  577. description: string;
  578. exception: string;
  579. excluded?: boolean;
  580. }
  581. export interface PayrollDetail extends PayrollSummary {
  582. lines: PayrollLine[];
  583. exceptions: PayrollLine[];
  584. coachItems?: PayrollCoachSummary[];
  585. sourceStatus?: PayrollSourceStatus;
  586. refreshedAt?: string;
  587. }
  588. export interface PayrollCoachSummary extends Record<string, unknown> {
  589. coachId: number;
  590. coachName: string;
  591. coachObjectId?: string;
  592. storeId: number;
  593. storeName: string;
  594. type30Count: number;
  595. type60Count: number;
  596. trialCount: number;
  597. type4Count: number;
  598. lessonCount: number;
  599. totalHours: number;
  600. baseAmount: number;
  601. adjustmentAmount: number;
  602. payableAmount: number;
  603. averageScore?: number;
  604. latestLessonAt: string;
  605. }
  606. export interface PayrollSourceStatus extends Record<string, unknown> {
  607. available: boolean;
  608. mode: 'live' | 'cache';
  609. message: string;
  610. total: number;
  611. refreshedAt: string;
  612. }
  613. export interface PayrollOverview extends PayrollDetail {
  614. coachItems: PayrollCoachSummary[];
  615. sourceStatus: PayrollSourceStatus;
  616. batches: PayrollSummary[];
  617. stores: Array<{ storeId: number; storeName: string }>;
  618. }
  619. export interface PayrollExportData extends Record<string, unknown> {
  620. fileName: string;
  621. summary: PayrollSummary;
  622. coachItems: PayrollCoachSummary[];
  623. lines: PayrollLine[];
  624. exceptions: PayrollLine[];
  625. }
  626. export interface VocabularyNode extends Record<string, unknown> {
  627. objectId: string;
  628. nodeId: number;
  629. name: string;
  630. parentId: number;
  631. depth: number;
  632. orderId: number;
  633. directWordCount: number;
  634. wordCount: number;
  635. childCount: number;
  636. status: number;
  637. canContainWords: boolean;
  638. }
  639. export interface VocabularyWord extends Record<string, unknown> {
  640. targetId: string;
  641. generalId: number;
  642. itemId: number;
  643. word: string;
  644. meaning: string;
  645. phonetic: string;
  646. example: string;
  647. audioUrl: string;
  648. nodeId: number;
  649. unitName: string;
  650. bookName: string;
  651. stageName: string;
  652. orderId: number;
  653. status: number;
  654. statusLabel: string;
  655. createdAt: string;
  656. updatedAt: string;
  657. }
  658. export interface VocabularyTree {
  659. items: VocabularyNode[];
  660. total: number;
  661. totalWords: number;
  662. refreshedAt: string;
  663. }
  664. export interface VocabularyImportPreview {
  665. batchId: string;
  666. status: string;
  667. summary: { total: number; create: number; update: number; unchanged: number; duplicate: number; error: number; missingMedia: number };
  668. items: Array<Record<string, unknown>>;
  669. createdAt: string;
  670. }
  671. export interface SpecialTrainingItem extends Record<string, unknown> {
  672. targetId: string;
  673. generalId: number;
  674. itemId: number;
  675. title: string;
  676. synopsis: string;
  677. coverUrl: string;
  678. tags: string[];
  679. contentHtml: string;
  680. videoUrl: string;
  681. author: string;
  682. source: string;
  683. orderId: number;
  684. hits: number;
  685. status: number;
  686. statusLabel: string;
  687. createdAt: string;
  688. updatedAt: string;
  689. }
  690. export interface SpecialTrainingPage extends OperationsPage<SpecialTrainingItem> {
  691. sourceStatus?: {
  692. available: boolean;
  693. message: string;
  694. legacyTotal: number;
  695. created: number;
  696. updated: number;
  697. refreshedAt: string;
  698. } | null;
  699. }
  700. export interface MediaUploadTicket {
  701. ticketId: string;
  702. method: 'PUT';
  703. uploadUrl: string;
  704. publicUrl: string;
  705. objectKey: string;
  706. headers: Record<string, string>;
  707. expiresAt: string;
  708. }