@ingenx-io/valets-schema-mcp-server 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/data/docs/collections/firestore-paths.md +32 -20
  2. package/data/docs/enums/app-status.md +24 -0
  3. package/data/docs/enums/attention-status.md +2 -2
  4. package/data/docs/enums/booking-status.md +2 -2
  5. package/data/docs/enums/contract-status.md +24 -0
  6. package/data/docs/enums/customer-payment-status.md +2 -2
  7. package/data/docs/enums/customer-payment-target-type.md +2 -2
  8. package/data/docs/enums/delivery-type.md +2 -2
  9. package/data/docs/enums/deployment-link-type.md +2 -2
  10. package/data/docs/enums/event-status.md +2 -2
  11. package/data/docs/enums/fulfillment-status.md +2 -2
  12. package/data/docs/enums/loyalty-transaction-type.md +2 -2
  13. package/data/docs/enums/milestone-status.md +23 -0
  14. package/data/docs/enums/notification-channel.md +2 -2
  15. package/data/docs/enums/notification-entity-type.md +2 -2
  16. package/data/docs/enums/notification-status.md +2 -2
  17. package/data/docs/enums/order-status.md +2 -2
  18. package/data/docs/enums/outbound-message-format.md +2 -2
  19. package/data/docs/enums/outbound-message-purpose.md +2 -2
  20. package/data/docs/enums/outbound-message-status.md +2 -2
  21. package/data/docs/enums/payment-method.md +2 -2
  22. package/data/docs/enums/payment-proof-status.md +2 -2
  23. package/data/docs/enums/payment-status.md +2 -2
  24. package/data/docs/enums/pending-issue.md +2 -2
  25. package/data/docs/enums/return-status.md +2 -2
  26. package/data/docs/enums/session-status.md +2 -2
  27. package/data/docs/enums/site-status.md +2 -2
  28. package/data/docs/enums/stocktake-frequency.md +2 -2
  29. package/data/docs/enums/stocktake-item-status.md +2 -2
  30. package/data/docs/enums/stocktake-status.md +2 -2
  31. package/data/docs/enums/ticket-status.md +2 -2
  32. package/data/docs/enums/waba-label.md +3 -3
  33. package/data/docs/enums/whatsapp-button-sub-type.md +2 -2
  34. package/data/docs/enums/whatsapp-template-component.md +2 -2
  35. package/data/docs/enums/whatsapp-template-status.md +2 -2
  36. package/data/docs/index.md +17 -7
  37. package/data/docs/models/allowed-user.md +7 -7
  38. package/data/docs/models/analytics-backfill.md +7 -7
  39. package/data/docs/models/analytics-daily.md +6 -6
  40. package/data/docs/models/analytics-event.md +7 -7
  41. package/data/docs/models/analytics-hourly.md +6 -6
  42. package/data/docs/models/app-payment.md +200 -0
  43. package/data/docs/models/app.md +585 -0
  44. package/data/docs/models/booking-version.md +2 -2
  45. package/data/docs/models/booking.md +127 -127
  46. package/data/docs/models/contract.md +454 -0
  47. package/data/docs/models/customer-payment-allocation.md +20 -20
  48. package/data/docs/models/customer-payment.md +23 -23
  49. package/data/docs/models/customer.md +11 -11
  50. package/data/docs/models/event.md +22 -22
  51. package/data/docs/models/loyalty-config.md +4 -4
  52. package/data/docs/models/loyalty-reward.md +3 -3
  53. package/data/docs/models/loyalty-status.md +6 -6
  54. package/data/docs/models/loyalty-transaction.md +2 -2
  55. package/data/docs/models/magic-link-request.md +9 -9
  56. package/data/docs/models/metrics-current.md +169 -37
  57. package/data/docs/models/metrics-daily.md +172 -40
  58. package/data/docs/models/metrics-monthly.md +172 -40
  59. package/data/docs/models/notification-record.md +3 -3
  60. package/data/docs/models/order-item.md +6 -6
  61. package/data/docs/models/order.md +314 -294
  62. package/data/docs/models/outbound-payment-allocation.md +195 -0
  63. package/data/docs/models/outbound-payment.md +318 -0
  64. package/data/docs/models/payment-webhook-endpoint.md +191 -0
  65. package/data/docs/models/sale.md +18 -18
  66. package/data/docs/models/site-payment.md +2 -2
  67. package/data/docs/models/site.md +2 -2
  68. package/data/docs/models/stocktake-item.md +4 -4
  69. package/data/docs/models/stocktake.md +5 -5
  70. package/data/docs/models/ticket.md +3 -3
  71. package/data/docs/models/user.md +249 -0
  72. package/data/docs/models/whatsapp-inbound-message.md +6 -2
  73. package/data/docs/models/whatsapp-outbound-lifecycle-event.md +2 -2
  74. package/data/docs/models/whatsapp-outbound-message.md +43 -27
  75. package/data/docs/models/whatsapp-template.md +6 -2
  76. package/data/static/llms.txt +322 -36
  77. package/data/static/openapi.yaml +1074 -66
  78. package/data/static/schemas.json +1183 -104
  79. package/package.json +1 -1
@@ -20,6 +20,16 @@ info:
20
20
  paths: {}
21
21
  components:
22
22
  schemas:
23
+ AppStatus:
24
+ type: string
25
+ enum:
26
+ - ACTIVE
27
+ - INACTIVE
28
+ - EXPIRED
29
+ - ARCHIVED
30
+ description: 'Lifecycle status for an App (formerly Site — renamed per decision
31
+ #40 / D44). Drives whether the app is reachable and whether analytics/payments
32
+ flow.'
23
33
  AttentionStatus:
24
34
  type: string
25
35
  enum:
@@ -41,6 +51,16 @@ components:
41
51
  - COMPLETED_MIXED
42
52
  description: Booking lifecycle status. COMPLETED_MIXED = some sessions completed,
43
53
  others cancelled/no-show.
54
+ ContractStatus:
55
+ type: string
56
+ enum:
57
+ - DRAFT
58
+ - ACTIVE
59
+ - COMPLETED
60
+ - TERMINATED
61
+ description: Lifecycle status of a Contract. DRAFT = not yet signed. ACTIVE
62
+ = in force. COMPLETED = all milestones paid and obligations met. TERMINATED
63
+ = ended early by either party.
44
64
  CustomerPaymentStatus:
45
65
  type: string
46
66
  enum:
@@ -110,6 +130,14 @@ components:
110
130
  - BONUS
111
131
  - REFUND
112
132
  description: Loyalty point transaction type (D07). SCREAMING_SNAKE past tense.
133
+ MilestoneStatus:
134
+ type: string
135
+ enum:
136
+ - PENDING
137
+ - INVOICED
138
+ - PAID
139
+ description: Status of a ContractMilestone (#17). PENDING = created, not yet
140
+ invoiced. INVOICED = invoice issued, awaiting payment. PAID = payment received.
113
141
  NotificationChannel:
114
142
  type: string
115
143
  enum:
@@ -299,8 +327,9 @@ components:
299
327
  enum:
300
328
  - cmz
301
329
  - val
302
- description: Human-readable WABA label identifying which Meta business number
303
- received the message (GH#36).
330
+ description: WABA label identifying which Meta Business Account sent/received
331
+ the message. cmz = Chez Miss Zahoui sender; val = Valets sender. See WhatsApp
332
+ platform constraints doc (#50).
304
333
  WhatsappButtonSubType:
305
334
  type: string
306
335
  enum:
@@ -345,7 +374,9 @@ components:
345
374
  - _seconds
346
375
  - _nanoseconds
347
376
  additionalProperties: false
348
- description: Firestore Timestamp serialized representation
377
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
378
+ }. See types/firestore.ts for REST API v1 and client SDK serialization notes
379
+ (#10).'
349
380
  AllowedUser:
350
381
  type: object
351
382
  properties:
@@ -360,10 +391,11 @@ components:
360
391
  type: string
361
392
  x-immutable: true
362
393
  description: (Immutable) FK → Company document ID.
363
- siteId:
394
+ appId:
364
395
  type: string
365
396
  x-immutable: true
366
- description: (Immutable) FK → Site document ID (D40 sub-tenant scope).
397
+ description: (Immutable) FK → App document ID (D40 sub-tenant scope, renamed
398
+ per D44).
367
399
  contact:
368
400
  type: string
369
401
  description: Email or E.164 phone number. Canonical identifier for this
@@ -388,7 +420,7 @@ components:
388
420
  description: RFC3339Nano UTC when payment was completed.
389
421
  required:
390
422
  - companyId
391
- - siteId
423
+ - appId
392
424
  - contact
393
425
  - tier
394
426
  - amount
@@ -396,7 +428,7 @@ components:
396
428
  - transactionId
397
429
  - paidAt
398
430
  additionalProperties: false
399
- description: 'AllowedUser model (D40 / ING-304). Collection: companies/{companyId}/sites/{siteId}/allowed_users/{contactId}.
431
+ description: 'AllowedUser model (D40 / ING-304). Collection: companies/{companyId}/apps/{appId}/allowed_users/{contactId}.
400
432
  Authoritative paid-access allowlist. Upsert semantics — tier upgrades overwrite.
401
433
  Source of truth for access checks and referral code resolution.'
402
434
  AllowedUserCreate:
@@ -407,7 +439,7 @@ components:
407
439
  may be set once at creation.
408
440
  required:
409
441
  - companyId
410
- - siteId
442
+ - appId
411
443
  - contact
412
444
  - tier
413
445
  - amount
@@ -433,7 +465,7 @@ components:
433
465
  type: string
434
466
  x-immutable: true
435
467
  description: (Immutable) FK → Company document ID.
436
- siteId:
468
+ appId:
437
469
  type: string
438
470
  x-immutable: true
439
471
  description: (Immutable) FK → Site document ID (D40).
@@ -501,7 +533,7 @@ components:
501
533
  description: (Read-only) When the run reached a terminal status.
502
534
  required:
503
535
  - companyId
504
- - siteId
536
+ - appId
505
537
  - status
506
538
  - from
507
539
  - to
@@ -511,7 +543,7 @@ components:
511
543
  - triggeredBy
512
544
  - startedAt
513
545
  additionalProperties: false
514
- description: 'AnalyticsBackfill run (D42 / ING-304). Collection: companies/{companyId}/sites/{siteId}/analytics_backfills/{runId}.
546
+ description: 'AnalyticsBackfill run (D42 / ING-304). Collection: companies/{companyId}/apps/{appId}/analytics_backfills/{runId}.
515
547
  Tracks admin-triggered rollup backfill jobs; supports dry-run.'
516
548
  AnalyticsBackfillCreate:
517
549
  allOf:
@@ -521,7 +553,7 @@ components:
521
553
  may be set once at creation.
522
554
  required:
523
555
  - companyId
524
- - siteId
556
+ - appId
525
557
  - status
526
558
  - from
527
559
  - to
@@ -607,7 +639,7 @@ components:
607
639
  type: string
608
640
  x-immutable: true
609
641
  description: (Immutable) FK → Company document ID.
610
- siteId:
642
+ appId:
611
643
  type: string
612
644
  x-immutable: true
613
645
  description: (Immutable) FK → Site document ID (D40).
@@ -641,11 +673,11 @@ components:
641
673
  - errors
642
674
  - eventCounts
643
675
  - companyId
644
- - siteId
676
+ - appId
645
677
  - date
646
678
  - computedAt
647
679
  additionalProperties: false
648
- description: 'AnalyticsDaily rollup (D42 / ING-304). Collection: companies/{companyId}/sites/{siteId}/analytics_daily/{YYYY-MM-DD}.
680
+ description: 'AnalyticsDaily rollup (D42 / ING-304). Collection: companies/{companyId}/apps/{appId}/analytics_daily/{YYYY-MM-DD}.
649
681
  Idempotent set/merge — reruns overwrite. Fall back to raw analytics_events
650
682
  for uncovered slices.'
651
683
  AnalyticsDailyCreate:
@@ -666,7 +698,7 @@ components:
666
698
  - errors
667
699
  - eventCounts
668
700
  - companyId
669
- - siteId
701
+ - appId
670
702
  - date
671
703
  AnalyticsDailyUpdate:
672
704
  allOf:
@@ -687,10 +719,11 @@ components:
687
719
  type: string
688
720
  x-immutable: true
689
721
  description: (Immutable) FK → Company document ID.
690
- siteId:
722
+ appId:
691
723
  type: string
692
724
  x-immutable: true
693
- description: (Immutable) FK → Site document ID (D40 sub-tenant scope).
725
+ description: (Immutable) FK → App document ID (D40 sub-tenant scope, renamed
726
+ per D44).
694
727
  eventId:
695
728
  type: string
696
729
  x-immutable: true
@@ -806,7 +839,7 @@ components:
806
839
  additionalProperties: {}
807
840
  required:
808
841
  - companyId
809
- - siteId
842
+ - appId
810
843
  - eventId
811
844
  - eventName
812
845
  - timestamp
@@ -816,7 +849,7 @@ components:
816
849
  - properties
817
850
  - context
818
851
  additionalProperties: false
819
- description: 'AnalyticsEvent model (D40 / ING-304). Collection: companies/{companyId}/sites/{siteId}/analytics_events/{eventId}.
852
+ description: 'AnalyticsEvent model (D40 / ING-304). Collection: companies/{companyId}/apps/{appId}/analytics_events/{eventId}.
820
853
  Append-only, immutable product/behavior event stream. Event names are free
821
854
  strings; canonical vocabulary in CANONICAL_ANALYTICS_EVENT_NAMES.'
822
855
  AnalyticsEventCreate:
@@ -827,7 +860,7 @@ components:
827
860
  may be set once at creation.
828
861
  required:
829
862
  - companyId
830
- - siteId
863
+ - appId
831
864
  - eventId
832
865
  - eventName
833
866
  - timestamp
@@ -913,7 +946,7 @@ components:
913
946
  type: string
914
947
  x-immutable: true
915
948
  description: (Immutable) FK → Company document ID.
916
- siteId:
949
+ appId:
917
950
  type: string
918
951
  x-immutable: true
919
952
  description: (Immutable) FK → Site document ID (D40).
@@ -952,12 +985,12 @@ components:
952
985
  - errors
953
986
  - eventCounts
954
987
  - companyId
955
- - siteId
988
+ - appId
956
989
  - date
957
990
  - hour
958
991
  - computedAt
959
992
  additionalProperties: false
960
- description: 'AnalyticsHourly rollup (D42 / ING-304). Collection: companies/{companyId}/sites/{siteId}/analytics_hourly/{YYYY-MM-DD-HH}.
993
+ description: 'AnalyticsHourly rollup (D42 / ING-304). Collection: companies/{companyId}/apps/{appId}/analytics_hourly/{YYYY-MM-DD-HH}.
961
994
  Scheduled-CF writer; idempotent set/merge.'
962
995
  AnalyticsHourlyCreate:
963
996
  allOf:
@@ -977,7 +1010,7 @@ components:
977
1010
  - errors
978
1011
  - eventCounts
979
1012
  - companyId
980
- - siteId
1013
+ - appId
981
1014
  - date
982
1015
  - hour
983
1016
  AnalyticsHourlyUpdate:
@@ -986,6 +1019,267 @@ components:
986
1019
  description: Write payload for partial update (PATCH) of a AnalyticsHourly document.
987
1020
  All fields optional. Fields marked `readOnly` or `x-immutable` must not be
988
1021
  sent.
1022
+ App:
1023
+ type: object
1024
+ properties:
1025
+ id:
1026
+ readOnly: true
1027
+ description: (Read-only) Firestore document ID, auto-generated.
1028
+ type:
1029
+ - string
1030
+ - 'null'
1031
+ companyId:
1032
+ type: string
1033
+ x-immutable: true
1034
+ description: (Immutable) FK → Company document ID. Scopes all app sub-collections.
1035
+ name:
1036
+ type: string
1037
+ description: Human-readable app name shown in dashboards.
1038
+ description:
1039
+ description: Optional freeform description.
1040
+ type:
1041
+ - string
1042
+ - 'null'
1043
+ status:
1044
+ anyOf:
1045
+ - $ref: '#/components/schemas/AppStatus'
1046
+ - type: 'null'
1047
+ x-note: Real Firestore documents may be missing this field on pre-backfill
1048
+ records (#26). Treat absent as ACTIVE. A backfill script will populate
1049
+ status=ACTIVE on all existing documents; once confirmed, this field will
1050
+ be promoted to required.
1051
+ x-when: Written by operators via dashboard or by server triggers (expiration).
1052
+ Clients must not use server-side `where(status == ACTIVE)` until backfill
1053
+ is complete — filter client-side instead, treating absent as ACTIVE.
1054
+ description: Lifecycle status (D41/D44). Optional pending backfill of pre-existing
1055
+ records (#26) — treat absent as ACTIVE. Clients filter by ACTIVE.
1056
+ x-see:
1057
+ decisions:
1058
+ - D41
1059
+ - D44
1060
+ deploymentLinks:
1061
+ type: array
1062
+ items:
1063
+ type: object
1064
+ properties:
1065
+ label:
1066
+ type: string
1067
+ description: Human-readable label shown in dashboards (e.g. "Production",
1068
+ "Staging").
1069
+ url:
1070
+ type: string
1071
+ description: Absolute URL or store link.
1072
+ type:
1073
+ $ref: '#/components/schemas/DeploymentLinkType'
1074
+ description: Link category — drives icon/handler selection.
1075
+ x-note: Lowercase by convention — deployment link types are display-oriented
1076
+ labels, not lifecycle states.
1077
+ x-see:
1078
+ decisions:
1079
+ - D41
1080
+ isPrimary:
1081
+ description: If true, this link is used as the canonical deployment
1082
+ URL for the App.
1083
+ type: boolean
1084
+ required:
1085
+ - label
1086
+ - url
1087
+ - type
1088
+ additionalProperties: false
1089
+ description: Deployment link entry embedded on App.deploymentLinks[] (D41).
1090
+ description: Ordered list of deployment URLs (web, mobile, PWA, store links).
1091
+ expiresAt:
1092
+ anyOf:
1093
+ - $ref: '#/components/schemas/FirestoreTimestamp'
1094
+ - type: 'null'
1095
+ description: Optional expiration timestamp. When set and elapsed, `isExpired`
1096
+ flips true and status typically moves to EXPIRED.
1097
+ isExpired:
1098
+ readOnly: true
1099
+ description: (Read-only) Derived — true when `expiresAt` is in the past.
1100
+ Maintained by server trigger.
1101
+ type:
1102
+ - boolean
1103
+ - 'null'
1104
+ createdAt:
1105
+ anyOf:
1106
+ - $ref: '#/components/schemas/FirestoreTimestamp'
1107
+ - type: 'null'
1108
+ readOnly: true
1109
+ description: (Read-only) Server-generated creation timestamp.
1110
+ updatedAt:
1111
+ anyOf:
1112
+ - $ref: '#/components/schemas/FirestoreTimestamp'
1113
+ - type: 'null'
1114
+ readOnly: true
1115
+ description: (Read-only) Server-generated update timestamp.
1116
+ createdBy:
1117
+ type: string
1118
+ x-immutable: true
1119
+ description: (Immutable) FK → User/staff UID who created the app.
1120
+ analyticsEnabled:
1121
+ type: boolean
1122
+ description: Feature flag — when false, clients should not emit analytics
1123
+ events for this app.
1124
+ lastAnalyticsSync:
1125
+ anyOf:
1126
+ - $ref: '#/components/schemas/FirestoreTimestamp'
1127
+ - type: 'null'
1128
+ readOnly: true
1129
+ description: (Read-only) Last time the analytics rollup pipeline refreshed
1130
+ `cachedMetrics`.
1131
+ cachedMetrics:
1132
+ readOnly: true
1133
+ denormalized: true
1134
+ x-note: Updated by the rollup pipeline (D42). Readers should treat this
1135
+ as a hint; authoritative counts live in analytics_daily/analytics_hourly.
1136
+ x-see:
1137
+ decisions:
1138
+ - D41
1139
+ - D42
1140
+ description: (Read-only, Denormalized) Cached metrics snapshot for quick
1141
+ dashboard rendering.
1142
+ type:
1143
+ - object
1144
+ - 'null'
1145
+ properties:
1146
+ totalEvents:
1147
+ type: integer
1148
+ minimum: -9007199254740991
1149
+ maximum: 9007199254740991
1150
+ description: All-time event count cached on the App.
1151
+ totalPageViews:
1152
+ type: integer
1153
+ minimum: -9007199254740991
1154
+ maximum: 9007199254740991
1155
+ description: All-time `page_view` + `screen_view` count.
1156
+ totalSessions:
1157
+ type: integer
1158
+ minimum: -9007199254740991
1159
+ maximum: 9007199254740991
1160
+ description: All-time distinct session count.
1161
+ totalOrders:
1162
+ type: integer
1163
+ minimum: -9007199254740991
1164
+ maximum: 9007199254740991
1165
+ description: All-time order count attributed to this app (via `Order.appId`,
1166
+ D43).
1167
+ lastEventAt:
1168
+ $ref: '#/components/schemas/FirestoreTimestamp'
1169
+ description: Timestamp of the most recent analytics event seen for this
1170
+ app.
1171
+ required:
1172
+ - totalEvents
1173
+ - totalPageViews
1174
+ - totalSessions
1175
+ - totalOrders
1176
+ additionalProperties: false
1177
+ required:
1178
+ - companyId
1179
+ - name
1180
+ - deploymentLinks
1181
+ - createdBy
1182
+ - analyticsEnabled
1183
+ additionalProperties: false
1184
+ description: 'App model (D41 / ING-304). Collection: companies/{companyId}/apps/{appId}.
1185
+ Per-company product surface. Sub-collections (magic_link_requests, allowed_users,
1186
+ payments, analytics_events, analytics_daily, analytics_hourly, analytics_backfills)
1187
+ live under this document per D40/D42.'
1188
+ AppCreate:
1189
+ allOf:
1190
+ - $ref: '#/components/schemas/App'
1191
+ description: Write payload for creating a new App document. Fields marked `readOnly`
1192
+ are server-set and must not be included. Fields marked `x-immutable` may be
1193
+ set once at creation.
1194
+ required:
1195
+ - companyId
1196
+ - name
1197
+ - deploymentLinks
1198
+ - createdBy
1199
+ - analyticsEnabled
1200
+ AppUpdate:
1201
+ allOf:
1202
+ - $ref: '#/components/schemas/App'
1203
+ description: Write payload for partial update (PATCH) of a App document. All
1204
+ fields optional. Fields marked `readOnly` or `x-immutable` must not be sent.
1205
+ AppPayment:
1206
+ type: object
1207
+ properties:
1208
+ id:
1209
+ readOnly: true
1210
+ description: (Read-only) Firestore document ID, auto-generated.
1211
+ type:
1212
+ - string
1213
+ - 'null'
1214
+ companyId:
1215
+ type: string
1216
+ x-immutable: true
1217
+ description: (Immutable) FK → Company document ID.
1218
+ appId:
1219
+ type: string
1220
+ x-immutable: true
1221
+ description: (Immutable) FK → App document ID (D40 sub-tenant scope, renamed
1222
+ from siteId per D44).
1223
+ contact:
1224
+ type: string
1225
+ description: Email or E.164 phone number. Matches AllowedUser.contact.
1226
+ sessionId:
1227
+ type: string
1228
+ description: Payment provider checkout session ID (e.g. Wave). Usable for
1229
+ dedup across dual writes.
1230
+ transactionId:
1231
+ type: string
1232
+ description: Payment provider transaction ID.
1233
+ tier:
1234
+ type: string
1235
+ description: Access tier. Free string per app (ING-304 open question).
1236
+ amount:
1237
+ type: number
1238
+ description: Amount paid. Generalized from amount_xof per D40 decision.
1239
+ currency:
1240
+ default: XOF
1241
+ description: Currency code (ISO 4217). Defaults to XOF for legacy SR-Single
1242
+ parity.
1243
+ type: string
1244
+ paidAt:
1245
+ $ref: '#/components/schemas/FirestoreTimestamp'
1246
+ description: RFC3339Nano UTC when payment was completed.
1247
+ required:
1248
+ - companyId
1249
+ - appId
1250
+ - contact
1251
+ - sessionId
1252
+ - transactionId
1253
+ - tier
1254
+ - amount
1255
+ - currency
1256
+ - paidAt
1257
+ additionalProperties: false
1258
+ description: 'AppPayment model (D40 / ING-304). Collection: companies/{companyId}/apps/{appId}/payments/{paymentId}.
1259
+ Immutable append-only transaction ledger. Distinct from AllowedUser (access
1260
+ state) and CustomerPayment (D22 — customer-level billing).'
1261
+ AppPaymentCreate:
1262
+ allOf:
1263
+ - $ref: '#/components/schemas/AppPayment'
1264
+ description: Write payload for creating a new AppPayment document. Fields marked
1265
+ `readOnly` are server-set and must not be included. Fields marked `x-immutable`
1266
+ may be set once at creation.
1267
+ required:
1268
+ - companyId
1269
+ - appId
1270
+ - contact
1271
+ - sessionId
1272
+ - transactionId
1273
+ - tier
1274
+ - amount
1275
+ - currency
1276
+ - paidAt
1277
+ AppPaymentUpdate:
1278
+ allOf:
1279
+ - $ref: '#/components/schemas/AppPayment'
1280
+ description: Write payload for partial update (PATCH) of a AppPayment document.
1281
+ All fields optional. Fields marked `readOnly` or `x-immutable` must not be
1282
+ sent.
989
1283
  Booking:
990
1284
  type: object
991
1285
  properties:
@@ -1062,13 +1356,17 @@ components:
1062
1356
  type: boolean
1063
1357
  _deletedAt:
1064
1358
  $ref: '#/components/schemas/FirestoreTimestamp'
1065
- description: Firestore Timestamp serialized representation
1359
+ description: 'Firestore Timestamp Admin SDK form: { _seconds,
1360
+ _nanoseconds }. See types/firestore.ts for REST API v1 and
1361
+ client SDK serialization notes (#10).'
1066
1362
  _deletedBy:
1067
1363
  description: FK → User/staff UID who soft-deleted this item.
1068
1364
  type: string
1069
1365
  _lastModifiedAt:
1070
1366
  $ref: '#/components/schemas/FirestoreTimestamp'
1071
- description: Firestore Timestamp serialized representation
1367
+ description: 'Firestore Timestamp Admin SDK form: { _seconds,
1368
+ _nanoseconds }. See types/firestore.ts for REST API v1 and
1369
+ client SDK serialization notes (#10).'
1072
1370
  _lastModifiedBy:
1073
1371
  description: FK → User/staff UID who last modified this item.
1074
1372
  type: string
@@ -1133,7 +1431,9 @@ components:
1133
1431
  type: string
1134
1432
  additionalProperties:
1135
1433
  $ref: '#/components/schemas/FirestoreTimestamp'
1136
- description: Firestore Timestamp serialized representation
1434
+ description: 'Firestore Timestamp Admin SDK form: { _seconds,
1435
+ _nanoseconds }. See types/firestore.ts for REST API v1 and client
1436
+ SDK serialization notes (#10).'
1137
1437
  slotStatusUpdatedBy:
1138
1438
  type: object
1139
1439
  propertyNames:
@@ -1145,13 +1445,17 @@ components:
1145
1445
  type: boolean
1146
1446
  _deletedAt:
1147
1447
  $ref: '#/components/schemas/FirestoreTimestamp'
1148
- description: Firestore Timestamp serialized representation
1448
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
1449
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
1450
+ notes (#10).'
1149
1451
  _deletedBy:
1150
1452
  description: FK → User/staff UID who soft-deleted this item.
1151
1453
  type: string
1152
1454
  _lastModifiedAt:
1153
1455
  $ref: '#/components/schemas/FirestoreTimestamp'
1154
- description: Firestore Timestamp serialized representation
1456
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
1457
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
1458
+ notes (#10).'
1155
1459
  _lastModifiedBy:
1156
1460
  description: FK → User/staff UID who last modified this item.
1157
1461
  type: string
@@ -1313,13 +1617,17 @@ components:
1313
1617
  type: boolean
1314
1618
  _deletedAt:
1315
1619
  $ref: '#/components/schemas/FirestoreTimestamp'
1316
- description: Firestore Timestamp serialized representation
1620
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
1621
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
1622
+ notes (#10).'
1317
1623
  _deletedBy:
1318
1624
  description: FK → User/staff UID who soft-deleted this item.
1319
1625
  type: string
1320
1626
  _lastModifiedAt:
1321
1627
  $ref: '#/components/schemas/FirestoreTimestamp'
1322
- description: Firestore Timestamp serialized representation
1628
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
1629
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
1630
+ notes (#10).'
1323
1631
  _lastModifiedBy:
1324
1632
  description: FK → User/staff UID who last modified this item.
1325
1633
  type: string
@@ -1372,7 +1680,9 @@ components:
1372
1680
  anyOf:
1373
1681
  - $ref: '#/components/schemas/FirestoreTimestamp'
1374
1682
  - type: 'null'
1375
- description: Firestore Timestamp serialized representation
1683
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
1684
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
1685
+ notes (#10).'
1376
1686
  paymentProofUrl:
1377
1687
  description: URL to uploaded payment proof image/document.
1378
1688
  type:
@@ -1388,7 +1698,9 @@ components:
1388
1698
  anyOf:
1389
1699
  - $ref: '#/components/schemas/FirestoreTimestamp'
1390
1700
  - type: 'null'
1391
- description: Firestore Timestamp serialized representation
1701
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
1702
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
1703
+ notes (#10).'
1392
1704
  paymentProofAddedBy:
1393
1705
  description: FK → User/staff UID who uploaded the payment proof.
1394
1706
  type:
@@ -1403,7 +1715,9 @@ components:
1403
1715
  anyOf:
1404
1716
  - $ref: '#/components/schemas/FirestoreTimestamp'
1405
1717
  - type: 'null'
1406
- description: Firestore Timestamp serialized representation
1718
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
1719
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
1720
+ notes (#10).'
1407
1721
  paymentProofRejectionReason:
1408
1722
  type:
1409
1723
  - string
@@ -1426,7 +1740,9 @@ components:
1426
1740
  anyOf:
1427
1741
  - $ref: '#/components/schemas/FirestoreTimestamp'
1428
1742
  - type: 'null'
1429
- description: Firestore Timestamp serialized representation
1743
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
1744
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
1745
+ notes (#10).'
1430
1746
  cancelledByRole:
1431
1747
  type:
1432
1748
  - string
@@ -1587,6 +1903,156 @@ components:
1587
1903
  description: 'BookingVersion model (D18). Collection: companies/{companyId}/bookings/{bookingId}/versions/{versionId}.
1588
1904
  Server-owned audit trail written by Firebase trigger on every Booking mutation.
1589
1905
  Captures writes from all clients.'
1906
+ Contract:
1907
+ type: object
1908
+ properties:
1909
+ id:
1910
+ readOnly: true
1911
+ description: (Read-only) Firestore document ID, auto-generated.
1912
+ type:
1913
+ - string
1914
+ - 'null'
1915
+ companyId:
1916
+ type: string
1917
+ x-immutable: true
1918
+ description: (Immutable) FK → Company document ID.
1919
+ payeeId:
1920
+ type: string
1921
+ x-immutable: true
1922
+ description: '(Immutable) FK → Payee document ID. Full Payee model tracked
1923
+ in #20.'
1924
+ title:
1925
+ type: string
1926
+ description: Contract title or reference name shown in dashboards.
1927
+ description:
1928
+ description: Optional freeform description of the contract scope.
1929
+ type:
1930
+ - string
1931
+ - 'null'
1932
+ status:
1933
+ $ref: '#/components/schemas/ContractStatus'
1934
+ description: Contract lifecycle status (#14). Query by ACTIVE — do not use
1935
+ an isActive boolean.
1936
+ currency:
1937
+ type: string
1938
+ const: XOF
1939
+ description: Currency code. Locked to XOF (#18). Multi-currency support
1940
+ requires a deliberate schema version bump.
1941
+ totalAmount:
1942
+ type: number
1943
+ description: Total contract value (XOF).
1944
+ startDate:
1945
+ type: string
1946
+ description: Contract start date (ISO 8601 YYYY-MM-DD).
1947
+ endDate:
1948
+ description: Contract end date (ISO 8601 YYYY-MM-DD). Absent for open-ended
1949
+ contracts.
1950
+ type:
1951
+ - string
1952
+ - 'null'
1953
+ milestones:
1954
+ description: Ordered list of payment milestones. Embedded in the contract
1955
+ document (#17).
1956
+ type:
1957
+ - array
1958
+ - 'null'
1959
+ items:
1960
+ type: object
1961
+ properties:
1962
+ id:
1963
+ type: string
1964
+ description: Client-generated milestone ID (UUID or slug). Unique
1965
+ within the contract.
1966
+ title:
1967
+ type: string
1968
+ description: Milestone description or deliverable name.
1969
+ amount:
1970
+ type: number
1971
+ description: Amount due at this milestone (XOF).
1972
+ dueDate:
1973
+ description: ISO 8601 date string (YYYY-MM-DD) when the milestone
1974
+ is due. OVERDUE is derived at read time from dueDate — not stored.
1975
+ type: string
1976
+ status:
1977
+ $ref: '#/components/schemas/MilestoneStatus'
1978
+ x-note: Existing Firestore documents may have lowercase values (pending,
1979
+ invoiced, paid) from before this enum was introduced (#17). Normalize
1980
+ on read or via migration script.
1981
+ description: Milestone payment status (#17). OVERDUE is derived at
1982
+ read time from dueDate, not stored.
1983
+ invoicedAt:
1984
+ $ref: '#/components/schemas/FirestoreTimestamp'
1985
+ description: When the invoice was issued for this milestone.
1986
+ paidAt:
1987
+ $ref: '#/components/schemas/FirestoreTimestamp'
1988
+ description: When payment was received for this milestone.
1989
+ notes:
1990
+ description: Optional notes for this milestone.
1991
+ type: string
1992
+ required:
1993
+ - id
1994
+ - title
1995
+ - amount
1996
+ - status
1997
+ additionalProperties: false
1998
+ description: ContractMilestone — embedded sub-object on Contract (#17).
1999
+ Not a separate collection.
2000
+ notes:
2001
+ description: Optional internal notes.
2002
+ type:
2003
+ - string
2004
+ - 'null'
2005
+ createdBy:
2006
+ type: string
2007
+ x-immutable: true
2008
+ description: (Immutable) FK → User/staff UID who created the contract.
2009
+ createdAt:
2010
+ anyOf:
2011
+ - $ref: '#/components/schemas/FirestoreTimestamp'
2012
+ - type: 'null'
2013
+ readOnly: true
2014
+ description: (Read-only) Server-generated creation timestamp.
2015
+ updatedAt:
2016
+ anyOf:
2017
+ - $ref: '#/components/schemas/FirestoreTimestamp'
2018
+ - type: 'null'
2019
+ readOnly: true
2020
+ description: (Read-only) Server-generated update timestamp.
2021
+ required:
2022
+ - companyId
2023
+ - payeeId
2024
+ - title
2025
+ - status
2026
+ - currency
2027
+ - totalAmount
2028
+ - startDate
2029
+ - createdBy
2030
+ additionalProperties: false
2031
+ description: 'Contract model (GH#14/#17/#18). Collection: companies/{companyId}/contracts/{contractId}.
2032
+ Service/supplier contract between a company and a Payee. No isActive field
2033
+ — query by status. Currency locked to XOF. Milestones use MilestoneStatus
2034
+ enum (no stored OVERDUE).'
2035
+ ContractCreate:
2036
+ allOf:
2037
+ - $ref: '#/components/schemas/Contract'
2038
+ description: Write payload for creating a new Contract document. Fields marked
2039
+ `readOnly` are server-set and must not be included. Fields marked `x-immutable`
2040
+ may be set once at creation.
2041
+ required:
2042
+ - companyId
2043
+ - payeeId
2044
+ - title
2045
+ - status
2046
+ - currency
2047
+ - totalAmount
2048
+ - startDate
2049
+ - createdBy
2050
+ ContractUpdate:
2051
+ allOf:
2052
+ - $ref: '#/components/schemas/Contract'
2053
+ description: Write payload for partial update (PATCH) of a Contract document.
2054
+ All fields optional. Fields marked `readOnly` or `x-immutable` must not be
2055
+ sent.
1590
2056
  Customer:
1591
2057
  type: object
1592
2058
  properties:
@@ -1633,7 +2099,9 @@ components:
1633
2099
  properties:
1634
2100
  timestamp:
1635
2101
  $ref: '#/components/schemas/FirestoreTimestamp'
1636
- description: Firestore Timestamp serialized representation
2102
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
2103
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
2104
+ notes (#10).'
1637
2105
  type:
1638
2106
  type: string
1639
2107
  description: Communication channel (e.g. phone, email, meeting).
@@ -1750,7 +2218,9 @@ components:
1750
2218
  description: Currency code. Locked to XOF (West African CFA franc) for now.
1751
2219
  paymentDate:
1752
2220
  $ref: '#/components/schemas/FirestoreTimestamp'
1753
- description: Firestore Timestamp serialized representation
2221
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
2222
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
2223
+ notes (#10).'
1754
2224
  paymentMethod:
1755
2225
  $ref: '#/components/schemas/PaymentMethod'
1756
2226
  description: Unified payment method set with African + global methods (D02).
@@ -1894,7 +2364,9 @@ components:
1894
2364
  anyOf:
1895
2365
  - $ref: '#/components/schemas/FirestoreTimestamp'
1896
2366
  - type: 'null'
1897
- description: Firestore Timestamp serialized representation
2367
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
2368
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
2369
+ notes (#10).'
1898
2370
  createdBy:
1899
2371
  type: string
1900
2372
  x-immutable: true
@@ -1967,12 +2439,16 @@ components:
1967
2439
  - 'null'
1968
2440
  startDate:
1969
2441
  $ref: '#/components/schemas/FirestoreTimestamp'
1970
- description: Firestore Timestamp serialized representation
2442
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
2443
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
2444
+ notes (#10).'
1971
2445
  endDate:
1972
2446
  anyOf:
1973
2447
  - $ref: '#/components/schemas/FirestoreTimestamp'
1974
2448
  - type: 'null'
1975
- description: Firestore Timestamp serialized representation
2449
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
2450
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
2451
+ notes (#10).'
1976
2452
  status:
1977
2453
  $ref: '#/components/schemas/EventStatus'
1978
2454
  description: Event lifecycle status (D32). SCREAMING_SNAKE per D04. MIG-09
@@ -2417,10 +2893,11 @@ components:
2417
2893
  type: string
2418
2894
  x-immutable: true
2419
2895
  description: (Immutable) FK → Company document ID.
2420
- siteId:
2896
+ appId:
2421
2897
  type: string
2422
2898
  x-immutable: true
2423
- description: (Immutable) FK → Site document ID (D40 sub-tenant scope).
2899
+ description: (Immutable) FK → App document ID (D40 sub-tenant scope, renamed
2900
+ per D44).
2424
2901
  email:
2425
2902
  type: string
2426
2903
  description: Populated if identifier is email; empty string if phone. Mutually
@@ -2472,7 +2949,7 @@ components:
2472
2949
  description: RFC3339Nano UTC at verify time.
2473
2950
  required:
2474
2951
  - companyId
2475
- - siteId
2952
+ - appId
2476
2953
  - email
2477
2954
  - phone
2478
2955
  - ip
@@ -2483,7 +2960,7 @@ components:
2483
2960
  - referredBy
2484
2961
  - requestedAt
2485
2962
  additionalProperties: false
2486
- description: 'MagicLinkRequest model (D40 / ING-304). Collection: companies/{companyId}/sites/{siteId}/magic_link_requests/{requestId}.
2963
+ description: 'MagicLinkRequest model (D40 / ING-304). Collection: companies/{companyId}/apps/{appId}/magic_link_requests/{requestId}.
2487
2964
  Authentication audit log — every request is logged regardless of outcome.
2488
2965
  Two-stage write: Log() then LogVerify().'
2489
2966
  MagicLinkRequestCreate:
@@ -2494,7 +2971,7 @@ components:
2494
2971
  may be set once at creation.
2495
2972
  required:
2496
2973
  - companyId
2497
- - siteId
2974
+ - appId
2498
2975
  - email
2499
2976
  - phone
2500
2977
  - ip
@@ -2650,8 +3127,64 @@ components:
2650
3127
  monthlyRecurringRevenue:
2651
3128
  type: number
2652
3129
  readOnly: true
2653
- description: (Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments.
2654
- Always full recalc.
3130
+ description: (Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments.
3131
+ Always full recalc.
3132
+ escalatedOrdersCount:
3133
+ readOnly: true
3134
+ x-note: Absent on tenants whose metrics/current was last written before
3135
+ aggregator v2. Backfill required for universal presence (#8).
3136
+ description: (Read-only, Optional) Orders currently in an escalated state.
3137
+ Present once the current aggregator rewrites the document.
3138
+ type:
3139
+ - integer
3140
+ - 'null'
3141
+ minimum: -9007199254740991
3142
+ maximum: 9007199254740991
3143
+ staleOrdersCount:
3144
+ readOnly: true
3145
+ x-note: Absent on tenants whose metrics/current was last written before
3146
+ aggregator v2. Backfill required for universal presence (#8).
3147
+ description: (Read-only, Optional) Orders that have been in an incomplete
3148
+ status beyond the stale threshold.
3149
+ type:
3150
+ - integer
3151
+ - 'null'
3152
+ minimum: -9007199254740991
3153
+ maximum: 9007199254740991
3154
+ staleBookingsCount:
3155
+ readOnly: true
3156
+ x-note: Absent on tenants whose metrics/current was last written before
3157
+ aggregator v2. Backfill required for universal presence (#8).
3158
+ description: (Read-only, Optional) Bookings that have been in an incomplete
3159
+ status beyond the stale threshold.
3160
+ type:
3161
+ - integer
3162
+ - 'null'
3163
+ minimum: -9007199254740991
3164
+ maximum: 9007199254740991
3165
+ onHoldOrdersCount:
3166
+ readOnly: true
3167
+ x-note: Absent on tenants whose metrics/current was last written before
3168
+ aggregator v2. Backfill required for universal presence (#8).
3169
+ description: (Read-only, Optional) Orders currently in an on-hold state.
3170
+ type:
3171
+ - integer
3172
+ - 'null'
3173
+ minimum: -9007199254740991
3174
+ maximum: 9007199254740991
3175
+ paymentsByMethod:
3176
+ readOnly: true
3177
+ x-note: Always {} until the first real payment is recorded. Present on all
3178
+ tenants once rewritten by current aggregator (#8).
3179
+ description: (Read-only, Optional) Map of PaymentMethod → total amount for
3180
+ the current day. Empty map {} until first payment.
3181
+ type:
3182
+ - object
3183
+ - 'null'
3184
+ propertyNames:
3185
+ type: string
3186
+ additionalProperties:
3187
+ type: number
2655
3188
  computedForDay:
2656
3189
  type: string
2657
3190
  readOnly: true
@@ -2845,6 +3378,62 @@ components:
2845
3378
  readOnly: true
2846
3379
  description: (Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments.
2847
3380
  Always full recalc.
3381
+ escalatedOrdersCount:
3382
+ readOnly: true
3383
+ x-note: Absent on tenants whose metrics/current was last written before
3384
+ aggregator v2. Backfill required for universal presence (#8).
3385
+ description: (Read-only, Optional) Orders currently in an escalated state.
3386
+ Present once the current aggregator rewrites the document.
3387
+ type:
3388
+ - integer
3389
+ - 'null'
3390
+ minimum: -9007199254740991
3391
+ maximum: 9007199254740991
3392
+ staleOrdersCount:
3393
+ readOnly: true
3394
+ x-note: Absent on tenants whose metrics/current was last written before
3395
+ aggregator v2. Backfill required for universal presence (#8).
3396
+ description: (Read-only, Optional) Orders that have been in an incomplete
3397
+ status beyond the stale threshold.
3398
+ type:
3399
+ - integer
3400
+ - 'null'
3401
+ minimum: -9007199254740991
3402
+ maximum: 9007199254740991
3403
+ staleBookingsCount:
3404
+ readOnly: true
3405
+ x-note: Absent on tenants whose metrics/current was last written before
3406
+ aggregator v2. Backfill required for universal presence (#8).
3407
+ description: (Read-only, Optional) Bookings that have been in an incomplete
3408
+ status beyond the stale threshold.
3409
+ type:
3410
+ - integer
3411
+ - 'null'
3412
+ minimum: -9007199254740991
3413
+ maximum: 9007199254740991
3414
+ onHoldOrdersCount:
3415
+ readOnly: true
3416
+ x-note: Absent on tenants whose metrics/current was last written before
3417
+ aggregator v2. Backfill required for universal presence (#8).
3418
+ description: (Read-only, Optional) Orders currently in an on-hold state.
3419
+ type:
3420
+ - integer
3421
+ - 'null'
3422
+ minimum: -9007199254740991
3423
+ maximum: 9007199254740991
3424
+ paymentsByMethod:
3425
+ readOnly: true
3426
+ x-note: Always {} until the first real payment is recorded. Present on all
3427
+ tenants once rewritten by current aggregator (#8).
3428
+ description: (Read-only, Optional) Map of PaymentMethod → total amount for
3429
+ the current day. Empty map {} until first payment.
3430
+ type:
3431
+ - object
3432
+ - 'null'
3433
+ propertyNames:
3434
+ type: string
3435
+ additionalProperties:
3436
+ type: number
2848
3437
  computedForDay:
2849
3438
  type: string
2850
3439
  readOnly: true
@@ -3044,6 +3633,62 @@ components:
3044
3633
  readOnly: true
3045
3634
  description: (Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments.
3046
3635
  Always full recalc.
3636
+ escalatedOrdersCount:
3637
+ readOnly: true
3638
+ x-note: Absent on tenants whose metrics/current was last written before
3639
+ aggregator v2. Backfill required for universal presence (#8).
3640
+ description: (Read-only, Optional) Orders currently in an escalated state.
3641
+ Present once the current aggregator rewrites the document.
3642
+ type:
3643
+ - integer
3644
+ - 'null'
3645
+ minimum: -9007199254740991
3646
+ maximum: 9007199254740991
3647
+ staleOrdersCount:
3648
+ readOnly: true
3649
+ x-note: Absent on tenants whose metrics/current was last written before
3650
+ aggregator v2. Backfill required for universal presence (#8).
3651
+ description: (Read-only, Optional) Orders that have been in an incomplete
3652
+ status beyond the stale threshold.
3653
+ type:
3654
+ - integer
3655
+ - 'null'
3656
+ minimum: -9007199254740991
3657
+ maximum: 9007199254740991
3658
+ staleBookingsCount:
3659
+ readOnly: true
3660
+ x-note: Absent on tenants whose metrics/current was last written before
3661
+ aggregator v2. Backfill required for universal presence (#8).
3662
+ description: (Read-only, Optional) Bookings that have been in an incomplete
3663
+ status beyond the stale threshold.
3664
+ type:
3665
+ - integer
3666
+ - 'null'
3667
+ minimum: -9007199254740991
3668
+ maximum: 9007199254740991
3669
+ onHoldOrdersCount:
3670
+ readOnly: true
3671
+ x-note: Absent on tenants whose metrics/current was last written before
3672
+ aggregator v2. Backfill required for universal presence (#8).
3673
+ description: (Read-only, Optional) Orders currently in an on-hold state.
3674
+ type:
3675
+ - integer
3676
+ - 'null'
3677
+ minimum: -9007199254740991
3678
+ maximum: 9007199254740991
3679
+ paymentsByMethod:
3680
+ readOnly: true
3681
+ x-note: Always {} until the first real payment is recorded. Present on all
3682
+ tenants once rewritten by current aggregator (#8).
3683
+ description: (Read-only, Optional) Map of PaymentMethod → total amount for
3684
+ the current day. Empty map {} until first payment.
3685
+ type:
3686
+ - object
3687
+ - 'null'
3688
+ propertyNames:
3689
+ type: string
3690
+ additionalProperties:
3691
+ type: number
3047
3692
  computedForDay:
3048
3693
  type: string
3049
3694
  readOnly: true
@@ -3263,9 +3908,11 @@ components:
3263
3908
  - createdAt
3264
3909
  additionalProperties: false
3265
3910
  description: 'NotificationRecord — backend-written, multi-channel notification
3266
- audit log (GH#48). Paths: companies/{cid}/notifications/{id} (always) and
3267
- companies/{cid}/orders/{oid}/notifications/{id} (dual-write when relatedEntity.type=order).
3268
- Consumers read only. Distinct from WhatsappOutboundMessage (GH#43).'
3911
+ audit log (GH#48). Paths: companies/{cid}/notification_records/{id} (always)
3912
+ and companies/{cid}/orders/{oid}/notification_records/{id} (dual-write when
3913
+ relatedEntity.type=order). Consumers read only. Distinct from WhatsappOutboundMessage
3914
+ (GH#43). Collection renamed from notifications → notification_records per
3915
+ decision #55 (2026-05-30).'
3269
3916
  Order:
3270
3917
  type: object
3271
3918
  properties:
@@ -3282,20 +3929,22 @@ components:
3282
3929
  type: string
3283
3930
  x-immutable: true
3284
3931
  description: (Immutable) FK → Company document ID. Scopes all queries.
3285
- siteId:
3932
+ appId:
3286
3933
  x-immutable: true
3287
- x-note: 'D43 / ADR-003: optional site attribution. Absent/null means company-wide
3288
- (legacy). When set, the order is attributed to a specific site for analytics
3289
- and site-scoped dashboards. No migration existing orders keep null.
3290
- Flat path; Order continues to live at `companies/{companyId}/orders/{orderId}`.'
3934
+ x-note: 'D43 / ADR-003: optional app attribution (renamed from siteId per
3935
+ D44). Absent/null means company-wide (legacy). When set, the order is
3936
+ attributed to a specific app for analytics and app-scoped dashboards.
3937
+ No migration — existing orders keep null. Flat path; Order continues to
3938
+ live at `companies/{companyId}/orders/{orderId}`.'
3291
3939
  x-see:
3292
3940
  decisions:
3293
3941
  - D43
3294
- x-when: 'Set at order creation when the client knows which site the order
3942
+ - D44
3943
+ x-when: 'Set at order creation when the client knows which app the order
3295
3944
  originated from (e.g. SR Single, Lifesense). Leave null for legacy/company-wide
3296
- orders. Composite index: (companyId, siteId, createdAt).'
3297
- description: (Immutable, Optional) FK → Site document ID (D43 / ADR-003).
3298
- null = company-wide.
3945
+ orders. Composite index: (companyId, appId, createdAt).'
3946
+ description: (Immutable, Optional) FK → App document ID (D43 / ADR-003,
3947
+ renamed from siteId per D44). null = company-wide.
3299
3948
  type:
3300
3949
  - string
3301
3950
  - 'null'
@@ -3303,6 +3952,17 @@ components:
3303
3952
  type: string
3304
3953
  readOnly: true
3305
3954
  description: (Read-only) Server-generated order number.
3955
+ followUpUrl:
3956
+ x-note: Standardized casing is followUpUrl (not followUpURL). Mobile legacy
3957
+ code reads both casings for backward compat but writes followUpUrl going
3958
+ forward (#6).
3959
+ x-when: 'Used by mobile to generate QR codes on printed receipts. If absent,
3960
+ the mobile app derives a deterministic fallback: https://zahoui.web.app/reviews?orderNumber={orderNumber}.'
3961
+ description: 'Optional URL for receipt QR code / post-order follow-up. Canonical
3962
+ casing: followUpUrl (#6).'
3963
+ type:
3964
+ - string
3965
+ - 'null'
3306
3966
  status:
3307
3967
  $ref: '#/components/schemas/OrderStatus'
3308
3968
  description: Core lifecycle status (D34, MIG-11). See OrderStatus enum for
@@ -3485,7 +4145,9 @@ components:
3485
4145
  readOnly: true
3486
4146
  orderDate:
3487
4147
  $ref: '#/components/schemas/FirestoreTimestamp'
3488
- description: Firestore Timestamp serialized representation
4148
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
4149
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
4150
+ notes (#10).'
3489
4151
  PROCESSING_ON:
3490
4152
  anyOf:
3491
4153
  - $ref: '#/components/schemas/FirestoreTimestamp'
@@ -3530,7 +4192,9 @@ components:
3530
4192
  anyOf:
3531
4193
  - $ref: '#/components/schemas/FirestoreTimestamp'
3532
4194
  - type: 'null'
3533
- description: Firestore Timestamp serialized representation
4195
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
4196
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
4197
+ notes (#10).'
3534
4198
  shippingCost:
3535
4199
  type:
3536
4200
  - number
@@ -3617,7 +4281,9 @@ components:
3617
4281
  description: Payment method used (D02).
3618
4282
  paymentDate:
3619
4283
  $ref: '#/components/schemas/FirestoreTimestamp'
3620
- description: Firestore Timestamp serialized representation
4284
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
4285
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
4286
+ notes (#10).'
3621
4287
  referenceNumber:
3622
4288
  description: Payment reference (receipt number, transaction ID, etc.).
3623
4289
  type: string
@@ -3831,6 +4497,235 @@ components:
3831
4497
  description: Write payload for partial update (PATCH) of a OrderItem document.
3832
4498
  All fields optional. Fields marked `readOnly` or `x-immutable` must not be
3833
4499
  sent.
4500
+ OutboundPayment:
4501
+ type: object
4502
+ properties:
4503
+ id:
4504
+ readOnly: true
4505
+ description: (Read-only) Firestore document ID, auto-generated.
4506
+ type:
4507
+ - string
4508
+ - 'null'
4509
+ companyId:
4510
+ type: string
4511
+ x-immutable: true
4512
+ description: (Immutable) FK → Company document ID.
4513
+ payeeId:
4514
+ type: string
4515
+ x-immutable: true
4516
+ description: '(Immutable) FK → Payee document ID. Full Payee model tracked
4517
+ in #20.'
4518
+ contractId:
4519
+ x-immutable: true
4520
+ description: (Immutable, Optional) FK → Contract document ID. Absent for
4521
+ ad-hoc payments outside a formal contract.
4522
+ type:
4523
+ - string
4524
+ - 'null'
4525
+ amount:
4526
+ type: number
4527
+ description: Amount paid (XOF).
4528
+ currency:
4529
+ type: string
4530
+ const: XOF
4531
+ description: Currency code. Locked to XOF — consistent with Contract and
4532
+ CustomerPayment.
4533
+ method:
4534
+ $ref: '#/components/schemas/PaymentMethod'
4535
+ description: Payment method used (e.g. BANK_TRANSFER, MOBILE_MONEY, CHECK).
4536
+ reference:
4537
+ description: Optional payment reference — bank transaction number, check
4538
+ number, Wave ref, etc.
4539
+ type:
4540
+ - string
4541
+ - 'null'
4542
+ paidAt:
4543
+ $ref: '#/components/schemas/FirestoreTimestamp'
4544
+ description: When the payment was made.
4545
+ notes:
4546
+ description: Optional internal notes.
4547
+ type:
4548
+ - string
4549
+ - 'null'
4550
+ createdBy:
4551
+ type: string
4552
+ x-immutable: true
4553
+ description: (Immutable) FK → User/staff UID who recorded the payment.
4554
+ createdAt:
4555
+ anyOf:
4556
+ - $ref: '#/components/schemas/FirestoreTimestamp'
4557
+ - type: 'null'
4558
+ readOnly: true
4559
+ description: (Read-only) Server-generated creation timestamp.
4560
+ updatedAt:
4561
+ anyOf:
4562
+ - $ref: '#/components/schemas/FirestoreTimestamp'
4563
+ - type: 'null'
4564
+ readOnly: true
4565
+ description: (Read-only) Server-generated update timestamp.
4566
+ required:
4567
+ - companyId
4568
+ - payeeId
4569
+ - amount
4570
+ - currency
4571
+ - method
4572
+ - paidAt
4573
+ - createdBy
4574
+ additionalProperties: false
4575
+ description: 'OutboundPayment (GH#15). Collection: companies/{companyId}/outboundPayments/{paymentId}.
4576
+ Single outbound transfer to a Payee. Canonical name chosen over SupplierPayment/VendorPayment
4577
+ — direction-based, agnostic of payee type. Currency locked to XOF.'
4578
+ OutboundPaymentCreate:
4579
+ allOf:
4580
+ - $ref: '#/components/schemas/OutboundPayment'
4581
+ description: Write payload for creating a new OutboundPayment document. Fields
4582
+ marked `readOnly` are server-set and must not be included. Fields marked `x-immutable`
4583
+ may be set once at creation.
4584
+ required:
4585
+ - companyId
4586
+ - payeeId
4587
+ - amount
4588
+ - currency
4589
+ - method
4590
+ - paidAt
4591
+ - createdBy
4592
+ OutboundPaymentUpdate:
4593
+ allOf:
4594
+ - $ref: '#/components/schemas/OutboundPayment'
4595
+ description: Write payload for partial update (PATCH) of a OutboundPayment document.
4596
+ All fields optional. Fields marked `readOnly` or `x-immutable` must not be
4597
+ sent.
4598
+ OutboundPaymentAllocation:
4599
+ type: object
4600
+ properties:
4601
+ id:
4602
+ readOnly: true
4603
+ description: (Read-only) Firestore document ID, auto-generated.
4604
+ type:
4605
+ - string
4606
+ - 'null'
4607
+ outboundPaymentId:
4608
+ type: string
4609
+ x-immutable: true
4610
+ description: (Immutable) FK → OutboundPayment document ID (parent).
4611
+ expenseId:
4612
+ type: string
4613
+ x-immutable: true
4614
+ description: '(Immutable) FK → Expense document ID. Full Expense model tracked
4615
+ in #20.'
4616
+ companyId:
4617
+ type: string
4618
+ x-immutable: true
4619
+ description: (Immutable) FK → Company document ID. Denormalized for collection-group
4620
+ queries.
4621
+ amount:
4622
+ type: number
4623
+ description: Amount of this payment allocated to the referenced expense
4624
+ (XOF).
4625
+ notes:
4626
+ description: Optional notes about this allocation.
4627
+ type:
4628
+ - string
4629
+ - 'null'
4630
+ allocatedAt:
4631
+ anyOf:
4632
+ - $ref: '#/components/schemas/FirestoreTimestamp'
4633
+ - type: 'null'
4634
+ readOnly: true
4635
+ description: (Read-only) When this allocation was recorded.
4636
+ required:
4637
+ - outboundPaymentId
4638
+ - expenseId
4639
+ - companyId
4640
+ - amount
4641
+ additionalProperties: false
4642
+ description: 'OutboundPaymentAllocation (GH#15). Subcollection: companies/{companyId}/outboundPayments/{paymentId}/allocations/{allocationId}.
4643
+ Links an OutboundPayment to an Expense. Supports partial and multi-expense
4644
+ allocations.'
4645
+ OutboundPaymentAllocationCreate:
4646
+ allOf:
4647
+ - $ref: '#/components/schemas/OutboundPaymentAllocation'
4648
+ description: Write payload for creating a new OutboundPaymentAllocation document.
4649
+ Fields marked `readOnly` are server-set and must not be included. Fields marked
4650
+ `x-immutable` may be set once at creation.
4651
+ required:
4652
+ - outboundPaymentId
4653
+ - expenseId
4654
+ - companyId
4655
+ - amount
4656
+ OutboundPaymentAllocationUpdate:
4657
+ allOf:
4658
+ - $ref: '#/components/schemas/OutboundPaymentAllocation'
4659
+ description: Write payload for partial update (PATCH) of a OutboundPaymentAllocation
4660
+ document. All fields optional. Fields marked `readOnly` or `x-immutable` must
4661
+ not be sent.
4662
+ PaymentWebhookEndpoint:
4663
+ type: object
4664
+ properties:
4665
+ token:
4666
+ type: string
4667
+ x-immutable: true
4668
+ description: (Immutable) base64url-encoded 24-byte random token. Also the
4669
+ Firestore document ID and the URL path segment.
4670
+ company:
4671
+ type: string
4672
+ x-immutable: true
4673
+ description: (Immutable) Company slug (e.g. gerko_studios). Identifies the
4674
+ tenant that owns this endpoint.
4675
+ provider:
4676
+ type: string
4677
+ enum:
4678
+ - wave
4679
+ - jeko
4680
+ x-immutable: true
4681
+ description: (Immutable) Payment provider whose webhooks this endpoint accepts.
4682
+ secret:
4683
+ type: string
4684
+ x-note: Provider-specific webhook signing secret. Used to verify HMAC-SHA256
4685
+ signatures on inbound payloads. Never expose to clients.
4686
+ readOnly: true
4687
+ description: Webhook signing secret for HMAC verification. Backend-only
4688
+ — never returned to dashboard or mobile clients.
4689
+ active:
4690
+ type: boolean
4691
+ description: Whether this endpoint is currently active. Inactive endpoints
4692
+ reject incoming webhooks.
4693
+ createdAt:
4694
+ anyOf:
4695
+ - $ref: '#/components/schemas/FirestoreTimestamp'
4696
+ - type: 'null'
4697
+ readOnly: true
4698
+ description: (Read-only) When this endpoint was provisioned.
4699
+ required:
4700
+ - token
4701
+ - company
4702
+ - provider
4703
+ - secret
4704
+ - active
4705
+ additionalProperties: false
4706
+ x-internal: true
4707
+ description: 'PaymentWebhookEndpoint (GH#41). Collection: payment_webhook_endpoints/{token}.
4708
+ Top-level, not tenant-scoped. Provisioned by superadmin; used by the backend
4709
+ to verify inbound payment webhooks from Wave and Jeko. The delivery log collection
4710
+ (raw webhook payloads + verification outcomes) is tracked in GH#41 — shape
4711
+ TBD.'
4712
+ PaymentWebhookEndpointCreate:
4713
+ allOf:
4714
+ - $ref: '#/components/schemas/PaymentWebhookEndpoint'
4715
+ description: Write payload for creating a new PaymentWebhookEndpoint document.
4716
+ Fields marked `readOnly` are server-set and must not be included. Fields marked
4717
+ `x-immutable` may be set once at creation.
4718
+ required:
4719
+ - token
4720
+ - company
4721
+ - provider
4722
+ - active
4723
+ PaymentWebhookEndpointUpdate:
4724
+ allOf:
4725
+ - $ref: '#/components/schemas/PaymentWebhookEndpoint'
4726
+ description: Write payload for partial update (PATCH) of a PaymentWebhookEndpoint
4727
+ document. All fields optional. Fields marked `readOnly` or `x-immutable` must
4728
+ not be sent.
3834
4729
  Sale:
3835
4730
  type: object
3836
4731
  properties:
@@ -3895,7 +4790,9 @@ components:
3895
4790
  - 'null'
3896
4791
  purchaseDate:
3897
4792
  $ref: '#/components/schemas/FirestoreTimestamp'
3898
- description: Firestore Timestamp serialized representation
4793
+ description: 'Firestore Timestamp Admin SDK form: { _seconds, _nanoseconds
4794
+ }. See types/firestore.ts for REST API v1 and client SDK serialization
4795
+ notes (#10).'
3899
4796
  createdAt:
3900
4797
  anyOf:
3901
4798
  - $ref: '#/components/schemas/FirestoreTimestamp'
@@ -4631,6 +5528,86 @@ components:
4631
5528
  description: Write payload for partial update (PATCH) of a Ticket document.
4632
5529
  All fields optional. Fields marked `readOnly` or `x-immutable` must not be
4633
5530
  sent.
5531
+ User:
5532
+ type: object
5533
+ properties:
5534
+ id:
5535
+ readOnly: true
5536
+ description: (Read-only) Firebase Auth UID. Matches the Firestore document
5537
+ ID.
5538
+ type:
5539
+ - string
5540
+ - 'null'
5541
+ displayName:
5542
+ description: User display name from Firebase Auth or manually set.
5543
+ type:
5544
+ - string
5545
+ - 'null'
5546
+ email:
5547
+ description: Email address. Present for email/Google auth users.
5548
+ type:
5549
+ - string
5550
+ - 'null'
5551
+ phoneE164:
5552
+ x-note: 'E.164 format with leading + (e.g. +2250777471485). Canonical phone
5553
+ identity field (decision #27). Distinct from wa_id (Meta conversation
5554
+ key). Server must normalize at write time: 8-digit CI local → prepend
5555
+ +225.'
5556
+ x-when: Set when the user authenticated via phone (WhatsApp OTP) or when
5557
+ a staff member's phone is known. Required for WhatsApp OTP lookup — add
5558
+ a Firestore composite index on phoneE164.
5559
+ description: E.164-normalized phone number (e.g. +2250777471485). Canonical
5560
+ phone identity field; used for WhatsApp OTP lookup via indexed query.
5561
+ type:
5562
+ - string
5563
+ - 'null'
5564
+ companyId:
5565
+ x-immutable: true
5566
+ description: (Immutable) FK → Company document ID. Present for staff accounts
5567
+ scoped to a company.
5568
+ type:
5569
+ - string
5570
+ - 'null'
5571
+ role:
5572
+ description: Staff role within the company (e.g. admin, manager, staff).
5573
+ Application-defined.
5574
+ type:
5575
+ - string
5576
+ - 'null'
5577
+ isActive:
5578
+ description: Whether this user account is active. Inactive accounts are
5579
+ denied access.
5580
+ type:
5581
+ - boolean
5582
+ - 'null'
5583
+ createdAt:
5584
+ anyOf:
5585
+ - $ref: '#/components/schemas/FirestoreTimestamp'
5586
+ - type: 'null'
5587
+ readOnly: true
5588
+ description: (Read-only) When the user document was created.
5589
+ updatedAt:
5590
+ anyOf:
5591
+ - $ref: '#/components/schemas/FirestoreTimestamp'
5592
+ - type: 'null'
5593
+ readOnly: true
5594
+ description: (Read-only) When the user document was last updated.
5595
+ additionalProperties: false
5596
+ description: 'User / staff account. Collection: users/{uid}. Document ID is
5597
+ always the Firebase Auth UID (decision #27). phoneE164 is the canonical E.164
5598
+ phone field for OTP lookup. Legacy phone-keyed documents must be migrated
5599
+ to uid-keyed docs with phoneE164 set.'
5600
+ UserCreate:
5601
+ allOf:
5602
+ - $ref: '#/components/schemas/User'
5603
+ description: Write payload for creating a new User document. Fields marked `readOnly`
5604
+ are server-set and must not be included. Fields marked `x-immutable` may be
5605
+ set once at creation.
5606
+ UserUpdate:
5607
+ allOf:
5608
+ - $ref: '#/components/schemas/User'
5609
+ description: Write payload for partial update (PATCH) of a User document. All
5610
+ fields optional. Fields marked `readOnly` or `x-immutable` must not be sent.
4634
5611
  WhatsappInboundMessage:
4635
5612
  type: object
4636
5613
  properties:
@@ -4647,6 +5624,12 @@ components:
4647
5624
  received the message.
4648
5625
  waba:
4649
5626
  $ref: '#/components/schemas/WabaLabel'
5627
+ x-note: 'cmz = "IngenX - Chez Miss Zahoui", phone number ID 446424085225188,
5628
+ used for chez_miss_zahoui_* companies. val = "IngenX - Valets", phone
5629
+ number ID 425582173979125, used for all other companies. Templates are
5630
+ approved at WABA level — if cmz and val share a WABA ID, all templates
5631
+ are available to both. Routing logic: functions/src/orders/order.ts#isSender2Company.
5632
+ See messaging/whatsapp-platform-constraints (#50).'
4650
5633
  description: (Immutable) Human-readable WABA label (cmz | val). See WabaLabel
4651
5634
  enum.
4652
5635
  x-immutable: true
@@ -4849,10 +5832,26 @@ components:
4849
5832
  to:
4850
5833
  type: string
4851
5834
  x-immutable: true
4852
- description: (Immutable) Recipient phone number in E.164 format. Must match
4853
- the inbound `from` for the same conversation.
5835
+ x-note: 'This is the Meta wa_id (digits only, no leading +), NOT a literal
5836
+ E.164 string. Meta normalizes phone numbers in non-obvious ways (e.g.
5837
+ +2250777471485 → wa_id 22577471485, dropping the trunk zero). The only
5838
+ reliable source is Meta''s contacts[].wa_id from the send response — do
5839
+ not derive from E.164 by stripping + alone. The dashboard conversational
5840
+ sends and the whatsapp-server OTP path both use the wa_id form. Confirmed
5841
+ in prod: ING-368.'
5842
+ x-when: Set at enqueue time to the recipient's Meta wa_id. Use inbound WhatsappInboundMessage.from
5843
+ (also wa_id) as the join key for the conversation thread.
5844
+ description: (Immutable) Recipient Meta wa_id — digits only, no leading
5845
+ + (e.g. 22577471485). Equal to the inbound `from` for the same conversation.
5846
+ NOT literal E.164; see x-note (#54).
4854
5847
  waba:
4855
5848
  $ref: '#/components/schemas/WabaLabel'
5849
+ x-note: 'cmz = "IngenX - Chez Miss Zahoui", phone number ID 446424085225188,
5850
+ used for chez_miss_zahoui_* companies. val = "IngenX - Valets", phone
5851
+ number ID 425582173979125, used for all other companies. Templates are
5852
+ approved at WABA level — if cmz and val share a WABA ID, all templates
5853
+ are available to both. Routing logic: functions/src/orders/order.ts#isSender2Company.
5854
+ See messaging/whatsapp-platform-constraints (#50).'
4856
5855
  description: (Immutable) WABA that will send the message. Mirrors inbound
4857
5856
  `waba`.
4858
5857
  x-immutable: true
@@ -4938,6 +5937,9 @@ components:
4938
5937
  after (sent → delivered → read, or failed).
4939
5938
  wamid:
4940
5939
  readOnly: true
5940
+ x-note: Useful for audit and dedup. Does NOT enable reply-threading for
5941
+ template messages — Meta silently ignores context.message_id on templates.
5942
+ Threading only works for kind=text within the 24-hour service window (#50).
4941
5943
  description: (Read-only) WhatsApp message ID returned by Meta once the backend
4942
5944
  sends. Absent while queued or on failure.
4943
5945
  type:
@@ -5017,6 +6019,12 @@ components:
5017
6019
  - 'null'
5018
6020
  waba:
5019
6021
  $ref: '#/components/schemas/WabaLabel'
6022
+ x-note: 'cmz = "IngenX - Chez Miss Zahoui", phone number ID 446424085225188,
6023
+ used for chez_miss_zahoui_* companies. val = "IngenX - Valets", phone
6024
+ number ID 425582173979125, used for all other companies. Templates are
6025
+ approved at WABA level — if cmz and val share a WABA ID, all templates
6026
+ are available to both. Routing logic: functions/src/orders/order.ts#isSender2Company.
6027
+ See messaging/whatsapp-platform-constraints (#50).'
5020
6028
  description: (Read-only) WABA label this template is approved on. Templates
5021
6029
  are WABA-scoped. Mirrors WhatsappOutboundMessage.waba.
5022
6030
  readOnly: true