@ingenx-io/valets-schema-mcp-server 0.1.1 → 0.1.3

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 (43) hide show
  1. package/data/docs/collections/firestore-paths.md +49 -0
  2. package/data/docs/decisions/migrations.md +56 -0
  3. package/data/docs/decisions/summary.md +78 -0
  4. package/data/docs/enums/booking-status.md +26 -0
  5. package/data/docs/enums/customer-payment-status.md +26 -0
  6. package/data/docs/enums/customer-payment-target-type.md +23 -0
  7. package/data/docs/enums/delivery-type.md +23 -0
  8. package/data/docs/enums/event-status.md +30 -0
  9. package/data/docs/enums/fulfillment-status.md +32 -0
  10. package/data/docs/enums/loyalty-transaction-type.md +32 -0
  11. package/data/docs/enums/order-status.md +65 -0
  12. package/data/docs/enums/payment-method.md +36 -0
  13. package/data/docs/enums/payment-proof-status.md +23 -0
  14. package/data/docs/enums/payment-status.md +34 -0
  15. package/data/docs/enums/return-status.md +32 -0
  16. package/data/docs/enums/session-status.md +32 -0
  17. package/data/docs/enums/ticket-status.md +29 -0
  18. package/data/docs/index.md +102 -0
  19. package/data/docs/models/booking-version.md +295 -0
  20. package/data/docs/models/booking.md +1754 -0
  21. package/data/docs/models/customer-payment-allocation.md +336 -0
  22. package/data/docs/models/customer-payment.md +392 -0
  23. package/data/docs/models/customer.md +475 -0
  24. package/data/docs/models/event.md +386 -0
  25. package/data/docs/models/loyalty-config.md +317 -0
  26. package/data/docs/models/loyalty-reward.md +236 -0
  27. package/data/docs/models/loyalty-status.md +328 -0
  28. package/data/docs/models/loyalty-transaction.md +326 -0
  29. package/data/docs/models/metrics-current.md +532 -0
  30. package/data/docs/models/metrics-daily.md +548 -0
  31. package/data/docs/models/metrics-monthly.md +548 -0
  32. package/data/docs/models/order-item.md +361 -0
  33. package/data/docs/models/order.md +1637 -0
  34. package/data/docs/models/payment-summary.md +123 -0
  35. package/data/docs/models/sale.md +540 -0
  36. package/data/docs/models/ticket.md +405 -0
  37. package/data/docs/triggers/event-ticket-triggers.md +204 -0
  38. package/data/docs/triggers/loyalty-automation.md +123 -0
  39. package/data/static/decisions.json +966 -0
  40. package/data/static/llms.txt +1056 -0
  41. package/data/static/openapi.yaml +3090 -0
  42. package/data/static/schemas.json +4055 -0
  43. package/package.json +1 -1
@@ -0,0 +1,4055 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "description": "@valets/schema \u2014 consolidated schema bundle",
4
+ "generated": "2026-04-06T19:27:48.749605+00:00",
5
+ "schemas": {
6
+ "booking": {
7
+ "type": "object",
8
+ "properties": {
9
+ "id": {
10
+ "type": "string",
11
+ "readOnly": true,
12
+ "description": "(Read-only) Firestore document ID."
13
+ },
14
+ "uid": {
15
+ "type": "string",
16
+ "readOnly": true,
17
+ "description": "(Read-only) Entity UID. Often mirrors id."
18
+ },
19
+ "companyId": {
20
+ "x-immutable": true,
21
+ "description": "(Immutable) FK \u2192 Company document ID. Note: optional in current schema \u2014 should be required (see ID consistency audit).",
22
+ "type": [
23
+ "string",
24
+ "null"
25
+ ]
26
+ },
27
+ "status": {
28
+ "$ref": "#/definitions/booking-status",
29
+ "description": "Booking lifecycle status. COMPLETED_MIXED = some sessions completed, others cancelled/no-show."
30
+ },
31
+ "totalAmount": {
32
+ "type": "number",
33
+ "x-note": "Booking uses `totalAmount`; Order uses `amount` for the equivalent field (D05 locked `amount` as canonical for orders). Pending cross-model alignment.",
34
+ "x-see": {
35
+ "decisions": [
36
+ "D05"
37
+ ]
38
+ }
39
+ },
40
+ "bookingDates": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "object",
44
+ "properties": {
45
+ "date": {
46
+ "type": "string"
47
+ },
48
+ "selectedTimeSlots": {
49
+ "type": "array",
50
+ "items": {
51
+ "type": "object",
52
+ "properties": {
53
+ "id": {
54
+ "type": "number"
55
+ },
56
+ "name": {
57
+ "type": "string"
58
+ },
59
+ "timeRange": {
60
+ "type": "string"
61
+ },
62
+ "price": {
63
+ "type": "number",
64
+ "description": "Canonical price field (D11). Replaces legacy standardPrice/fullPrice (MIG-08)."
65
+ },
66
+ "notMainPurposePrice": {
67
+ "type": "number",
68
+ "description": "Price when slot is not the main purpose (D11). Canonical name; replaces legacy aliases (MIG-08)."
69
+ },
70
+ "hours": {
71
+ "type": "string"
72
+ },
73
+ "startTime": {
74
+ "type": "string"
75
+ },
76
+ "endTime": {
77
+ "type": "string"
78
+ },
79
+ "isNightSlot": {
80
+ "type": "boolean"
81
+ },
82
+ "isNotMainPurpose": {
83
+ "type": "boolean"
84
+ },
85
+ "isNotMainPurposeReason": {
86
+ "type": "string"
87
+ },
88
+ "isNotMainPurposeOtherReason": {
89
+ "type": "string"
90
+ },
91
+ "_deleted": {
92
+ "description": "Soft-delete flag. Dashboard writes; Mobile filters (IG-6).",
93
+ "type": "boolean"
94
+ },
95
+ "_deletedAt": {
96
+ "$ref": "#/definitions/firestore-timestamp",
97
+ "description": "Firestore Timestamp serialized representation"
98
+ },
99
+ "_deletedBy": {
100
+ "description": "FK \u2192 User/staff UID who soft-deleted this item.",
101
+ "type": "string"
102
+ },
103
+ "_lastModifiedAt": {
104
+ "$ref": "#/definitions/firestore-timestamp",
105
+ "description": "Firestore Timestamp serialized representation"
106
+ },
107
+ "_lastModifiedBy": {
108
+ "description": "FK \u2192 User/staff UID who last modified this item.",
109
+ "type": "string"
110
+ },
111
+ "_version": {
112
+ "description": "Monotonic version counter for conflict detection.",
113
+ "type": "number"
114
+ }
115
+ },
116
+ "required": [
117
+ "id",
118
+ "name",
119
+ "timeRange",
120
+ "price",
121
+ "notMainPurposePrice",
122
+ "hours",
123
+ "startTime",
124
+ "endTime"
125
+ ],
126
+ "additionalProperties": false,
127
+ "description": "Selected time slot within a BookingDate. Carries CRDT metadata for conflict-free editing."
128
+ }
129
+ },
130
+ "slotKitTypes": {
131
+ "type": "object",
132
+ "propertyNames": {
133
+ "type": "string"
134
+ },
135
+ "additionalProperties": {
136
+ "type": "string"
137
+ },
138
+ "description": "Kit type per slot (IG-11). Firebase parity gap \u2014 exists in Firestore but Firebase type lacked it."
139
+ },
140
+ "slotAddOns": {
141
+ "type": "object",
142
+ "propertyNames": {
143
+ "type": "string"
144
+ },
145
+ "additionalProperties": {
146
+ "type": "array",
147
+ "items": {
148
+ "type": "string"
149
+ }
150
+ },
151
+ "description": "Add-on IDs per slot. Numeric IDs to be normalized to descriptive names (MIG-07/D10)."
152
+ },
153
+ "extraHours": {
154
+ "type": "number"
155
+ },
156
+ "status": {
157
+ "$ref": "#/definitions/session-status",
158
+ "description": "Per-date session status (D19). Dashboard is sole writer; Mobile is read-only."
159
+ },
160
+ "statusUpdatedAt": {
161
+ "$ref": "#/definitions/firestore-timestamp",
162
+ "readOnly": true,
163
+ "description": "(Read-only) Timestamp of last status change."
164
+ },
165
+ "statusUpdatedBy": {
166
+ "description": "FK \u2192 User/staff UID who updated this date status.",
167
+ "type": "string"
168
+ },
169
+ "slotStatuses": {
170
+ "description": "Per-slot session statuses (D19). Keyed by slot ID string.",
171
+ "type": "object",
172
+ "propertyNames": {
173
+ "type": "string"
174
+ },
175
+ "additionalProperties": {
176
+ "$ref": "#/definitions/session-status",
177
+ "description": "Per-date/per-slot booking session status (D19). Dashboard is sole writer; Mobile is read-only."
178
+ }
179
+ },
180
+ "slotStatusUpdatedAt": {
181
+ "type": "object",
182
+ "propertyNames": {
183
+ "type": "string"
184
+ },
185
+ "additionalProperties": {
186
+ "$ref": "#/definitions/firestore-timestamp",
187
+ "description": "Firestore Timestamp serialized representation"
188
+ }
189
+ },
190
+ "slotStatusUpdatedBy": {
191
+ "type": "object",
192
+ "propertyNames": {
193
+ "type": "string"
194
+ },
195
+ "additionalProperties": {
196
+ "type": "string"
197
+ }
198
+ },
199
+ "_deleted": {
200
+ "description": "Soft-delete flag. Dashboard writes; Mobile filters (IG-6).",
201
+ "type": "boolean"
202
+ },
203
+ "_deletedAt": {
204
+ "$ref": "#/definitions/firestore-timestamp",
205
+ "description": "Firestore Timestamp serialized representation"
206
+ },
207
+ "_deletedBy": {
208
+ "description": "FK \u2192 User/staff UID who soft-deleted this item.",
209
+ "type": "string"
210
+ },
211
+ "_lastModifiedAt": {
212
+ "$ref": "#/definitions/firestore-timestamp",
213
+ "description": "Firestore Timestamp serialized representation"
214
+ },
215
+ "_lastModifiedBy": {
216
+ "description": "FK \u2192 User/staff UID who last modified this item.",
217
+ "type": "string"
218
+ },
219
+ "_version": {
220
+ "description": "Monotonic version counter for conflict detection.",
221
+ "type": "number"
222
+ }
223
+ },
224
+ "required": [
225
+ "date",
226
+ "selectedTimeSlots",
227
+ "slotKitTypes",
228
+ "slotAddOns",
229
+ "extraHours"
230
+ ],
231
+ "additionalProperties": false,
232
+ "description": "Single date entry within a Booking. Contains time slots, session statuses (D19), and CRDT metadata."
233
+ }
234
+ },
235
+ "customerId": {
236
+ "description": "FK \u2192 Customer.id (Firestore doc ID). Links booking to customer record.",
237
+ "type": [
238
+ "string",
239
+ "null"
240
+ ]
241
+ },
242
+ "client": {
243
+ "denormalized": true,
244
+ "description": "(Denormalized) Embedded client snapshot from Customer at write time.",
245
+ "type": [
246
+ "object",
247
+ "null"
248
+ ],
249
+ "properties": {
250
+ "firstName": {
251
+ "type": "string"
252
+ },
253
+ "lastName": {
254
+ "type": "string"
255
+ },
256
+ "email": {
257
+ "type": "string"
258
+ },
259
+ "phone": {
260
+ "type": "string"
261
+ }
262
+ },
263
+ "additionalProperties": false
264
+ },
265
+ "customerName": {
266
+ "denormalized": true,
267
+ "description": "(Denormalized) From Customer.name at write time. Canonical field per D24.",
268
+ "type": [
269
+ "string",
270
+ "null"
271
+ ]
272
+ },
273
+ "customerEmail": {
274
+ "denormalized": true,
275
+ "description": "(Denormalized) From Customer.email at write time. Canonical field per D24.",
276
+ "type": [
277
+ "string",
278
+ "null"
279
+ ]
280
+ },
281
+ "customerPhone": {
282
+ "denormalized": true,
283
+ "description": "(Denormalized) From Customer.phone at write time. Canonical field per D24.",
284
+ "type": [
285
+ "string",
286
+ "null"
287
+ ]
288
+ },
289
+ "clientName": {
290
+ "denormalized": true,
291
+ "deprecated": true,
292
+ "x-replaced-by": "customerName",
293
+ "description": "(Denormalized) Legacy \u2014 use `customerName`. D24 standardized to customer* prefix.",
294
+ "type": [
295
+ "string",
296
+ "null"
297
+ ]
298
+ },
299
+ "clientReference": {
300
+ "type": "string"
301
+ },
302
+ "clientEmail": {
303
+ "denormalized": true,
304
+ "deprecated": true,
305
+ "x-replaced-by": "customerEmail",
306
+ "description": "(Denormalized) Legacy \u2014 use `customerEmail`. D24 standardized to customer* prefix.",
307
+ "type": [
308
+ "string",
309
+ "null"
310
+ ]
311
+ },
312
+ "clientPhone": {
313
+ "denormalized": true,
314
+ "deprecated": true,
315
+ "x-replaced-by": "customerPhone",
316
+ "description": "(Denormalized) Legacy \u2014 use `customerPhone`. D24 standardized to customer* prefix.",
317
+ "type": [
318
+ "string",
319
+ "null"
320
+ ]
321
+ },
322
+ "service": {
323
+ "type": [
324
+ "object",
325
+ "null"
326
+ ],
327
+ "properties": {
328
+ "name": {
329
+ "type": "string"
330
+ }
331
+ },
332
+ "additionalProperties": false
333
+ },
334
+ "serviceId": {
335
+ "description": "FK \u2192 Service document ID.",
336
+ "type": [
337
+ "string",
338
+ "null"
339
+ ]
340
+ },
341
+ "serviceName": {
342
+ "denormalized": true,
343
+ "description": "(Denormalized) From Service.name at write time.",
344
+ "type": [
345
+ "string",
346
+ "null"
347
+ ]
348
+ },
349
+ "timeSlot": {
350
+ "type": [
351
+ "string",
352
+ "null"
353
+ ]
354
+ },
355
+ "date": {
356
+ "type": [
357
+ "string",
358
+ "null"
359
+ ]
360
+ },
361
+ "startDate": {
362
+ "type": [
363
+ "string",
364
+ "null"
365
+ ]
366
+ },
367
+ "endDate": {
368
+ "type": [
369
+ "string",
370
+ "null"
371
+ ]
372
+ },
373
+ "startTime": {
374
+ "type": [
375
+ "string",
376
+ "null"
377
+ ]
378
+ },
379
+ "endTime": {
380
+ "type": [
381
+ "string",
382
+ "null"
383
+ ]
384
+ },
385
+ "notes": {
386
+ "type": [
387
+ "array",
388
+ "null"
389
+ ],
390
+ "items": {
391
+ "type": "object",
392
+ "properties": {
393
+ "id": {
394
+ "type": "string",
395
+ "readOnly": true,
396
+ "description": "(Read-only) Note ID."
397
+ },
398
+ "text": {
399
+ "type": "string"
400
+ },
401
+ "createdAt": {
402
+ "$ref": "#/definitions/firestore-timestamp",
403
+ "description": "(Read-only) Firestore Timestamp serialized representation.",
404
+ "readOnly": true
405
+ },
406
+ "createdBy": {
407
+ "x-immutable": true,
408
+ "description": "(Immutable) FK \u2192 User/staff UID who created this note.",
409
+ "type": "string"
410
+ },
411
+ "createdByName": {
412
+ "denormalized": true,
413
+ "description": "(Denormalized) From User display name at write time.",
414
+ "type": "string"
415
+ },
416
+ "_deleted": {
417
+ "description": "Soft-delete flag. Dashboard writes; Mobile filters (IG-6).",
418
+ "type": "boolean"
419
+ },
420
+ "_deletedAt": {
421
+ "$ref": "#/definitions/firestore-timestamp",
422
+ "description": "Firestore Timestamp serialized representation"
423
+ },
424
+ "_deletedBy": {
425
+ "description": "FK \u2192 User/staff UID who soft-deleted this item.",
426
+ "type": "string"
427
+ },
428
+ "_lastModifiedAt": {
429
+ "$ref": "#/definitions/firestore-timestamp",
430
+ "description": "Firestore Timestamp serialized representation"
431
+ },
432
+ "_lastModifiedBy": {
433
+ "description": "FK \u2192 User/staff UID who last modified this item.",
434
+ "type": "string"
435
+ },
436
+ "_version": {
437
+ "description": "Monotonic version counter for conflict detection.",
438
+ "type": "number"
439
+ }
440
+ },
441
+ "required": [
442
+ "id",
443
+ "text",
444
+ "createdAt"
445
+ ],
446
+ "additionalProperties": false,
447
+ "description": "Timestamped note attached to a Booking. Carries CRDT metadata."
448
+ }
449
+ },
450
+ "technicalInfo": {
451
+ "type": [
452
+ "string",
453
+ "null"
454
+ ]
455
+ },
456
+ "paymentStatus": {
457
+ "anyOf": [
458
+ {
459
+ "$ref": "#/definitions/payment-status"
460
+ },
461
+ {
462
+ "type": "null"
463
+ }
464
+ ],
465
+ "description": "Payment lifecycle status (D01 amended). Used by Order, Sale/Purchase, Booking."
466
+ },
467
+ "amountPaid": {
468
+ "type": [
469
+ "number",
470
+ "null"
471
+ ]
472
+ },
473
+ "amountRefunded": {
474
+ "type": [
475
+ "number",
476
+ "null"
477
+ ]
478
+ },
479
+ "amountPending": {
480
+ "type": [
481
+ "number",
482
+ "null"
483
+ ]
484
+ },
485
+ "purchaseId": {
486
+ "description": "FK \u2192 Sale.id. Link to associated Sale document.",
487
+ "type": [
488
+ "string",
489
+ "null"
490
+ ]
491
+ },
492
+ "paymentStatusChangeReason": {
493
+ "type": [
494
+ "string",
495
+ "null"
496
+ ]
497
+ },
498
+ "paymentStatusChangedBy": {
499
+ "description": "FK \u2192 User/staff UID who changed payment status.",
500
+ "type": [
501
+ "string",
502
+ "null"
503
+ ]
504
+ },
505
+ "paymentStatusChangedAt": {
506
+ "anyOf": [
507
+ {
508
+ "$ref": "#/definitions/firestore-timestamp"
509
+ },
510
+ {
511
+ "type": "null"
512
+ }
513
+ ],
514
+ "description": "Firestore Timestamp serialized representation"
515
+ },
516
+ "paymentProofUrl": {
517
+ "description": "URL to uploaded payment proof image/document.",
518
+ "type": [
519
+ "string",
520
+ "null"
521
+ ]
522
+ },
523
+ "paymentProofStatus": {
524
+ "anyOf": [
525
+ {
526
+ "$ref": "#/definitions/payment-proof-status"
527
+ },
528
+ {
529
+ "type": "null"
530
+ }
531
+ ],
532
+ "description": "Payment proof review status. Used by Order and Booking payment proof workflows."
533
+ },
534
+ "paymentProofAddedAt": {
535
+ "anyOf": [
536
+ {
537
+ "$ref": "#/definitions/firestore-timestamp"
538
+ },
539
+ {
540
+ "type": "null"
541
+ }
542
+ ],
543
+ "description": "Firestore Timestamp serialized representation"
544
+ },
545
+ "paymentProofAddedBy": {
546
+ "description": "FK \u2192 User/staff UID who uploaded the payment proof.",
547
+ "type": [
548
+ "string",
549
+ "null"
550
+ ]
551
+ },
552
+ "paymentProofReviewedBy": {
553
+ "description": "FK \u2192 User/staff UID who reviewed the payment proof.",
554
+ "type": [
555
+ "string",
556
+ "null"
557
+ ]
558
+ },
559
+ "paymentProofReviewedAt": {
560
+ "anyOf": [
561
+ {
562
+ "$ref": "#/definitions/firestore-timestamp"
563
+ },
564
+ {
565
+ "type": "null"
566
+ }
567
+ ],
568
+ "description": "Firestore Timestamp serialized representation"
569
+ },
570
+ "paymentProofRejectionReason": {
571
+ "type": [
572
+ "string",
573
+ "null"
574
+ ]
575
+ },
576
+ "cancellationRequestedById": {
577
+ "description": "FK \u2192 User/staff UID who requested cancellation.",
578
+ "type": [
579
+ "string",
580
+ "null"
581
+ ]
582
+ },
583
+ "cancellationRequestReason": {
584
+ "type": [
585
+ "string",
586
+ "null"
587
+ ]
588
+ },
589
+ "cancellationProcessedById": {
590
+ "description": "FK \u2192 User/staff UID who processed the cancellation.",
591
+ "type": [
592
+ "string",
593
+ "null"
594
+ ]
595
+ },
596
+ "cancellationProcessedAt": {
597
+ "anyOf": [
598
+ {
599
+ "$ref": "#/definitions/firestore-timestamp"
600
+ },
601
+ {
602
+ "type": "null"
603
+ }
604
+ ],
605
+ "description": "Firestore Timestamp serialized representation"
606
+ },
607
+ "cancelledByRole": {
608
+ "type": [
609
+ "string",
610
+ "null"
611
+ ]
612
+ },
613
+ "cancellationReason": {
614
+ "type": [
615
+ "string",
616
+ "null"
617
+ ]
618
+ },
619
+ "createdAt": {
620
+ "$ref": "#/definitions/firestore-timestamp",
621
+ "description": "(Read-only) Server-generated creation timestamp.",
622
+ "readOnly": true
623
+ },
624
+ "updatedAt": {
625
+ "anyOf": [
626
+ {
627
+ "$ref": "#/definitions/firestore-timestamp"
628
+ },
629
+ {
630
+ "type": "null"
631
+ }
632
+ ],
633
+ "readOnly": true,
634
+ "description": "(Read-only) Server-generated update timestamp."
635
+ },
636
+ "createdBy": {
637
+ "x-immutable": true,
638
+ "description": "(Immutable) FK \u2192 User/staff UID who created this booking.",
639
+ "type": [
640
+ "string",
641
+ "null"
642
+ ]
643
+ },
644
+ "updatedBy": {
645
+ "description": "FK \u2192 User/staff UID who last updated this booking.",
646
+ "type": [
647
+ "string",
648
+ "null"
649
+ ]
650
+ },
651
+ "createdFromBackend": {
652
+ "description": "When true, suppresses Firebase notification triggers (D20/IG-8).",
653
+ "type": [
654
+ "boolean",
655
+ "null"
656
+ ]
657
+ }
658
+ },
659
+ "required": [
660
+ "id",
661
+ "uid",
662
+ "status",
663
+ "totalAmount",
664
+ "bookingDates",
665
+ "clientReference",
666
+ "createdAt"
667
+ ],
668
+ "additionalProperties": false,
669
+ "description": "Booking model. Collection: companies/{companyId}/bookings/{bookingId}. All platforms. Uses BookingStatus (6 values) and SessionStatus (D19) for per-date/slot tracking.",
670
+ "example": {
671
+ "id": "bk_abc123def456",
672
+ "uid": "user_u8x92kqm",
673
+ "companyId": null,
674
+ "status": "status",
675
+ "totalAmount": 45000,
676
+ "bookingDates": [
677
+ {
678
+ "date": "2026-02-15",
679
+ "selectedTimeSlots": [
680
+ {
681
+ "id": 0,
682
+ "name": "Morning Full Session",
683
+ "timeRange": "09:00 - 11:00",
684
+ "price": 15000,
685
+ "notMainPurposePrice": 10000,
686
+ "hours": "2h",
687
+ "startTime": "09:00",
688
+ "endTime": "11:00"
689
+ }
690
+ ],
691
+ "slotKitTypes": {},
692
+ "slotAddOns": {},
693
+ "extraHours": 0
694
+ }
695
+ ],
696
+ "customerId": null,
697
+ "client": null,
698
+ "customerName": null,
699
+ "customerEmail": null,
700
+ "customerPhone": null,
701
+ "clientName": null,
702
+ "clientReference": "CLI-2026-0017",
703
+ "clientEmail": null,
704
+ "clientPhone": null,
705
+ "service": null,
706
+ "serviceId": null,
707
+ "serviceName": null,
708
+ "timeSlot": null,
709
+ "date": null,
710
+ "startDate": null,
711
+ "endDate": null,
712
+ "startTime": null,
713
+ "endTime": null,
714
+ "notes": null,
715
+ "technicalInfo": null,
716
+ "paymentStatus": "paymentStatus",
717
+ "amountPaid": null,
718
+ "amountRefunded": null,
719
+ "amountPending": null,
720
+ "purchaseId": null,
721
+ "paymentStatusChangeReason": null,
722
+ "paymentStatusChangedBy": null,
723
+ "paymentStatusChangedAt": "paymentStatusChangedAt",
724
+ "paymentProofUrl": null,
725
+ "paymentProofStatus": "paymentProofStatus",
726
+ "paymentProofAddedAt": "paymentProofAddedAt",
727
+ "paymentProofAddedBy": null,
728
+ "paymentProofReviewedBy": null,
729
+ "paymentProofReviewedAt": "paymentProofReviewedAt",
730
+ "paymentProofRejectionReason": null,
731
+ "cancellationRequestedById": null,
732
+ "cancellationRequestReason": null,
733
+ "cancellationProcessedById": null,
734
+ "cancellationProcessedAt": "cancellationProcessedAt",
735
+ "cancelledByRole": null,
736
+ "cancellationReason": null,
737
+ "createdAt": "createdAt",
738
+ "updatedAt": "updatedAt",
739
+ "createdBy": null,
740
+ "updatedBy": null,
741
+ "createdFromBackend": null
742
+ }
743
+ },
744
+ "booking-status": {
745
+ "type": "string",
746
+ "enum": [
747
+ "PENDING",
748
+ "CONFIRMED",
749
+ "COMPLETED",
750
+ "CANCELLED",
751
+ "CANCELLATION_REQUESTED",
752
+ "COMPLETED_MIXED"
753
+ ],
754
+ "description": "Booking lifecycle status. COMPLETED_MIXED = some sessions completed, others cancelled/no-show."
755
+ },
756
+ "booking-version": {
757
+ "type": "object",
758
+ "properties": {
759
+ "id": {
760
+ "type": "string",
761
+ "readOnly": true,
762
+ "description": "(Read-only) Firestore document ID. Server-generated version record."
763
+ },
764
+ "bookingId": {
765
+ "type": "string",
766
+ "x-immutable": true,
767
+ "description": "(Immutable) FK \u2192 Booking.id. Parent booking this version records."
768
+ },
769
+ "companyId": {
770
+ "type": "string",
771
+ "x-immutable": true,
772
+ "description": "(Immutable) FK \u2192 Company document ID. Denormalized for direct queries."
773
+ },
774
+ "changeType": {
775
+ "type": "string",
776
+ "enum": [
777
+ "CREATE",
778
+ "UPDATE",
779
+ "DELETE"
780
+ ],
781
+ "x-immutable": true,
782
+ "x-note": "DELETE versions are written when a booking is soft-deleted (CRDT _deleted flag). Hard deletes bypass this trail.",
783
+ "description": "(Immutable) What caused this version record: CREATE, UPDATE, or DELETE."
784
+ },
785
+ "changedAt": {
786
+ "$ref": "#/definitions/firestore-timestamp",
787
+ "description": "(Read-only) Timestamp of the write that created this version. Server-set.",
788
+ "readOnly": true
789
+ },
790
+ "changedBy": {
791
+ "x-immutable": true,
792
+ "x-note": "Null when the write originated from a Firebase trigger, CLI script, or unknown source.",
793
+ "description": "(Immutable) FK \u2192 User/staff UID who made the change. Null for server-originated writes.",
794
+ "type": [
795
+ "string",
796
+ "null"
797
+ ]
798
+ },
799
+ "changedByRole": {
800
+ "x-immutable": true,
801
+ "x-note": "Populated by the Firebase trigger from the write context. Helps distinguish client vs. server writes.",
802
+ "description": "(Immutable) Which platform/context made the write: DASHBOARD, MOBILE, FIREBASE (trigger), CLI (migration script), or UNKNOWN. SCREAMING_SNAKE per D04.",
803
+ "type": [
804
+ "string",
805
+ "null"
806
+ ],
807
+ "enum": [
808
+ "DASHBOARD",
809
+ "MOBILE",
810
+ "FIREBASE",
811
+ "CLI",
812
+ "UNKNOWN"
813
+ ]
814
+ },
815
+ "fieldsChanged": {
816
+ "x-immutable": true,
817
+ "readOnly": true,
818
+ "x-note": "Dot-notation paths for nested fields (e.g. \"bookingDates.0.status\"). Populated by diff in Firebase trigger. Null for CREATE versions.",
819
+ "description": "(Immutable, Read-only) List of field paths that changed in this write. Populated by the Firebase trigger diff. Null for CREATE records.",
820
+ "type": [
821
+ "array",
822
+ "null"
823
+ ],
824
+ "items": {
825
+ "type": "string"
826
+ }
827
+ },
828
+ "snapshot": {
829
+ "type": "object",
830
+ "propertyNames": {
831
+ "type": "string"
832
+ },
833
+ "additionalProperties": {},
834
+ "x-immutable": true,
835
+ "readOnly": true,
836
+ "x-note": "Full Booking document at the time of write. Typed as a string-keyed record to avoid circular schema reference. Consumers cast to Booking at runtime.",
837
+ "x-see": {
838
+ "decisions": [
839
+ "D18"
840
+ ]
841
+ },
842
+ "description": "(Immutable, Read-only) Full snapshot of the Booking document immediately after the write. Untyped to avoid circular schema dependency \u2014 cast to Booking at runtime."
843
+ }
844
+ },
845
+ "required": [
846
+ "id",
847
+ "bookingId",
848
+ "companyId",
849
+ "changeType",
850
+ "changedAt",
851
+ "snapshot"
852
+ ],
853
+ "additionalProperties": false,
854
+ "description": "BookingVersion model (D18). Collection: companies/{companyId}/bookings/{bookingId}/versions/{versionId}. Server-owned audit trail written by Firebase trigger on every Booking mutation. Captures writes from all clients.",
855
+ "example": {
856
+ "id": "bk_abc123def456",
857
+ "bookingId": "boo_ref123",
858
+ "companyId": "comp_xyz789",
859
+ "changeType": "CREATE",
860
+ "changedAt": "changedAt",
861
+ "changedBy": null,
862
+ "changedByRole": "DASHBOARD",
863
+ "fieldsChanged": null,
864
+ "snapshot": {}
865
+ }
866
+ },
867
+ "customer": {
868
+ "type": "object",
869
+ "properties": {
870
+ "id": {
871
+ "type": "string",
872
+ "readOnly": true,
873
+ "description": "(Read-only) Firestore document ID. This is the canonical FK target \u2014 other models reference Customer via this field."
874
+ },
875
+ "uid": {
876
+ "type": "string",
877
+ "readOnly": true,
878
+ "description": "(Read-only) Entity UID. Often mirrors id."
879
+ },
880
+ "name": {
881
+ "type": "string"
882
+ },
883
+ "email": {
884
+ "type": [
885
+ "string",
886
+ "null"
887
+ ]
888
+ },
889
+ "phone": {
890
+ "type": [
891
+ "string",
892
+ "null"
893
+ ]
894
+ },
895
+ "address": {
896
+ "type": [
897
+ "string",
898
+ "null"
899
+ ]
900
+ },
901
+ "notes": {
902
+ "type": [
903
+ "string",
904
+ "null"
905
+ ]
906
+ },
907
+ "tags": {
908
+ "type": [
909
+ "array",
910
+ "null"
911
+ ],
912
+ "items": {
913
+ "type": "string"
914
+ }
915
+ },
916
+ "communicationEntries": {
917
+ "description": "Embedded CRM log (D25). Stays as array, not subcollection.",
918
+ "type": [
919
+ "array",
920
+ "null"
921
+ ],
922
+ "items": {
923
+ "type": "object",
924
+ "properties": {
925
+ "timestamp": {
926
+ "$ref": "#/definitions/firestore-timestamp",
927
+ "description": "Firestore Timestamp serialized representation"
928
+ },
929
+ "type": {
930
+ "type": "string",
931
+ "description": "Communication channel (e.g. phone, email, meeting)."
932
+ },
933
+ "summary": {
934
+ "type": "string"
935
+ },
936
+ "staffMember": {
937
+ "description": "Staff member name involved in this communication.",
938
+ "type": "string"
939
+ }
940
+ },
941
+ "required": [
942
+ "timestamp",
943
+ "type",
944
+ "summary"
945
+ ],
946
+ "additionalProperties": false,
947
+ "description": "CRM communication log entry (D25). Embedded array on Customer document."
948
+ }
949
+ },
950
+ "createdAt": {
951
+ "$ref": "#/definitions/firestore-timestamp",
952
+ "description": "(Read-only) Server-generated creation timestamp.",
953
+ "readOnly": true
954
+ },
955
+ "lastOrderDate": {
956
+ "anyOf": [
957
+ {
958
+ "$ref": "#/definitions/firestore-timestamp"
959
+ },
960
+ {
961
+ "type": "null"
962
+ }
963
+ ],
964
+ "readOnly": true,
965
+ "description": "(Read-only) Updated by server when orders are placed."
966
+ },
967
+ "balance": {
968
+ "readOnly": true,
969
+ "description": "(Read-only) Outstanding balance. Dashboard-originated, server-calculated.",
970
+ "type": [
971
+ "number",
972
+ "null"
973
+ ]
974
+ },
975
+ "creditLimit": {
976
+ "type": [
977
+ "number",
978
+ "null"
979
+ ]
980
+ },
981
+ "lastPaymentDate": {
982
+ "anyOf": [
983
+ {
984
+ "$ref": "#/definitions/firestore-timestamp"
985
+ },
986
+ {
987
+ "type": "null"
988
+ }
989
+ ],
990
+ "readOnly": true,
991
+ "description": "(Read-only) Updated by server when payments are recorded."
992
+ },
993
+ "totalPaid": {
994
+ "readOnly": true,
995
+ "description": "(Read-only) Server-calculated total paid amount.",
996
+ "type": [
997
+ "number",
998
+ "null"
999
+ ]
1000
+ },
1001
+ "totalOwed": {
1002
+ "readOnly": true,
1003
+ "description": "(Read-only) Server-calculated total owed amount.",
1004
+ "type": [
1005
+ "number",
1006
+ "null"
1007
+ ]
1008
+ },
1009
+ "loyaltyPoints": {
1010
+ "denormalized": true,
1011
+ "readOnly": true,
1012
+ "description": "(Read-only, Denormalized) Derived summary from loyalty/status subcollection (D08). Source of truth is LoyaltyStatus.pointsBalance.",
1013
+ "type": [
1014
+ "number",
1015
+ "null"
1016
+ ]
1017
+ }
1018
+ },
1019
+ "required": [
1020
+ "id",
1021
+ "uid",
1022
+ "name",
1023
+ "createdAt"
1024
+ ],
1025
+ "additionalProperties": false,
1026
+ "description": "Customer model. Collection: companies/{companyId}/customers/{customerId}. Canonical across all platforms.",
1027
+ "example": {
1028
+ "id": "bk_abc123def456",
1029
+ "uid": "user_u8x92kqm",
1030
+ "name": "Amadou Diallo",
1031
+ "email": null,
1032
+ "phone": null,
1033
+ "address": null,
1034
+ "notes": null,
1035
+ "tags": null,
1036
+ "communicationEntries": null,
1037
+ "createdAt": "createdAt",
1038
+ "lastOrderDate": "lastOrderDate",
1039
+ "balance": null,
1040
+ "creditLimit": null,
1041
+ "lastPaymentDate": "lastPaymentDate",
1042
+ "totalPaid": null,
1043
+ "totalOwed": null,
1044
+ "loyaltyPoints": null
1045
+ }
1046
+ },
1047
+ "customer-payment": {
1048
+ "type": "object",
1049
+ "properties": {
1050
+ "id": {
1051
+ "readOnly": true,
1052
+ "description": "(Read-only) Firestore document ID. Note: optional in current schema \u2014 some legacy docs may lack this field.",
1053
+ "type": [
1054
+ "string",
1055
+ "null"
1056
+ ]
1057
+ },
1058
+ "companyId": {
1059
+ "type": "string",
1060
+ "x-immutable": true,
1061
+ "description": "(Immutable) FK \u2192 Company document ID. Scopes all queries."
1062
+ },
1063
+ "customerId": {
1064
+ "type": "string",
1065
+ "x-immutable": true,
1066
+ "description": "(Immutable) FK \u2192 Customer.id (Firestore doc ID). Links payment to customer. Set at creation."
1067
+ },
1068
+ "customerName": {
1069
+ "denormalized": true,
1070
+ "description": "(Denormalized) From Customer.name at write time.",
1071
+ "type": [
1072
+ "string",
1073
+ "null"
1074
+ ]
1075
+ },
1076
+ "amount": {
1077
+ "type": "number"
1078
+ },
1079
+ "currency": {
1080
+ "type": "string",
1081
+ "const": "XOF",
1082
+ "description": "Currency code. Locked to XOF (West African CFA franc) for now."
1083
+ },
1084
+ "paymentDate": {
1085
+ "$ref": "#/definitions/firestore-timestamp",
1086
+ "description": "Firestore Timestamp serialized representation"
1087
+ },
1088
+ "paymentMethod": {
1089
+ "$ref": "#/definitions/payment-method",
1090
+ "description": "Unified payment method set with African + global methods (D02)."
1091
+ },
1092
+ "referenceNumber": {
1093
+ "type": "string",
1094
+ "description": "Unique payment reference (receipt number, transaction ID, etc.)."
1095
+ },
1096
+ "allocatedAmount": {
1097
+ "type": "number",
1098
+ "readOnly": true,
1099
+ "description": "(Read-only) Total amount allocated to bookings/orders/purchases via allocations. Server-calculated."
1100
+ },
1101
+ "unappliedAmount": {
1102
+ "type": "number",
1103
+ "readOnly": true,
1104
+ "description": "(Read-only) Remaining unallocated amount (amount - allocatedAmount). Server-calculated."
1105
+ },
1106
+ "status": {
1107
+ "$ref": "#/definitions/customer-payment-status",
1108
+ "description": "Customer payment lifecycle status (D22). Tracks allocation progress of received payments."
1109
+ },
1110
+ "notes": {
1111
+ "type": [
1112
+ "string",
1113
+ "null"
1114
+ ]
1115
+ },
1116
+ "recordedBy": {
1117
+ "type": "string",
1118
+ "x-immutable": true,
1119
+ "description": "(Immutable) FK \u2192 User/staff UID who recorded the payment. Required audit field (D22/IG-7)."
1120
+ },
1121
+ "recordedByName": {
1122
+ "x-immutable": true,
1123
+ "denormalized": true,
1124
+ "description": "(Immutable, Denormalized) From User display name at creation time.",
1125
+ "type": [
1126
+ "string",
1127
+ "null"
1128
+ ]
1129
+ },
1130
+ "createdAt": {
1131
+ "$ref": "#/definitions/firestore-timestamp",
1132
+ "description": "(Read-only) Server-generated creation timestamp.",
1133
+ "readOnly": true
1134
+ },
1135
+ "updatedAt": {
1136
+ "anyOf": [
1137
+ {
1138
+ "$ref": "#/definitions/firestore-timestamp"
1139
+ },
1140
+ {
1141
+ "type": "null"
1142
+ }
1143
+ ],
1144
+ "readOnly": true,
1145
+ "description": "(Read-only) Server-generated update timestamp."
1146
+ }
1147
+ },
1148
+ "required": [
1149
+ "companyId",
1150
+ "customerId",
1151
+ "amount",
1152
+ "currency",
1153
+ "paymentDate",
1154
+ "paymentMethod",
1155
+ "referenceNumber",
1156
+ "allocatedAmount",
1157
+ "unappliedAmount",
1158
+ "status",
1159
+ "recordedBy",
1160
+ "createdAt"
1161
+ ],
1162
+ "additionalProperties": false,
1163
+ "description": "CustomerPayment model (D22). Collection: companies/{companyId}/customerPayments/{paymentId}. Dashboard-only today; Mobile read in Wave 2, write in Wave 4 (trusted-party workflow).",
1164
+ "example": {
1165
+ "id": null,
1166
+ "companyId": "comp_xyz789",
1167
+ "customerId": "cus_ref123",
1168
+ "customerName": null,
1169
+ "amount": 45000,
1170
+ "currency": "XOF",
1171
+ "paymentDate": "paymentDate",
1172
+ "paymentMethod": "paymentMethod",
1173
+ "referenceNumber": "PAY-2026-0099",
1174
+ "allocatedAmount": 30000,
1175
+ "unappliedAmount": 15000,
1176
+ "status": "status",
1177
+ "notes": null,
1178
+ "recordedBy": "staff_k0f1",
1179
+ "recordedByName": null,
1180
+ "createdAt": "createdAt",
1181
+ "updatedAt": "updatedAt"
1182
+ }
1183
+ },
1184
+ "customer-payment-allocation": {
1185
+ "type": "object",
1186
+ "properties": {
1187
+ "id": {
1188
+ "readOnly": true,
1189
+ "description": "(Read-only) Firestore document ID. Note: optional in current schema.",
1190
+ "type": [
1191
+ "string",
1192
+ "null"
1193
+ ]
1194
+ },
1195
+ "companyId": {
1196
+ "type": "string",
1197
+ "x-immutable": true,
1198
+ "description": "(Immutable) FK \u2192 Company document ID."
1199
+ },
1200
+ "paymentId": {
1201
+ "type": "string",
1202
+ "x-immutable": true,
1203
+ "description": "(Immutable) FK \u2192 CustomerPayment.id. Parent payment this allocation draws from."
1204
+ },
1205
+ "customerId": {
1206
+ "type": "string",
1207
+ "x-immutable": true,
1208
+ "description": "(Immutable) FK \u2192 Customer.id (Firestore doc ID)."
1209
+ },
1210
+ "targetId": {
1211
+ "type": "string",
1212
+ "x-immutable": true,
1213
+ "description": "(Immutable) FK \u2192 Booking.id, Order.id, or Sale.id (polymorphic). See targetType for discriminator."
1214
+ },
1215
+ "targetType": {
1216
+ "$ref": "#/definitions/customer-payment-target-type",
1217
+ "description": "(Immutable) Discriminator for targetId: BOOKING, ORDER, or PURCHASE.",
1218
+ "x-immutable": true
1219
+ },
1220
+ "targetReference": {
1221
+ "type": [
1222
+ "string",
1223
+ "null"
1224
+ ]
1225
+ },
1226
+ "allocatedAmount": {
1227
+ "type": "number",
1228
+ "x-immutable": true,
1229
+ "description": "(Immutable) Amount allocated in this allocation. Set at creation."
1230
+ },
1231
+ "transferredToAllocationId": {
1232
+ "description": "FK \u2192 CustomerPaymentAllocation.id. Self-reference for transfer chains.",
1233
+ "type": [
1234
+ "string",
1235
+ "null"
1236
+ ]
1237
+ },
1238
+ "transferredFromAllocationId": {
1239
+ "description": "FK \u2192 CustomerPaymentAllocation.id. Self-reference for transfer chains.",
1240
+ "type": [
1241
+ "string",
1242
+ "null"
1243
+ ]
1244
+ },
1245
+ "transferredAt": {
1246
+ "anyOf": [
1247
+ {
1248
+ "$ref": "#/definitions/firestore-timestamp"
1249
+ },
1250
+ {
1251
+ "type": "null"
1252
+ }
1253
+ ],
1254
+ "description": "Firestore Timestamp serialized representation"
1255
+ },
1256
+ "createdBy": {
1257
+ "type": "string",
1258
+ "x-immutable": true,
1259
+ "description": "(Immutable) FK \u2192 User/staff UID who created this allocation."
1260
+ },
1261
+ "createdByName": {
1262
+ "x-immutable": true,
1263
+ "denormalized": true,
1264
+ "description": "(Immutable, Denormalized) From User display name at creation time.",
1265
+ "type": [
1266
+ "string",
1267
+ "null"
1268
+ ]
1269
+ },
1270
+ "createdAt": {
1271
+ "$ref": "#/definitions/firestore-timestamp",
1272
+ "description": "(Read-only) Server-generated creation timestamp.",
1273
+ "readOnly": true
1274
+ }
1275
+ },
1276
+ "required": [
1277
+ "companyId",
1278
+ "paymentId",
1279
+ "customerId",
1280
+ "targetId",
1281
+ "targetType",
1282
+ "allocatedAmount",
1283
+ "createdBy",
1284
+ "createdAt"
1285
+ ],
1286
+ "additionalProperties": false,
1287
+ "description": "CustomerPaymentAllocation model. Collection: companies/{companyId}/customerPaymentAllocations/{allocId}. Links customer payments to bookings, orders, or purchases.",
1288
+ "example": {
1289
+ "id": null,
1290
+ "companyId": "comp_xyz789",
1291
+ "paymentId": "pay_ref123",
1292
+ "customerId": "cus_ref123",
1293
+ "targetId": "tar_ref123",
1294
+ "targetType": "targetType",
1295
+ "targetReference": null,
1296
+ "allocatedAmount": 30000,
1297
+ "transferredToAllocationId": null,
1298
+ "transferredFromAllocationId": null,
1299
+ "transferredAt": "transferredAt",
1300
+ "createdBy": "staff_k0f1",
1301
+ "createdByName": null,
1302
+ "createdAt": "createdAt"
1303
+ }
1304
+ },
1305
+ "customer-payment-status": {
1306
+ "type": "string",
1307
+ "enum": [
1308
+ "PENDING",
1309
+ "CONFIRMED",
1310
+ "PARTIALLY_APPLIED",
1311
+ "FULLY_APPLIED",
1312
+ "REFUNDED",
1313
+ "CANCELLED"
1314
+ ],
1315
+ "description": "Customer payment lifecycle status (D22). Tracks allocation progress of received payments."
1316
+ },
1317
+ "customer-payment-target-type": {
1318
+ "type": "string",
1319
+ "enum": [
1320
+ "BOOKING",
1321
+ "ORDER",
1322
+ "PURCHASE"
1323
+ ],
1324
+ "description": "Target document type for customer payment allocation."
1325
+ },
1326
+ "delivery-type": {
1327
+ "type": "string",
1328
+ "enum": [
1329
+ "ON_SITE",
1330
+ "PICK_UP",
1331
+ "DELIVERY"
1332
+ ],
1333
+ "description": "Fulfillment channel for an order. Determines whether the customer comes to the business (ON_SITE), collects their order themselves (PICK_UP), or receives a physical delivery (DELIVERY). Drives whether fulfillmentStatus is relevant."
1334
+ },
1335
+ "event": {
1336
+ "type": "object",
1337
+ "properties": {
1338
+ "id": {
1339
+ "type": "string",
1340
+ "readOnly": true,
1341
+ "description": "(Read-only) Firestore document ID. Note: Event does not have a uid field."
1342
+ },
1343
+ "companyId": {
1344
+ "type": "string",
1345
+ "x-immutable": true,
1346
+ "description": "(Immutable) FK \u2192 Company document ID. Scopes all queries."
1347
+ },
1348
+ "name": {
1349
+ "type": "string"
1350
+ },
1351
+ "description": {
1352
+ "type": [
1353
+ "string",
1354
+ "null"
1355
+ ]
1356
+ },
1357
+ "location": {
1358
+ "type": [
1359
+ "string",
1360
+ "null"
1361
+ ]
1362
+ },
1363
+ "startDate": {
1364
+ "$ref": "#/definitions/firestore-timestamp",
1365
+ "description": "Firestore Timestamp serialized representation"
1366
+ },
1367
+ "endDate": {
1368
+ "anyOf": [
1369
+ {
1370
+ "$ref": "#/definitions/firestore-timestamp"
1371
+ },
1372
+ {
1373
+ "type": "null"
1374
+ }
1375
+ ],
1376
+ "description": "Firestore Timestamp serialized representation"
1377
+ },
1378
+ "status": {
1379
+ "$ref": "#/definitions/event-status",
1380
+ "description": "Event lifecycle status (D32). SCREAMING_SNAKE per D04. MIG-09 migrates legacy lowercase values."
1381
+ },
1382
+ "maxTickets": {
1383
+ "type": [
1384
+ "integer",
1385
+ "null"
1386
+ ],
1387
+ "minimum": -9007199254740991,
1388
+ "maximum": 9007199254740991
1389
+ },
1390
+ "ticketsSold": {
1391
+ "type": "integer",
1392
+ "minimum": -9007199254740991,
1393
+ "maximum": 9007199254740991,
1394
+ "readOnly": true,
1395
+ "description": "(Read-only) Counter: total tickets sold. Updated by Firebase triggers (D28)."
1396
+ },
1397
+ "ticketsUsed": {
1398
+ "type": "integer",
1399
+ "minimum": -9007199254740991,
1400
+ "maximum": 9007199254740991,
1401
+ "readOnly": true,
1402
+ "description": "(Read-only) Counter: tickets scanned/used. Updated by Firebase triggers (D28)."
1403
+ },
1404
+ "ticketPrice": {
1405
+ "type": [
1406
+ "number",
1407
+ "null"
1408
+ ]
1409
+ },
1410
+ "createdAt": {
1411
+ "$ref": "#/definitions/firestore-timestamp",
1412
+ "description": "(Read-only) Server-generated creation timestamp.",
1413
+ "readOnly": true
1414
+ },
1415
+ "updatedAt": {
1416
+ "$ref": "#/definitions/firestore-timestamp",
1417
+ "description": "(Read-only) Server-generated update timestamp.",
1418
+ "readOnly": true
1419
+ },
1420
+ "createdBy": {
1421
+ "x-immutable": true,
1422
+ "description": "(Immutable) FK \u2192 User/staff UID who created this event.",
1423
+ "type": [
1424
+ "string",
1425
+ "null"
1426
+ ]
1427
+ }
1428
+ },
1429
+ "required": [
1430
+ "id",
1431
+ "companyId",
1432
+ "name",
1433
+ "startDate",
1434
+ "status",
1435
+ "ticketsSold",
1436
+ "ticketsUsed",
1437
+ "createdAt",
1438
+ "updatedAt"
1439
+ ],
1440
+ "additionalProperties": false,
1441
+ "description": "Event model (D26, D32). Collection: companies/{companyId}/events/{eventId}. Mobile-only today; Dashboard in Wave 4.",
1442
+ "example": {
1443
+ "id": "bk_abc123def456",
1444
+ "companyId": "comp_xyz789",
1445
+ "name": "Amadou Diallo",
1446
+ "description": null,
1447
+ "location": null,
1448
+ "startDate": "2026-02-15",
1449
+ "endDate": "2026-02-20",
1450
+ "status": "status",
1451
+ "maxTickets": null,
1452
+ "ticketsSold": 47,
1453
+ "ticketsUsed": 12,
1454
+ "ticketPrice": null,
1455
+ "createdAt": "createdAt",
1456
+ "updatedAt": "updatedAt",
1457
+ "createdBy": null
1458
+ }
1459
+ },
1460
+ "event-status": {
1461
+ "type": "string",
1462
+ "enum": [
1463
+ "DRAFT",
1464
+ "ACTIVE",
1465
+ "CANCELLED",
1466
+ "COMPLETED"
1467
+ ],
1468
+ "description": "Ticketed event lifecycle (D32). Mobile-only today; Dashboard in Wave 4."
1469
+ },
1470
+ "fulfillment-status": {
1471
+ "type": "string",
1472
+ "enum": [
1473
+ "PREPARING",
1474
+ "PARTIALLY_SHIPPED",
1475
+ "SHIPPED",
1476
+ "IN_TRANSIT",
1477
+ "DELIVERED",
1478
+ "PICKED_UP"
1479
+ ],
1480
+ "description": "Delivery/fulfillment lifecycle (D34). Optional \u2014 null for in-person orders."
1481
+ },
1482
+ "loyalty-config": {
1483
+ "type": "object",
1484
+ "properties": {
1485
+ "id": {
1486
+ "readOnly": true,
1487
+ "description": "(Read-only) Firestore document ID. Singleton doc \u2014 typically \"config\".",
1488
+ "type": [
1489
+ "string",
1490
+ "null"
1491
+ ]
1492
+ },
1493
+ "isEnabled": {
1494
+ "type": "boolean"
1495
+ },
1496
+ "pointSystem": {
1497
+ "type": "string",
1498
+ "enum": [
1499
+ "SPENDING",
1500
+ "PRODUCT",
1501
+ "VISIT"
1502
+ ],
1503
+ "description": "How points are earned: SPENDING (per currency spent), PRODUCT (per product purchased), or VISIT (per visit). SCREAMING_SNAKE per D04."
1504
+ },
1505
+ "pointsPerCurrency": {
1506
+ "description": "[Deprecated alias: pointsPerCurrencyUnit \u2014 renamed by MIG-06]",
1507
+ "deprecated": true,
1508
+ "type": [
1509
+ "number",
1510
+ "null"
1511
+ ]
1512
+ },
1513
+ "pointsPerVisit": {
1514
+ "description": "Points earned per visit (when pointSystem is visit).",
1515
+ "type": [
1516
+ "number",
1517
+ "null"
1518
+ ]
1519
+ },
1520
+ "pointValue": {
1521
+ "description": "Monetary value of one point for redemption.",
1522
+ "type": [
1523
+ "number",
1524
+ "null"
1525
+ ]
1526
+ },
1527
+ "minimumRedeemPoints": {
1528
+ "description": "Minimum points balance required before redemption is allowed.",
1529
+ "type": [
1530
+ "integer",
1531
+ "null"
1532
+ ],
1533
+ "minimum": -9007199254740991,
1534
+ "maximum": 9007199254740991
1535
+ },
1536
+ "welcomeBonusPoints": {
1537
+ "description": "One-time points bonus for new loyalty enrollees.",
1538
+ "type": [
1539
+ "integer",
1540
+ "null"
1541
+ ],
1542
+ "minimum": -9007199254740991,
1543
+ "maximum": 9007199254740991
1544
+ },
1545
+ "pointsExpirationDays": {
1546
+ "description": "[Deprecated alias: pointsExpiryMonths \u2014 renamed and converted months*30 to days by MIG-06]",
1547
+ "deprecated": true,
1548
+ "anyOf": [
1549
+ {
1550
+ "type": "integer",
1551
+ "minimum": -9007199254740991,
1552
+ "maximum": 9007199254740991
1553
+ },
1554
+ {
1555
+ "type": "null"
1556
+ }
1557
+ ]
1558
+ },
1559
+ "createdAt": {
1560
+ "anyOf": [
1561
+ {
1562
+ "$ref": "#/definitions/firestore-timestamp"
1563
+ },
1564
+ {
1565
+ "type": "null"
1566
+ }
1567
+ ],
1568
+ "readOnly": true,
1569
+ "description": "(Read-only) Server-generated creation timestamp."
1570
+ },
1571
+ "updatedAt": {
1572
+ "anyOf": [
1573
+ {
1574
+ "$ref": "#/definitions/firestore-timestamp"
1575
+ },
1576
+ {
1577
+ "type": "null"
1578
+ }
1579
+ ],
1580
+ "readOnly": true,
1581
+ "description": "(Read-only) Server-generated update timestamp."
1582
+ }
1583
+ },
1584
+ "required": [
1585
+ "isEnabled",
1586
+ "pointSystem"
1587
+ ],
1588
+ "additionalProperties": false,
1589
+ "description": "LoyaltyConfig model (D21). Collection: companies/{companyId}/loyaltySettings/config. 1 doc per company. Uses canonical field names per D21 (MIG-06 renames legacy aliases).",
1590
+ "example": {
1591
+ "id": null,
1592
+ "isEnabled": true,
1593
+ "pointSystem": "SPENDING",
1594
+ "pointsPerCurrency": null,
1595
+ "pointsPerVisit": null,
1596
+ "pointValue": null,
1597
+ "minimumRedeemPoints": null,
1598
+ "welcomeBonusPoints": null,
1599
+ "pointsExpirationDays": "pointsExpirationDays",
1600
+ "createdAt": "createdAt",
1601
+ "updatedAt": "updatedAt"
1602
+ }
1603
+ },
1604
+ "loyalty-reward": {
1605
+ "type": "object",
1606
+ "properties": {
1607
+ "id": {
1608
+ "type": "string",
1609
+ "readOnly": true,
1610
+ "description": "(Read-only) Firestore document ID."
1611
+ },
1612
+ "name": {
1613
+ "type": "string"
1614
+ },
1615
+ "description": {
1616
+ "type": [
1617
+ "string",
1618
+ "null"
1619
+ ]
1620
+ },
1621
+ "pointsRequired": {
1622
+ "type": "integer",
1623
+ "minimum": -9007199254740991,
1624
+ "maximum": 9007199254740991,
1625
+ "description": "Points cost to redeem this reward."
1626
+ },
1627
+ "isActive": {
1628
+ "type": "boolean",
1629
+ "description": "Whether this reward is currently available for redemption."
1630
+ },
1631
+ "imageUrl": {
1632
+ "type": [
1633
+ "string",
1634
+ "null"
1635
+ ]
1636
+ },
1637
+ "rewardType": {
1638
+ "description": "Category of reward (e.g. discount, free_item, service).",
1639
+ "type": [
1640
+ "string",
1641
+ "null"
1642
+ ]
1643
+ },
1644
+ "discountValue": {
1645
+ "description": "Discount amount when rewardType is discount.",
1646
+ "type": [
1647
+ "number",
1648
+ "null"
1649
+ ]
1650
+ },
1651
+ "productId": {
1652
+ "description": "FK \u2192 Product.id. Linked product when rewardType is free_item.",
1653
+ "type": [
1654
+ "string",
1655
+ "null"
1656
+ ]
1657
+ },
1658
+ "createdAt": {
1659
+ "$ref": "#/definitions/firestore-timestamp",
1660
+ "description": "(Read-only) Server-generated creation timestamp.",
1661
+ "readOnly": true
1662
+ },
1663
+ "updatedAt": {
1664
+ "anyOf": [
1665
+ {
1666
+ "$ref": "#/definitions/firestore-timestamp"
1667
+ },
1668
+ {
1669
+ "type": "null"
1670
+ }
1671
+ ],
1672
+ "readOnly": true,
1673
+ "description": "(Read-only) Server-generated update timestamp."
1674
+ }
1675
+ },
1676
+ "required": [
1677
+ "id",
1678
+ "name",
1679
+ "pointsRequired",
1680
+ "isActive",
1681
+ "createdAt"
1682
+ ],
1683
+ "additionalProperties": false,
1684
+ "description": "LoyaltyReward model. Collection: companies/{companyId}/loyaltyRewards/{rewardId}. Reward catalog for point redemption (IG-10).",
1685
+ "example": {
1686
+ "id": "bk_abc123def456",
1687
+ "name": "Free Braiding Session",
1688
+ "description": null,
1689
+ "pointsRequired": 200,
1690
+ "isActive": true,
1691
+ "imageUrl": null,
1692
+ "rewardType": null,
1693
+ "discountValue": null,
1694
+ "productId": null,
1695
+ "createdAt": "createdAt",
1696
+ "updatedAt": "updatedAt"
1697
+ }
1698
+ },
1699
+ "loyalty-status": {
1700
+ "type": "object",
1701
+ "properties": {
1702
+ "customerId": {
1703
+ "x-immutable": true,
1704
+ "description": "(Immutable) FK \u2192 Customer.id. Note: optional despite being in customer subcollection (path already contains custId).",
1705
+ "type": [
1706
+ "string",
1707
+ "null"
1708
+ ]
1709
+ },
1710
+ "pointsBalance": {
1711
+ "type": "integer",
1712
+ "minimum": -9007199254740991,
1713
+ "maximum": 9007199254740991,
1714
+ "readOnly": true,
1715
+ "description": "(Read-only) Current available points balance. Canonical name (D08). Dashboard: pointsBalance, Mobile: currentPoints. Updated by transaction triggers."
1716
+ },
1717
+ "totalPointsEarned": {
1718
+ "type": "integer",
1719
+ "minimum": -9007199254740991,
1720
+ "maximum": 9007199254740991,
1721
+ "readOnly": true,
1722
+ "description": "(Read-only) Lifetime total points earned. Canonical name (D08). Dashboard: totalPointsEarned, Mobile: lifetimePoints. Updated by transaction triggers."
1723
+ },
1724
+ "redeemedPoints": {
1725
+ "readOnly": true,
1726
+ "description": "(Read-only) Total redeemed points. Updated by transaction triggers.",
1727
+ "type": [
1728
+ "integer",
1729
+ "null"
1730
+ ],
1731
+ "minimum": -9007199254740991,
1732
+ "maximum": 9007199254740991
1733
+ },
1734
+ "lastActivityDate": {
1735
+ "anyOf": [
1736
+ {
1737
+ "$ref": "#/definitions/firestore-timestamp"
1738
+ },
1739
+ {
1740
+ "type": "null"
1741
+ }
1742
+ ],
1743
+ "readOnly": true,
1744
+ "description": "(Read-only) Last earn/redeem activity. Canonical name (D08). Mobile alias: lastEarnedAt."
1745
+ },
1746
+ "lastRedeemedAt": {
1747
+ "anyOf": [
1748
+ {
1749
+ "$ref": "#/definitions/firestore-timestamp"
1750
+ },
1751
+ {
1752
+ "type": "null"
1753
+ }
1754
+ ],
1755
+ "readOnly": true,
1756
+ "description": "(Read-only) Last redemption timestamp."
1757
+ },
1758
+ "tier": {
1759
+ "type": [
1760
+ "string",
1761
+ "null"
1762
+ ]
1763
+ },
1764
+ "createdAt": {
1765
+ "anyOf": [
1766
+ {
1767
+ "$ref": "#/definitions/firestore-timestamp"
1768
+ },
1769
+ {
1770
+ "type": "null"
1771
+ }
1772
+ ],
1773
+ "readOnly": true,
1774
+ "description": "(Read-only) Server-generated creation timestamp."
1775
+ },
1776
+ "updatedAt": {
1777
+ "anyOf": [
1778
+ {
1779
+ "$ref": "#/definitions/firestore-timestamp"
1780
+ },
1781
+ {
1782
+ "type": "null"
1783
+ }
1784
+ ],
1785
+ "readOnly": true,
1786
+ "description": "(Read-only) Server-generated update timestamp."
1787
+ }
1788
+ },
1789
+ "required": [
1790
+ "pointsBalance",
1791
+ "totalPointsEarned"
1792
+ ],
1793
+ "additionalProperties": false,
1794
+ "description": "LoyaltyStatus model (D08). Collection: companies/{companyId}/customers/{custId}/loyalty/status. Source of truth for customer points. Customer doc loyaltyPoints is a derived summary only.",
1795
+ "example": {
1796
+ "customerId": null,
1797
+ "pointsBalance": 340,
1798
+ "totalPointsEarned": 1200,
1799
+ "redeemedPoints": null,
1800
+ "lastActivityDate": "lastActivityDate",
1801
+ "lastRedeemedAt": "lastRedeemedAt",
1802
+ "tier": null,
1803
+ "createdAt": "createdAt",
1804
+ "updatedAt": "updatedAt"
1805
+ }
1806
+ },
1807
+ "loyalty-transaction": {
1808
+ "type": "object",
1809
+ "properties": {
1810
+ "id": {
1811
+ "type": "string",
1812
+ "readOnly": true,
1813
+ "description": "(Read-only) Firestore document ID."
1814
+ },
1815
+ "customerId": {
1816
+ "x-immutable": true,
1817
+ "description": "(Immutable) FK \u2192 Customer.id. Note: optional despite being in customer subcollection.",
1818
+ "type": [
1819
+ "string",
1820
+ "null"
1821
+ ]
1822
+ },
1823
+ "type": {
1824
+ "$ref": "#/definitions/loyalty-transaction-type",
1825
+ "description": "(Immutable) Transaction type (D07). SCREAMING_SNAKE per D04. MIG-05 migrates legacy lowercase values.",
1826
+ "x-immutable": true
1827
+ },
1828
+ "pointsChange": {
1829
+ "type": "integer",
1830
+ "minimum": -9007199254740991,
1831
+ "maximum": 9007199254740991,
1832
+ "x-immutable": true,
1833
+ "description": "(Immutable) Points delta (+/-). Canonical name (D07). Dashboard: pointsChange, Mobile: points."
1834
+ },
1835
+ "description": {
1836
+ "type": [
1837
+ "string",
1838
+ "null"
1839
+ ]
1840
+ },
1841
+ "reason": {
1842
+ "type": [
1843
+ "string",
1844
+ "null"
1845
+ ]
1846
+ },
1847
+ "relatedPurchaseId": {
1848
+ "x-immutable": true,
1849
+ "description": "(Immutable) FK \u2192 Sale.id. Linked sale that triggered this transaction.",
1850
+ "type": [
1851
+ "string",
1852
+ "null"
1853
+ ]
1854
+ },
1855
+ "relatedOrderId": {
1856
+ "x-immutable": true,
1857
+ "description": "(Immutable) FK \u2192 Order.id. Linked order that triggered this transaction.",
1858
+ "type": [
1859
+ "string",
1860
+ "null"
1861
+ ]
1862
+ },
1863
+ "relatedRewardId": {
1864
+ "x-immutable": true,
1865
+ "description": "(Immutable) FK \u2192 LoyaltyReward.id. Reward redeemed in this transaction.",
1866
+ "type": [
1867
+ "string",
1868
+ "null"
1869
+ ]
1870
+ },
1871
+ "orderId": {
1872
+ "x-immutable": true,
1873
+ "description": "(Immutable) FK \u2192 Order.id. Note: may overlap with relatedOrderId \u2014 naming inconsistency from Mobile.",
1874
+ "type": [
1875
+ "string",
1876
+ "null"
1877
+ ]
1878
+ },
1879
+ "sessionId": {
1880
+ "description": "Session/booking reference. Context-dependent identifier.",
1881
+ "type": [
1882
+ "string",
1883
+ "null"
1884
+ ]
1885
+ },
1886
+ "orderAmount": {
1887
+ "type": [
1888
+ "number",
1889
+ "null"
1890
+ ]
1891
+ },
1892
+ "transactionDate": {
1893
+ "$ref": "#/definitions/firestore-timestamp",
1894
+ "description": "(Immutable) When the transaction occurred. Canonical name (D07). Dashboard: transactionDate, Mobile: createdAt.",
1895
+ "x-immutable": true
1896
+ },
1897
+ "pointsBalanceAfter": {
1898
+ "readOnly": true,
1899
+ "description": "(Read-only) Running balance after this transaction. Canonical name (D07). Dashboard: pointsBalanceAfter, Mobile: balanceAfter. Server-calculated.",
1900
+ "type": [
1901
+ "integer",
1902
+ "null"
1903
+ ],
1904
+ "minimum": -9007199254740991,
1905
+ "maximum": 9007199254740991
1906
+ },
1907
+ "createdBy": {
1908
+ "x-immutable": true,
1909
+ "description": "(Immutable) FK \u2192 User/staff UID who created this transaction.",
1910
+ "type": [
1911
+ "string",
1912
+ "null"
1913
+ ]
1914
+ },
1915
+ "createdByName": {
1916
+ "x-immutable": true,
1917
+ "denormalized": true,
1918
+ "description": "(Immutable, Denormalized) From User display name at creation time.",
1919
+ "type": [
1920
+ "string",
1921
+ "null"
1922
+ ]
1923
+ }
1924
+ },
1925
+ "required": [
1926
+ "id",
1927
+ "type",
1928
+ "pointsChange",
1929
+ "transactionDate"
1930
+ ],
1931
+ "additionalProperties": false,
1932
+ "description": "LoyaltyTransaction model (D07). Collection: companies/{companyId}/customers/{custId}/loyaltyTransactions/{txId}. Type values use SCREAMING_SNAKE per D04 (MIG-05 migrates lowercase).",
1933
+ "example": {
1934
+ "id": "bk_abc123def456",
1935
+ "customerId": null,
1936
+ "type": "phone",
1937
+ "pointsChange": 50,
1938
+ "description": null,
1939
+ "reason": null,
1940
+ "relatedPurchaseId": null,
1941
+ "relatedOrderId": null,
1942
+ "relatedRewardId": null,
1943
+ "orderId": null,
1944
+ "sessionId": null,
1945
+ "orderAmount": null,
1946
+ "transactionDate": "transactionDate",
1947
+ "pointsBalanceAfter": null,
1948
+ "createdBy": null,
1949
+ "createdByName": null
1950
+ }
1951
+ },
1952
+ "loyalty-transaction-type": {
1953
+ "type": "string",
1954
+ "enum": [
1955
+ "EARNED",
1956
+ "REDEEMED",
1957
+ "ADJUSTED",
1958
+ "EXPIRED",
1959
+ "BONUS",
1960
+ "REFUND"
1961
+ ],
1962
+ "description": "Loyalty point transaction type (D07). SCREAMING_SNAKE past tense."
1963
+ },
1964
+ "metrics-current": {
1965
+ "type": "object",
1966
+ "properties": {
1967
+ "todayOrdersCount": {
1968
+ "type": "integer",
1969
+ "minimum": -9007199254740991,
1970
+ "maximum": 9007199254740991,
1971
+ "readOnly": true,
1972
+ "description": "(Read-only) Orders created today (UTC). Resets at midnight."
1973
+ },
1974
+ "pendingOrdersCount": {
1975
+ "type": "integer",
1976
+ "minimum": -9007199254740991,
1977
+ "maximum": 9007199254740991,
1978
+ "readOnly": true,
1979
+ "description": "(Read-only) Orders currently in PENDING status (all-time cumulative). Corrected by full recalc on order updates."
1980
+ },
1981
+ "orderCompletionRate30d": {
1982
+ "type": "number",
1983
+ "readOnly": true,
1984
+ "description": "(Read-only) Percentage of orders completed or delivered in the last 30 days. Always full recalc."
1985
+ },
1986
+ "todayPurchasesCount": {
1987
+ "type": "integer",
1988
+ "minimum": -9007199254740991,
1989
+ "maximum": 9007199254740991,
1990
+ "readOnly": true,
1991
+ "description": "(Read-only) Purchases created today (UTC). Resets at midnight."
1992
+ },
1993
+ "todayPurchasesSum": {
1994
+ "type": "number",
1995
+ "readOnly": true,
1996
+ "description": "(Read-only) Total value of purchases created today (UTC). Resets at midnight."
1997
+ },
1998
+ "averagePurchaseAmount": {
1999
+ "type": "number",
2000
+ "readOnly": true,
2001
+ "description": "(Read-only) Average purchase value across last 200 purchases (rolling, not time-windowed)."
2002
+ },
2003
+ "monthlyRevenue": {
2004
+ "type": "number",
2005
+ "readOnly": true,
2006
+ "description": "(Read-only) Total purchase value in the current calendar month (UTC)."
2007
+ },
2008
+ "monthlyPurchasesCount": {
2009
+ "type": "integer",
2010
+ "minimum": -9007199254740991,
2011
+ "maximum": 9007199254740991,
2012
+ "readOnly": true,
2013
+ "description": "(Read-only) Number of purchases in the current calendar month (UTC)."
2014
+ },
2015
+ "todayBookingsCount": {
2016
+ "type": "integer",
2017
+ "minimum": -9007199254740991,
2018
+ "maximum": 9007199254740991,
2019
+ "readOnly": true,
2020
+ "description": "(Read-only) Bookings with date == today AND status in [PENDING, CONFIRMED]. Resets at midnight."
2021
+ },
2022
+ "bookingsCreatedToday": {
2023
+ "type": "integer",
2024
+ "minimum": -9007199254740991,
2025
+ "maximum": 9007199254740991,
2026
+ "readOnly": true,
2027
+ "description": "(Read-only) Bookings with createdAt today (all statuses). Resets at midnight."
2028
+ },
2029
+ "todayBookingsConfirmedAmount": {
2030
+ "type": "number",
2031
+ "readOnly": true,
2032
+ "description": "(Read-only) Sum of totalAmount for bookings created today with status CONFIRMED or COMPLETED. Resets at midnight."
2033
+ },
2034
+ "monthlyBookingsConfirmedAmount": {
2035
+ "type": "number",
2036
+ "readOnly": true,
2037
+ "description": "(Read-only) Sum of totalAmount for bookings created this month with status CONFIRMED or COMPLETED."
2038
+ },
2039
+ "todayCollectedAmount": {
2040
+ "type": "number",
2041
+ "readOnly": true,
2042
+ "description": "(Read-only) Sum of totalAmount for bookings where PAYMENT_PAID_AT is today. Resets at midnight."
2043
+ },
2044
+ "todayRevenue": {
2045
+ "type": "number",
2046
+ "readOnly": true,
2047
+ "description": "(Read-only) Sum of totalAmount for COMPLETED bookings where startDate == endDate == today (numeric YYYYMMDD). Resets at midnight."
2048
+ },
2049
+ "bookingsPendingPaymentVerification": {
2050
+ "type": "integer",
2051
+ "minimum": -9007199254740991,
2052
+ "maximum": 9007199254740991,
2053
+ "readOnly": true,
2054
+ "description": "(Read-only) Bookings with status PENDING/CANCELLATION_REQUESTED and a payment proof uploaded but not yet verified. Current state, always full recalc."
2055
+ },
2056
+ "bookingsPendingValidation": {
2057
+ "type": "integer",
2058
+ "minimum": -9007199254740991,
2059
+ "maximum": 9007199254740991,
2060
+ "readOnly": true,
2061
+ "description": "(Read-only) PENDING bookings with startDate within \u00b130 days of today. Rolling window, always full recalc."
2062
+ },
2063
+ "bookingsPendingValidation24h": {
2064
+ "type": "integer",
2065
+ "minimum": -9007199254740991,
2066
+ "maximum": 9007199254740991,
2067
+ "readOnly": true,
2068
+ "description": "(Read-only) PENDING bookings created in the last 24h with startDate >= tomorrow. Always full recalc."
2069
+ },
2070
+ "customersCount": {
2071
+ "type": "integer",
2072
+ "minimum": -9007199254740991,
2073
+ "maximum": 9007199254740991,
2074
+ "readOnly": true,
2075
+ "description": "(Read-only) Total customer count (all-time cumulative)."
2076
+ },
2077
+ "newCustomersThisMonth": {
2078
+ "type": "integer",
2079
+ "minimum": -9007199254740991,
2080
+ "maximum": 9007199254740991,
2081
+ "readOnly": true,
2082
+ "description": "(Read-only) Customers with createdAt in the current calendar month (UTC)."
2083
+ },
2084
+ "averageRating": {
2085
+ "type": "number",
2086
+ "readOnly": true,
2087
+ "description": "(Read-only) Average rating from the last 200 reviews (rolling). Always full recalc."
2088
+ },
2089
+ "lowStockItemsCount": {
2090
+ "type": "integer",
2091
+ "minimum": -9007199254740991,
2092
+ "maximum": 9007199254740991,
2093
+ "readOnly": true,
2094
+ "description": "(Read-only) Active stock items where currentQuantity <= minimumQuantity. Current state, always full recalc."
2095
+ },
2096
+ "activeRecurringPaymentsCount": {
2097
+ "type": "integer",
2098
+ "minimum": -9007199254740991,
2099
+ "maximum": 9007199254740991,
2100
+ "readOnly": true,
2101
+ "description": "(Read-only) Recurring payments with status ACTIVE. Current state, always full recalc."
2102
+ },
2103
+ "monthlyRecurringRevenue": {
2104
+ "type": "number",
2105
+ "readOnly": true,
2106
+ "description": "(Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc."
2107
+ },
2108
+ "computedForDay": {
2109
+ "type": "string",
2110
+ "readOnly": true,
2111
+ "description": "(Read-only) YYYY-MM-DD string indicating which day these metrics reflect. Used to detect day boundaries and trigger midnight resets."
2112
+ },
2113
+ "generatedAt": {
2114
+ "$ref": "#/definitions/firestore-timestamp",
2115
+ "description": "(Read-only) Server timestamp of the last metrics write.",
2116
+ "readOnly": true
2117
+ }
2118
+ },
2119
+ "required": [
2120
+ "todayOrdersCount",
2121
+ "pendingOrdersCount",
2122
+ "orderCompletionRate30d",
2123
+ "todayPurchasesCount",
2124
+ "todayPurchasesSum",
2125
+ "averagePurchaseAmount",
2126
+ "monthlyRevenue",
2127
+ "monthlyPurchasesCount",
2128
+ "todayBookingsCount",
2129
+ "bookingsCreatedToday",
2130
+ "todayBookingsConfirmedAmount",
2131
+ "monthlyBookingsConfirmedAmount",
2132
+ "todayCollectedAmount",
2133
+ "todayRevenue",
2134
+ "bookingsPendingPaymentVerification",
2135
+ "bookingsPendingValidation",
2136
+ "bookingsPendingValidation24h",
2137
+ "customersCount",
2138
+ "newCustomersThisMonth",
2139
+ "averageRating",
2140
+ "lowStockItemsCount",
2141
+ "activeRecurringPaymentsCount",
2142
+ "monthlyRecurringRevenue",
2143
+ "computedForDay",
2144
+ "generatedAt"
2145
+ ],
2146
+ "additionalProperties": false,
2147
+ "description": "Server-computed company metrics. Collection: companies/{companyId}/metrics/current (singleton). All fields are server-set \u2014 clients must never write to this collection. See also MetricsDaily and MetricsMonthly for historical snapshots.",
2148
+ "example": {
2149
+ "todayOrdersCount": 2,
2150
+ "pendingOrdersCount": 2,
2151
+ "orderCompletionRate30d": 0,
2152
+ "todayPurchasesCount": 2,
2153
+ "todayPurchasesSum": 0,
2154
+ "averagePurchaseAmount": 15000,
2155
+ "monthlyRevenue": 0,
2156
+ "monthlyPurchasesCount": 2,
2157
+ "todayBookingsCount": 2,
2158
+ "bookingsCreatedToday": 0,
2159
+ "todayBookingsConfirmedAmount": 15000,
2160
+ "monthlyBookingsConfirmedAmount": 15000,
2161
+ "todayCollectedAmount": 15000,
2162
+ "todayRevenue": 0,
2163
+ "bookingsPendingPaymentVerification": 0,
2164
+ "bookingsPendingValidation": 0,
2165
+ "bookingsPendingValidation24h": 0,
2166
+ "customersCount": 2,
2167
+ "newCustomersThisMonth": 0,
2168
+ "averageRating": 0,
2169
+ "lowStockItemsCount": 2,
2170
+ "activeRecurringPaymentsCount": 2,
2171
+ "monthlyRecurringRevenue": 0,
2172
+ "computedForDay": "computedForDay",
2173
+ "generatedAt": "generatedAt"
2174
+ }
2175
+ },
2176
+ "metrics-daily": {
2177
+ "type": "object",
2178
+ "properties": {
2179
+ "todayOrdersCount": {
2180
+ "type": "integer",
2181
+ "minimum": -9007199254740991,
2182
+ "maximum": 9007199254740991,
2183
+ "readOnly": true,
2184
+ "description": "(Read-only) Orders created today (UTC). Resets at midnight."
2185
+ },
2186
+ "pendingOrdersCount": {
2187
+ "type": "integer",
2188
+ "minimum": -9007199254740991,
2189
+ "maximum": 9007199254740991,
2190
+ "readOnly": true,
2191
+ "description": "(Read-only) Orders currently in PENDING status (all-time cumulative). Corrected by full recalc on order updates."
2192
+ },
2193
+ "orderCompletionRate30d": {
2194
+ "type": "number",
2195
+ "readOnly": true,
2196
+ "description": "(Read-only) Percentage of orders completed or delivered in the last 30 days. Always full recalc."
2197
+ },
2198
+ "todayPurchasesCount": {
2199
+ "type": "integer",
2200
+ "minimum": -9007199254740991,
2201
+ "maximum": 9007199254740991,
2202
+ "readOnly": true,
2203
+ "description": "(Read-only) Purchases created today (UTC). Resets at midnight."
2204
+ },
2205
+ "todayPurchasesSum": {
2206
+ "type": "number",
2207
+ "readOnly": true,
2208
+ "description": "(Read-only) Total value of purchases created today (UTC). Resets at midnight."
2209
+ },
2210
+ "averagePurchaseAmount": {
2211
+ "type": "number",
2212
+ "readOnly": true,
2213
+ "description": "(Read-only) Average purchase value across last 200 purchases (rolling, not time-windowed)."
2214
+ },
2215
+ "monthlyRevenue": {
2216
+ "type": "number",
2217
+ "readOnly": true,
2218
+ "description": "(Read-only) Total purchase value in the current calendar month (UTC)."
2219
+ },
2220
+ "monthlyPurchasesCount": {
2221
+ "type": "integer",
2222
+ "minimum": -9007199254740991,
2223
+ "maximum": 9007199254740991,
2224
+ "readOnly": true,
2225
+ "description": "(Read-only) Number of purchases in the current calendar month (UTC)."
2226
+ },
2227
+ "todayBookingsCount": {
2228
+ "type": "integer",
2229
+ "minimum": -9007199254740991,
2230
+ "maximum": 9007199254740991,
2231
+ "readOnly": true,
2232
+ "description": "(Read-only) Bookings with date == today AND status in [PENDING, CONFIRMED]. Resets at midnight."
2233
+ },
2234
+ "bookingsCreatedToday": {
2235
+ "type": "integer",
2236
+ "minimum": -9007199254740991,
2237
+ "maximum": 9007199254740991,
2238
+ "readOnly": true,
2239
+ "description": "(Read-only) Bookings with createdAt today (all statuses). Resets at midnight."
2240
+ },
2241
+ "todayBookingsConfirmedAmount": {
2242
+ "type": "number",
2243
+ "readOnly": true,
2244
+ "description": "(Read-only) Sum of totalAmount for bookings created today with status CONFIRMED or COMPLETED. Resets at midnight."
2245
+ },
2246
+ "monthlyBookingsConfirmedAmount": {
2247
+ "type": "number",
2248
+ "readOnly": true,
2249
+ "description": "(Read-only) Sum of totalAmount for bookings created this month with status CONFIRMED or COMPLETED."
2250
+ },
2251
+ "todayCollectedAmount": {
2252
+ "type": "number",
2253
+ "readOnly": true,
2254
+ "description": "(Read-only) Sum of totalAmount for bookings where PAYMENT_PAID_AT is today. Resets at midnight."
2255
+ },
2256
+ "todayRevenue": {
2257
+ "type": "number",
2258
+ "readOnly": true,
2259
+ "description": "(Read-only) Sum of totalAmount for COMPLETED bookings where startDate == endDate == today (numeric YYYYMMDD). Resets at midnight."
2260
+ },
2261
+ "bookingsPendingPaymentVerification": {
2262
+ "type": "integer",
2263
+ "minimum": -9007199254740991,
2264
+ "maximum": 9007199254740991,
2265
+ "readOnly": true,
2266
+ "description": "(Read-only) Bookings with status PENDING/CANCELLATION_REQUESTED and a payment proof uploaded but not yet verified. Current state, always full recalc."
2267
+ },
2268
+ "bookingsPendingValidation": {
2269
+ "type": "integer",
2270
+ "minimum": -9007199254740991,
2271
+ "maximum": 9007199254740991,
2272
+ "readOnly": true,
2273
+ "description": "(Read-only) PENDING bookings with startDate within \u00b130 days of today. Rolling window, always full recalc."
2274
+ },
2275
+ "bookingsPendingValidation24h": {
2276
+ "type": "integer",
2277
+ "minimum": -9007199254740991,
2278
+ "maximum": 9007199254740991,
2279
+ "readOnly": true,
2280
+ "description": "(Read-only) PENDING bookings created in the last 24h with startDate >= tomorrow. Always full recalc."
2281
+ },
2282
+ "customersCount": {
2283
+ "type": "integer",
2284
+ "minimum": -9007199254740991,
2285
+ "maximum": 9007199254740991,
2286
+ "readOnly": true,
2287
+ "description": "(Read-only) Total customer count (all-time cumulative)."
2288
+ },
2289
+ "newCustomersThisMonth": {
2290
+ "type": "integer",
2291
+ "minimum": -9007199254740991,
2292
+ "maximum": 9007199254740991,
2293
+ "readOnly": true,
2294
+ "description": "(Read-only) Customers with createdAt in the current calendar month (UTC)."
2295
+ },
2296
+ "averageRating": {
2297
+ "type": "number",
2298
+ "readOnly": true,
2299
+ "description": "(Read-only) Average rating from the last 200 reviews (rolling). Always full recalc."
2300
+ },
2301
+ "lowStockItemsCount": {
2302
+ "type": "integer",
2303
+ "minimum": -9007199254740991,
2304
+ "maximum": 9007199254740991,
2305
+ "readOnly": true,
2306
+ "description": "(Read-only) Active stock items where currentQuantity <= minimumQuantity. Current state, always full recalc."
2307
+ },
2308
+ "activeRecurringPaymentsCount": {
2309
+ "type": "integer",
2310
+ "minimum": -9007199254740991,
2311
+ "maximum": 9007199254740991,
2312
+ "readOnly": true,
2313
+ "description": "(Read-only) Recurring payments with status ACTIVE. Current state, always full recalc."
2314
+ },
2315
+ "monthlyRecurringRevenue": {
2316
+ "type": "number",
2317
+ "readOnly": true,
2318
+ "description": "(Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc."
2319
+ },
2320
+ "computedForDay": {
2321
+ "type": "string",
2322
+ "readOnly": true,
2323
+ "description": "(Read-only) YYYY-MM-DD string indicating which day these metrics reflect. Used to detect day boundaries and trigger midnight resets."
2324
+ },
2325
+ "generatedAt": {
2326
+ "$ref": "#/definitions/firestore-timestamp",
2327
+ "description": "(Read-only) Server timestamp of the last metrics write.",
2328
+ "readOnly": true
2329
+ },
2330
+ "date": {
2331
+ "type": "string",
2332
+ "readOnly": true,
2333
+ "description": "(Read-only) YYYY-MM-DD document ID repeated as a field. Identifies the day this snapshot covers."
2334
+ }
2335
+ },
2336
+ "required": [
2337
+ "todayOrdersCount",
2338
+ "pendingOrdersCount",
2339
+ "orderCompletionRate30d",
2340
+ "todayPurchasesCount",
2341
+ "todayPurchasesSum",
2342
+ "averagePurchaseAmount",
2343
+ "monthlyRevenue",
2344
+ "monthlyPurchasesCount",
2345
+ "todayBookingsCount",
2346
+ "bookingsCreatedToday",
2347
+ "todayBookingsConfirmedAmount",
2348
+ "monthlyBookingsConfirmedAmount",
2349
+ "todayCollectedAmount",
2350
+ "todayRevenue",
2351
+ "bookingsPendingPaymentVerification",
2352
+ "bookingsPendingValidation",
2353
+ "bookingsPendingValidation24h",
2354
+ "customersCount",
2355
+ "newCustomersThisMonth",
2356
+ "averageRating",
2357
+ "lowStockItemsCount",
2358
+ "activeRecurringPaymentsCount",
2359
+ "monthlyRecurringRevenue",
2360
+ "computedForDay",
2361
+ "generatedAt",
2362
+ "date"
2363
+ ],
2364
+ "additionalProperties": false,
2365
+ "description": "Daily metrics snapshot. Collection: companies/{companyId}/metrics_daily/{YYYY-MM-DD}. Same fields as MetricsCurrent plus `date`. Written by computeDailyCompanyMetrics cron (02:00 UTC) and inline after full recalcs.",
2366
+ "example": {
2367
+ "todayOrdersCount": 2,
2368
+ "pendingOrdersCount": 2,
2369
+ "orderCompletionRate30d": 0,
2370
+ "todayPurchasesCount": 2,
2371
+ "todayPurchasesSum": 0,
2372
+ "averagePurchaseAmount": 15000,
2373
+ "monthlyRevenue": 0,
2374
+ "monthlyPurchasesCount": 2,
2375
+ "todayBookingsCount": 2,
2376
+ "bookingsCreatedToday": 0,
2377
+ "todayBookingsConfirmedAmount": 15000,
2378
+ "monthlyBookingsConfirmedAmount": 15000,
2379
+ "todayCollectedAmount": 15000,
2380
+ "todayRevenue": 0,
2381
+ "bookingsPendingPaymentVerification": 0,
2382
+ "bookingsPendingValidation": 0,
2383
+ "bookingsPendingValidation24h": 0,
2384
+ "customersCount": 2,
2385
+ "newCustomersThisMonth": 0,
2386
+ "averageRating": 0,
2387
+ "lowStockItemsCount": 2,
2388
+ "activeRecurringPaymentsCount": 2,
2389
+ "monthlyRecurringRevenue": 0,
2390
+ "computedForDay": "computedForDay",
2391
+ "generatedAt": "generatedAt",
2392
+ "date": "2026-02-15"
2393
+ }
2394
+ },
2395
+ "metrics-monthly": {
2396
+ "type": "object",
2397
+ "properties": {
2398
+ "todayOrdersCount": {
2399
+ "type": "integer",
2400
+ "minimum": -9007199254740991,
2401
+ "maximum": 9007199254740991,
2402
+ "readOnly": true,
2403
+ "description": "(Read-only) Orders created today (UTC). Resets at midnight."
2404
+ },
2405
+ "pendingOrdersCount": {
2406
+ "type": "integer",
2407
+ "minimum": -9007199254740991,
2408
+ "maximum": 9007199254740991,
2409
+ "readOnly": true,
2410
+ "description": "(Read-only) Orders currently in PENDING status (all-time cumulative). Corrected by full recalc on order updates."
2411
+ },
2412
+ "orderCompletionRate30d": {
2413
+ "type": "number",
2414
+ "readOnly": true,
2415
+ "description": "(Read-only) Percentage of orders completed or delivered in the last 30 days. Always full recalc."
2416
+ },
2417
+ "todayPurchasesCount": {
2418
+ "type": "integer",
2419
+ "minimum": -9007199254740991,
2420
+ "maximum": 9007199254740991,
2421
+ "readOnly": true,
2422
+ "description": "(Read-only) Purchases created today (UTC). Resets at midnight."
2423
+ },
2424
+ "todayPurchasesSum": {
2425
+ "type": "number",
2426
+ "readOnly": true,
2427
+ "description": "(Read-only) Total value of purchases created today (UTC). Resets at midnight."
2428
+ },
2429
+ "averagePurchaseAmount": {
2430
+ "type": "number",
2431
+ "readOnly": true,
2432
+ "description": "(Read-only) Average purchase value across last 200 purchases (rolling, not time-windowed)."
2433
+ },
2434
+ "monthlyRevenue": {
2435
+ "type": "number",
2436
+ "readOnly": true,
2437
+ "description": "(Read-only) Total purchase value in the current calendar month (UTC)."
2438
+ },
2439
+ "monthlyPurchasesCount": {
2440
+ "type": "integer",
2441
+ "minimum": -9007199254740991,
2442
+ "maximum": 9007199254740991,
2443
+ "readOnly": true,
2444
+ "description": "(Read-only) Number of purchases in the current calendar month (UTC)."
2445
+ },
2446
+ "todayBookingsCount": {
2447
+ "type": "integer",
2448
+ "minimum": -9007199254740991,
2449
+ "maximum": 9007199254740991,
2450
+ "readOnly": true,
2451
+ "description": "(Read-only) Bookings with date == today AND status in [PENDING, CONFIRMED]. Resets at midnight."
2452
+ },
2453
+ "bookingsCreatedToday": {
2454
+ "type": "integer",
2455
+ "minimum": -9007199254740991,
2456
+ "maximum": 9007199254740991,
2457
+ "readOnly": true,
2458
+ "description": "(Read-only) Bookings with createdAt today (all statuses). Resets at midnight."
2459
+ },
2460
+ "todayBookingsConfirmedAmount": {
2461
+ "type": "number",
2462
+ "readOnly": true,
2463
+ "description": "(Read-only) Sum of totalAmount for bookings created today with status CONFIRMED or COMPLETED. Resets at midnight."
2464
+ },
2465
+ "monthlyBookingsConfirmedAmount": {
2466
+ "type": "number",
2467
+ "readOnly": true,
2468
+ "description": "(Read-only) Sum of totalAmount for bookings created this month with status CONFIRMED or COMPLETED."
2469
+ },
2470
+ "todayCollectedAmount": {
2471
+ "type": "number",
2472
+ "readOnly": true,
2473
+ "description": "(Read-only) Sum of totalAmount for bookings where PAYMENT_PAID_AT is today. Resets at midnight."
2474
+ },
2475
+ "todayRevenue": {
2476
+ "type": "number",
2477
+ "readOnly": true,
2478
+ "description": "(Read-only) Sum of totalAmount for COMPLETED bookings where startDate == endDate == today (numeric YYYYMMDD). Resets at midnight."
2479
+ },
2480
+ "bookingsPendingPaymentVerification": {
2481
+ "type": "integer",
2482
+ "minimum": -9007199254740991,
2483
+ "maximum": 9007199254740991,
2484
+ "readOnly": true,
2485
+ "description": "(Read-only) Bookings with status PENDING/CANCELLATION_REQUESTED and a payment proof uploaded but not yet verified. Current state, always full recalc."
2486
+ },
2487
+ "bookingsPendingValidation": {
2488
+ "type": "integer",
2489
+ "minimum": -9007199254740991,
2490
+ "maximum": 9007199254740991,
2491
+ "readOnly": true,
2492
+ "description": "(Read-only) PENDING bookings with startDate within \u00b130 days of today. Rolling window, always full recalc."
2493
+ },
2494
+ "bookingsPendingValidation24h": {
2495
+ "type": "integer",
2496
+ "minimum": -9007199254740991,
2497
+ "maximum": 9007199254740991,
2498
+ "readOnly": true,
2499
+ "description": "(Read-only) PENDING bookings created in the last 24h with startDate >= tomorrow. Always full recalc."
2500
+ },
2501
+ "customersCount": {
2502
+ "type": "integer",
2503
+ "minimum": -9007199254740991,
2504
+ "maximum": 9007199254740991,
2505
+ "readOnly": true,
2506
+ "description": "(Read-only) Total customer count (all-time cumulative)."
2507
+ },
2508
+ "newCustomersThisMonth": {
2509
+ "type": "integer",
2510
+ "minimum": -9007199254740991,
2511
+ "maximum": 9007199254740991,
2512
+ "readOnly": true,
2513
+ "description": "(Read-only) Customers with createdAt in the current calendar month (UTC)."
2514
+ },
2515
+ "averageRating": {
2516
+ "type": "number",
2517
+ "readOnly": true,
2518
+ "description": "(Read-only) Average rating from the last 200 reviews (rolling). Always full recalc."
2519
+ },
2520
+ "lowStockItemsCount": {
2521
+ "type": "integer",
2522
+ "minimum": -9007199254740991,
2523
+ "maximum": 9007199254740991,
2524
+ "readOnly": true,
2525
+ "description": "(Read-only) Active stock items where currentQuantity <= minimumQuantity. Current state, always full recalc."
2526
+ },
2527
+ "activeRecurringPaymentsCount": {
2528
+ "type": "integer",
2529
+ "minimum": -9007199254740991,
2530
+ "maximum": 9007199254740991,
2531
+ "readOnly": true,
2532
+ "description": "(Read-only) Recurring payments with status ACTIVE. Current state, always full recalc."
2533
+ },
2534
+ "monthlyRecurringRevenue": {
2535
+ "type": "number",
2536
+ "readOnly": true,
2537
+ "description": "(Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc."
2538
+ },
2539
+ "computedForDay": {
2540
+ "type": "string",
2541
+ "readOnly": true,
2542
+ "description": "(Read-only) YYYY-MM-DD string indicating which day these metrics reflect. Used to detect day boundaries and trigger midnight resets."
2543
+ },
2544
+ "generatedAt": {
2545
+ "$ref": "#/definitions/firestore-timestamp",
2546
+ "description": "(Read-only) Server timestamp of the last metrics write.",
2547
+ "readOnly": true
2548
+ },
2549
+ "month": {
2550
+ "type": "string",
2551
+ "readOnly": true,
2552
+ "description": "(Read-only) YYYY-MM document ID repeated as a field. Identifies the month this rollup covers."
2553
+ }
2554
+ },
2555
+ "required": [
2556
+ "todayOrdersCount",
2557
+ "pendingOrdersCount",
2558
+ "orderCompletionRate30d",
2559
+ "todayPurchasesCount",
2560
+ "todayPurchasesSum",
2561
+ "averagePurchaseAmount",
2562
+ "monthlyRevenue",
2563
+ "monthlyPurchasesCount",
2564
+ "todayBookingsCount",
2565
+ "bookingsCreatedToday",
2566
+ "todayBookingsConfirmedAmount",
2567
+ "monthlyBookingsConfirmedAmount",
2568
+ "todayCollectedAmount",
2569
+ "todayRevenue",
2570
+ "bookingsPendingPaymentVerification",
2571
+ "bookingsPendingValidation",
2572
+ "bookingsPendingValidation24h",
2573
+ "customersCount",
2574
+ "newCustomersThisMonth",
2575
+ "averageRating",
2576
+ "lowStockItemsCount",
2577
+ "activeRecurringPaymentsCount",
2578
+ "monthlyRecurringRevenue",
2579
+ "computedForDay",
2580
+ "generatedAt",
2581
+ "month"
2582
+ ],
2583
+ "additionalProperties": false,
2584
+ "description": "Monthly metrics rollup. Collection: companies/{companyId}/metrics_monthly/{YYYY-MM}. Same fields as MetricsCurrent plus `month`. Written alongside daily snapshots; reflects state at last full recalc within the month.",
2585
+ "example": {
2586
+ "todayOrdersCount": 2,
2587
+ "pendingOrdersCount": 2,
2588
+ "orderCompletionRate30d": 0,
2589
+ "todayPurchasesCount": 2,
2590
+ "todayPurchasesSum": 0,
2591
+ "averagePurchaseAmount": 15000,
2592
+ "monthlyRevenue": 0,
2593
+ "monthlyPurchasesCount": 2,
2594
+ "todayBookingsCount": 2,
2595
+ "bookingsCreatedToday": 0,
2596
+ "todayBookingsConfirmedAmount": 15000,
2597
+ "monthlyBookingsConfirmedAmount": 15000,
2598
+ "todayCollectedAmount": 15000,
2599
+ "todayRevenue": 0,
2600
+ "bookingsPendingPaymentVerification": 0,
2601
+ "bookingsPendingValidation": 0,
2602
+ "bookingsPendingValidation24h": 0,
2603
+ "customersCount": 2,
2604
+ "newCustomersThisMonth": 0,
2605
+ "averageRating": 0,
2606
+ "lowStockItemsCount": 2,
2607
+ "activeRecurringPaymentsCount": 2,
2608
+ "monthlyRecurringRevenue": 0,
2609
+ "computedForDay": "computedForDay",
2610
+ "generatedAt": "generatedAt",
2611
+ "month": "month"
2612
+ }
2613
+ },
2614
+ "order": {
2615
+ "type": "object",
2616
+ "properties": {
2617
+ "id": {
2618
+ "type": "string",
2619
+ "readOnly": true,
2620
+ "description": "(Read-only) Firestore document ID. Note: some models also have uid; see ID conventions."
2621
+ },
2622
+ "uid": {
2623
+ "type": "string",
2624
+ "readOnly": true,
2625
+ "description": "(Read-only) Entity UID. Often mirrors id."
2626
+ },
2627
+ "companyId": {
2628
+ "type": "string",
2629
+ "x-immutable": true,
2630
+ "description": "(Immutable) FK \u2192 Company document ID. Scopes all queries."
2631
+ },
2632
+ "orderNumber": {
2633
+ "type": "string",
2634
+ "readOnly": true,
2635
+ "description": "(Read-only) Server-generated order number."
2636
+ },
2637
+ "status": {
2638
+ "$ref": "#/definitions/order-status",
2639
+ "description": "Core lifecycle status (D34, MIG-11). See OrderStatus enum for the legacy value migration mapping.",
2640
+ "x-migration": {
2641
+ "source": "Dashboard flat 20-value OrderStatus",
2642
+ "migration": "MIG-11",
2643
+ "note": "REFUND_PROCESSING and REFUNDED map to paymentStatus, not returnStatus \u2014 an order can be refunded without a physical return.",
2644
+ "mapping": [
2645
+ {
2646
+ "old": "PENDING",
2647
+ "status": "PENDING"
2648
+ },
2649
+ {
2650
+ "old": "CONFIRMED",
2651
+ "status": "CONFIRMED"
2652
+ },
2653
+ {
2654
+ "old": "PROCESSING",
2655
+ "status": "PROCESSING"
2656
+ },
2657
+ {
2658
+ "old": "READY",
2659
+ "status": "READY"
2660
+ },
2661
+ {
2662
+ "old": "COMPLETED",
2663
+ "status": "COMPLETED"
2664
+ },
2665
+ {
2666
+ "old": "CANCELLED",
2667
+ "status": "CANCELLED"
2668
+ },
2669
+ {
2670
+ "old": "EXPIRED",
2671
+ "status": "EXPIRED"
2672
+ },
2673
+ {
2674
+ "old": "AWAITING_PAYMENT",
2675
+ "status": "PENDING",
2676
+ "paymentStatus": "PENDING"
2677
+ },
2678
+ {
2679
+ "old": "SHIPPED",
2680
+ "status": "PROCESSING",
2681
+ "fulfillmentStatus": "SHIPPED"
2682
+ },
2683
+ {
2684
+ "old": "PARTIALLY_SHIPPED",
2685
+ "status": "PROCESSING",
2686
+ "fulfillmentStatus": "PARTIALLY_SHIPPED"
2687
+ },
2688
+ {
2689
+ "old": "ON_THE_WAY",
2690
+ "status": "PROCESSING",
2691
+ "fulfillmentStatus": "IN_TRANSIT"
2692
+ },
2693
+ {
2694
+ "old": "DELIVERED",
2695
+ "status": "COMPLETED",
2696
+ "fulfillmentStatus": "DELIVERED"
2697
+ },
2698
+ {
2699
+ "old": "PICKED_UP",
2700
+ "status": "COMPLETED",
2701
+ "fulfillmentStatus": "PICKED_UP"
2702
+ },
2703
+ {
2704
+ "old": "RETURN_REQUESTED",
2705
+ "status": "COMPLETED",
2706
+ "returnStatus": "RETURN_REQUESTED"
2707
+ },
2708
+ {
2709
+ "old": "RETURN_PROCESSING",
2710
+ "status": "COMPLETED",
2711
+ "returnStatus": "RETURN_PROCESSING"
2712
+ },
2713
+ {
2714
+ "old": "RETURNED",
2715
+ "status": "COMPLETED",
2716
+ "returnStatus": "RETURNED"
2717
+ },
2718
+ {
2719
+ "old": "EXCHANGE_REQUESTED",
2720
+ "status": "COMPLETED",
2721
+ "returnStatus": "EXCHANGE_REQUESTED"
2722
+ },
2723
+ {
2724
+ "old": "EXCHANGE_PROCESSING",
2725
+ "status": "COMPLETED",
2726
+ "returnStatus": "EXCHANGE_PROCESSING"
2727
+ },
2728
+ {
2729
+ "old": "EXCHANGE_COMPLETED",
2730
+ "status": "COMPLETED",
2731
+ "returnStatus": "EXCHANGE_COMPLETED"
2732
+ },
2733
+ {
2734
+ "old": "REFUND_PROCESSING",
2735
+ "status": "COMPLETED",
2736
+ "paymentStatus": "REFUND_PROCESSING"
2737
+ },
2738
+ {
2739
+ "old": "REFUNDED",
2740
+ "status": "COMPLETED",
2741
+ "paymentStatus": "REFUNDED"
2742
+ }
2743
+ ]
2744
+ }
2745
+ },
2746
+ "paymentStatus": {
2747
+ "anyOf": [
2748
+ {
2749
+ "$ref": "#/definitions/payment-status"
2750
+ },
2751
+ {
2752
+ "type": "null"
2753
+ }
2754
+ ],
2755
+ "description": "Payment lifecycle (D34). Null until payment is initiated."
2756
+ },
2757
+ "fulfillmentStatus": {
2758
+ "anyOf": [
2759
+ {
2760
+ "$ref": "#/definitions/fulfillment-status"
2761
+ },
2762
+ {
2763
+ "type": "null"
2764
+ }
2765
+ ],
2766
+ "x-when": "Null for ON_SITE and PICK_UP orders. Set when deliveryType is DELIVERY and a physical shipment or pickup is involved.",
2767
+ "x-see": {
2768
+ "decisions": [
2769
+ "D34"
2770
+ ]
2771
+ },
2772
+ "description": "Delivery/fulfillment lifecycle (D34)."
2773
+ },
2774
+ "returnStatus": {
2775
+ "anyOf": [
2776
+ {
2777
+ "$ref": "#/definitions/return-status"
2778
+ },
2779
+ {
2780
+ "type": "null"
2781
+ }
2782
+ ],
2783
+ "description": "Return/exchange lifecycle (D34). Null until a return or exchange is initiated."
2784
+ },
2785
+ "deliveryType": {
2786
+ "anyOf": [
2787
+ {
2788
+ "$ref": "#/definitions/delivery-type"
2789
+ },
2790
+ {
2791
+ "type": "null"
2792
+ }
2793
+ ],
2794
+ "x-note": "Drives whether fulfillmentStatus is relevant. ON_SITE and PICK_UP orders typically have no fulfillmentStatus.",
2795
+ "description": "Fulfillment channel for this order (ON_SITE, PICK_UP, DELIVERY)."
2796
+ },
2797
+ "paymentMethod": {
2798
+ "anyOf": [
2799
+ {
2800
+ "$ref": "#/definitions/payment-method"
2801
+ },
2802
+ {
2803
+ "type": "null"
2804
+ }
2805
+ ],
2806
+ "description": "Unified payment method set with African + global methods (D02)."
2807
+ },
2808
+ "invoiceId": {
2809
+ "description": "FK \u2192 Invoice document ID.",
2810
+ "type": [
2811
+ "string",
2812
+ "null"
2813
+ ]
2814
+ },
2815
+ "customerId": {
2816
+ "description": "FK \u2192 Customer.id (Firestore doc ID). Used to resolve customer details.",
2817
+ "type": [
2818
+ "string",
2819
+ "null"
2820
+ ]
2821
+ },
2822
+ "customerName": {
2823
+ "denormalized": true,
2824
+ "description": "(Denormalized) From Customer.name at write time.",
2825
+ "type": [
2826
+ "string",
2827
+ "null"
2828
+ ]
2829
+ },
2830
+ "customerEmail": {
2831
+ "denormalized": true,
2832
+ "description": "(Denormalized) From Customer.email at write time. Canonical field per D24.",
2833
+ "type": [
2834
+ "string",
2835
+ "null"
2836
+ ]
2837
+ },
2838
+ "customerPhone": {
2839
+ "denormalized": true,
2840
+ "description": "(Denormalized) From Customer.phone at write time. Canonical field per D24.",
2841
+ "type": [
2842
+ "string",
2843
+ "null"
2844
+ ]
2845
+ },
2846
+ "clientEmail": {
2847
+ "denormalized": true,
2848
+ "deprecated": true,
2849
+ "x-replaced-by": "customerEmail",
2850
+ "description": "(Denormalized) Legacy \u2014 use `customerEmail`. D24 standardized to customer* prefix.",
2851
+ "type": [
2852
+ "string",
2853
+ "null"
2854
+ ]
2855
+ },
2856
+ "clientPhoneNumber": {
2857
+ "denormalized": true,
2858
+ "deprecated": true,
2859
+ "x-replaced-by": "customerPhone",
2860
+ "description": "(Denormalized) Legacy \u2014 use `customerPhone`. D24 standardized to customer* prefix.",
2861
+ "type": [
2862
+ "string",
2863
+ "null"
2864
+ ]
2865
+ },
2866
+ "items": {
2867
+ "type": [
2868
+ "array",
2869
+ "null"
2870
+ ],
2871
+ "items": {
2872
+ "$ref": "#/definitions/order-item",
2873
+ "description": "Line item within an Order or Sale/Purchase. Canonical fields: name, quantity, price, productId. Optional commerce fields (increment, variantId, supplierId, supplierName) and kitchen tracking fields (sentAt, startedCookingAt, readyAt, servedAt) are TBD/WIP pending cross-platform alignment."
2874
+ }
2875
+ },
2876
+ "amount": {
2877
+ "type": "number",
2878
+ "description": "Total order amount. Canonical field for the order total."
2879
+ },
2880
+ "amountPaid": {
2881
+ "description": "Amount of `amount` paid to date. Derived from payment allocations.",
2882
+ "type": [
2883
+ "number",
2884
+ "null"
2885
+ ]
2886
+ },
2887
+ "total": {
2888
+ "deprecated": true,
2889
+ "x-replaced-by": "amount",
2890
+ "x-note": "Sent by Mobile only. Mirrors `amount`. Use `amount` for all new writes. Pending deduplication cleanup.",
2891
+ "description": "Mobile-only legacy total field. Deprecated \u2014 use `amount`.",
2892
+ "type": [
2893
+ "number",
2894
+ "null"
2895
+ ]
2896
+ },
2897
+ "createdAt": {
2898
+ "$ref": "#/definitions/firestore-timestamp",
2899
+ "description": "(Read-only) Server-generated creation timestamp.",
2900
+ "readOnly": true
2901
+ },
2902
+ "orderDate": {
2903
+ "$ref": "#/definitions/firestore-timestamp",
2904
+ "description": "Firestore Timestamp serialized representation"
2905
+ },
2906
+ "PROCESSING_ON": {
2907
+ "anyOf": [
2908
+ {
2909
+ "$ref": "#/definitions/firestore-timestamp"
2910
+ },
2911
+ {
2912
+ "type": "null"
2913
+ }
2914
+ ],
2915
+ "readOnly": true,
2916
+ "x-note": "Never write this field directly. The server sets it automatically on status transition to PROCESSING.",
2917
+ "x-see": {
2918
+ "decisions": [
2919
+ "D04"
2920
+ ],
2921
+ "migrations": [
2922
+ "MIG-01"
2923
+ ]
2924
+ },
2925
+ "description": "(Read-only) Timestamp when order entered PROCESSING (D04 SCREAMING_SNAKE). MIG-01 renames from camelCase."
2926
+ },
2927
+ "COMPLETED_ON": {
2928
+ "anyOf": [
2929
+ {
2930
+ "$ref": "#/definitions/firestore-timestamp"
2931
+ },
2932
+ {
2933
+ "type": "null"
2934
+ }
2935
+ ],
2936
+ "readOnly": true,
2937
+ "description": "(Read-only) Timestamp when order completed (D04 SCREAMING_SNAKE). MIG-01 renames from camelCase."
2938
+ },
2939
+ "CANCELLED_ON": {
2940
+ "anyOf": [
2941
+ {
2942
+ "$ref": "#/definitions/firestore-timestamp"
2943
+ },
2944
+ {
2945
+ "type": "null"
2946
+ }
2947
+ ],
2948
+ "readOnly": true,
2949
+ "description": "(Read-only) Timestamp when order cancelled (D04 SCREAMING_SNAKE). MIG-01 renames from camelCase."
2950
+ },
2951
+ "cancellationReason": {
2952
+ "type": [
2953
+ "string",
2954
+ "null"
2955
+ ]
2956
+ },
2957
+ "shippingCarrier": {
2958
+ "type": [
2959
+ "string",
2960
+ "null"
2961
+ ]
2962
+ },
2963
+ "trackingNumber": {
2964
+ "type": [
2965
+ "string",
2966
+ "null"
2967
+ ]
2968
+ },
2969
+ "estimatedDeliveryDate": {
2970
+ "anyOf": [
2971
+ {
2972
+ "$ref": "#/definitions/firestore-timestamp"
2973
+ },
2974
+ {
2975
+ "type": "null"
2976
+ }
2977
+ ],
2978
+ "description": "Firestore Timestamp serialized representation"
2979
+ },
2980
+ "shippingCost": {
2981
+ "type": [
2982
+ "number",
2983
+ "null"
2984
+ ]
2985
+ },
2986
+ "paymentProofUrl": {
2987
+ "description": "URL to uploaded payment proof image/document.",
2988
+ "type": [
2989
+ "string",
2990
+ "null"
2991
+ ]
2992
+ },
2993
+ "paymentProofStatus": {
2994
+ "anyOf": [
2995
+ {
2996
+ "$ref": "#/definitions/payment-proof-status"
2997
+ },
2998
+ {
2999
+ "type": "null"
3000
+ }
3001
+ ],
3002
+ "description": "Payment proof review status."
3003
+ },
3004
+ "paymentProofAddedAt": {
3005
+ "anyOf": [
3006
+ {
3007
+ "$ref": "#/definitions/firestore-timestamp"
3008
+ },
3009
+ {
3010
+ "type": "null"
3011
+ }
3012
+ ],
3013
+ "readOnly": true,
3014
+ "description": "(Read-only) Timestamp when proof was uploaded."
3015
+ },
3016
+ "paymentProofAddedBy": {
3017
+ "description": "FK \u2192 User/staff UID who uploaded the payment proof.",
3018
+ "type": [
3019
+ "string",
3020
+ "null"
3021
+ ]
3022
+ },
3023
+ "paymentProofReviewedAt": {
3024
+ "anyOf": [
3025
+ {
3026
+ "$ref": "#/definitions/firestore-timestamp"
3027
+ },
3028
+ {
3029
+ "type": "null"
3030
+ }
3031
+ ],
3032
+ "readOnly": true,
3033
+ "description": "(Read-only) Timestamp when proof was reviewed."
3034
+ },
3035
+ "paymentProofReviewedBy": {
3036
+ "description": "FK \u2192 User/staff UID who reviewed the payment proof.",
3037
+ "type": [
3038
+ "string",
3039
+ "null"
3040
+ ]
3041
+ },
3042
+ "paymentProofRejectionReason": {
3043
+ "type": [
3044
+ "string",
3045
+ "null"
3046
+ ]
3047
+ },
3048
+ "paymentStatusChangeReason": {
3049
+ "type": [
3050
+ "string",
3051
+ "null"
3052
+ ]
3053
+ },
3054
+ "paymentStatusChangedBy": {
3055
+ "description": "FK \u2192 User/staff UID who changed payment status.",
3056
+ "type": [
3057
+ "string",
3058
+ "null"
3059
+ ]
3060
+ },
3061
+ "paymentStatusChangedAt": {
3062
+ "anyOf": [
3063
+ {
3064
+ "$ref": "#/definitions/firestore-timestamp"
3065
+ },
3066
+ {
3067
+ "type": "null"
3068
+ }
3069
+ ],
3070
+ "readOnly": true,
3071
+ "description": "(Read-only) Timestamp of last payment status change."
3072
+ },
3073
+ "payments": {
3074
+ "x-note": "Shape and sync rules are TBD pending IG-4 resolution. Do not build production logic against this field until IG-4 is closed.",
3075
+ "x-see": {
3076
+ "issues": [
3077
+ "IG-4"
3078
+ ]
3079
+ },
3080
+ "description": "[TBD/WIP \u2014 IG-4] Denormalized snapshots of CustomerPayments allocated to this order. Sync rules pending IG-4 resolution.",
3081
+ "type": [
3082
+ "array",
3083
+ "null"
3084
+ ],
3085
+ "items": {
3086
+ "type": "object",
3087
+ "properties": {
3088
+ "paymentId": {
3089
+ "type": "string",
3090
+ "x-immutable": true,
3091
+ "description": "(Immutable) FK \u2192 CustomerPayment.id. Source document for this snapshot."
3092
+ },
3093
+ "amount": {
3094
+ "type": "number",
3095
+ "description": "Total payment amount."
3096
+ },
3097
+ "currency": {
3098
+ "type": "string",
3099
+ "const": "XOF",
3100
+ "description": "Currency code. Locked to XOF."
3101
+ },
3102
+ "paymentMethod": {
3103
+ "$ref": "#/definitions/payment-method",
3104
+ "description": "Payment method used (D02)."
3105
+ },
3106
+ "paymentDate": {
3107
+ "$ref": "#/definitions/firestore-timestamp",
3108
+ "description": "Firestore Timestamp serialized representation"
3109
+ },
3110
+ "referenceNumber": {
3111
+ "description": "Payment reference (receipt number, transaction ID, etc.).",
3112
+ "type": "string"
3113
+ },
3114
+ "status": {
3115
+ "$ref": "#/definitions/customer-payment-status",
3116
+ "description": "Payment status at time of snapshot. May lag CustomerPayment.status until re-synced."
3117
+ }
3118
+ },
3119
+ "required": [
3120
+ "paymentId",
3121
+ "amount",
3122
+ "currency",
3123
+ "paymentMethod",
3124
+ "paymentDate",
3125
+ "status"
3126
+ ],
3127
+ "additionalProperties": false,
3128
+ "description": "[TBD/WIP \u2014 IG-4] Denormalized payment snapshot. Subset of CustomerPayment (D22), written when a payment is allocated to this order. Sync rules pending IG-4 resolution."
3129
+ }
3130
+ },
3131
+ "totalOverridden": {
3132
+ "x-when": "Set only by the mobile app when the user manually edits the order total. Dashboard and Cloud Functions should treat this field as read-only.",
3133
+ "x-see": {
3134
+ "decisions": [
3135
+ "D14"
3136
+ ]
3137
+ },
3138
+ "description": "Mobile-only. When true, total was manually overridden by user (D14).",
3139
+ "type": [
3140
+ "boolean",
3141
+ "null"
3142
+ ]
3143
+ },
3144
+ "notes": {
3145
+ "type": [
3146
+ "array",
3147
+ "null"
3148
+ ],
3149
+ "items": {
3150
+ "type": "object",
3151
+ "properties": {
3152
+ "id": {
3153
+ "type": "string",
3154
+ "readOnly": true,
3155
+ "description": "(Read-only) Note ID. Server-generated."
3156
+ },
3157
+ "text": {
3158
+ "type": "string"
3159
+ },
3160
+ "createdAt": {
3161
+ "$ref": "#/definitions/firestore-timestamp",
3162
+ "description": "(Read-only) Firestore Timestamp serialized representation.",
3163
+ "readOnly": true
3164
+ },
3165
+ "createdBy": {
3166
+ "x-immutable": true,
3167
+ "description": "(Immutable) FK \u2192 User/staff UID who created this note.",
3168
+ "type": "string"
3169
+ },
3170
+ "createdByName": {
3171
+ "denormalized": true,
3172
+ "description": "(Denormalized) From User display name at write time.",
3173
+ "type": "string"
3174
+ }
3175
+ },
3176
+ "required": [
3177
+ "id",
3178
+ "text",
3179
+ "createdAt"
3180
+ ],
3181
+ "additionalProperties": false,
3182
+ "description": "Timestamped note attached to an Order."
3183
+ }
3184
+ },
3185
+ "additionalInfo": {
3186
+ "type": [
3187
+ "string",
3188
+ "null"
3189
+ ]
3190
+ },
3191
+ "appliedDiscountCode": {
3192
+ "type": [
3193
+ "string",
3194
+ "null"
3195
+ ]
3196
+ },
3197
+ "purchaseId": {
3198
+ "description": "FK \u2192 Sale.id. Link to associated Sale document.",
3199
+ "type": [
3200
+ "string",
3201
+ "null"
3202
+ ]
3203
+ }
3204
+ },
3205
+ "required": [
3206
+ "id",
3207
+ "uid",
3208
+ "companyId",
3209
+ "orderNumber",
3210
+ "status",
3211
+ "amount",
3212
+ "createdAt",
3213
+ "orderDate"
3214
+ ],
3215
+ "additionalProperties": false,
3216
+ "description": "Order model (D34). Collection: companies/{companyId}/orders/{orderId}. Status decomposed into 4 orthogonal fields per D34: status (OrderStatus), paymentStatus, fulfillmentStatus, returnStatus. deliveryType determines the fulfillment channel. MIG-11 migrates the old flat 20-value enum.",
3217
+ "example": {
3218
+ "id": "bk_abc123def456",
3219
+ "uid": "user_u8x92kqm",
3220
+ "companyId": "comp_xyz789",
3221
+ "orderNumber": "ORD-2026-0042",
3222
+ "status": "status",
3223
+ "paymentStatus": "paymentStatus",
3224
+ "fulfillmentStatus": "fulfillmentStatus",
3225
+ "returnStatus": "returnStatus",
3226
+ "deliveryType": "deliveryType",
3227
+ "paymentMethod": "paymentMethod",
3228
+ "invoiceId": null,
3229
+ "customerId": null,
3230
+ "customerName": null,
3231
+ "customerEmail": null,
3232
+ "customerPhone": null,
3233
+ "clientEmail": null,
3234
+ "clientPhoneNumber": null,
3235
+ "items": null,
3236
+ "amount": 45000,
3237
+ "amountPaid": null,
3238
+ "total": null,
3239
+ "createdAt": "createdAt",
3240
+ "orderDate": "orderDate",
3241
+ "PROCESSING_ON": "PROCESSING_ON",
3242
+ "COMPLETED_ON": "COMPLETED_ON",
3243
+ "CANCELLED_ON": "CANCELLED_ON",
3244
+ "cancellationReason": null,
3245
+ "shippingCarrier": null,
3246
+ "trackingNumber": null,
3247
+ "estimatedDeliveryDate": "estimatedDeliveryDate",
3248
+ "shippingCost": null,
3249
+ "paymentProofUrl": null,
3250
+ "paymentProofStatus": "paymentProofStatus",
3251
+ "paymentProofAddedAt": "paymentProofAddedAt",
3252
+ "paymentProofAddedBy": null,
3253
+ "paymentProofReviewedAt": "paymentProofReviewedAt",
3254
+ "paymentProofReviewedBy": null,
3255
+ "paymentProofRejectionReason": null,
3256
+ "paymentStatusChangeReason": null,
3257
+ "paymentStatusChangedBy": null,
3258
+ "paymentStatusChangedAt": "paymentStatusChangedAt",
3259
+ "payments": null,
3260
+ "totalOverridden": null,
3261
+ "notes": null,
3262
+ "additionalInfo": null,
3263
+ "appliedDiscountCode": null,
3264
+ "purchaseId": null
3265
+ }
3266
+ },
3267
+ "order-item": {
3268
+ "type": "object",
3269
+ "properties": {
3270
+ "name": {
3271
+ "type": "string",
3272
+ "description": "Display name of the product or service."
3273
+ },
3274
+ "quantity": {
3275
+ "type": "number",
3276
+ "description": "Number of units ordered."
3277
+ },
3278
+ "price": {
3279
+ "type": "number",
3280
+ "description": "Unit price. Canonical name per D11."
3281
+ },
3282
+ "productId": {
3283
+ "description": "FK \u2192 Product document ID.",
3284
+ "type": [
3285
+ "string",
3286
+ "null"
3287
+ ]
3288
+ },
3289
+ "increment": {
3290
+ "x-note": "TBD/WIP \u2014 Dashboard/Firebase only. Canonical status pending cross-platform alignment.",
3291
+ "description": "Quantity step for bundle/bulk items (e.g. 5 for a 5-pack). Dashboard/Firebase only.",
3292
+ "type": [
3293
+ "number",
3294
+ "null"
3295
+ ]
3296
+ },
3297
+ "variantId": {
3298
+ "x-note": "TBD/WIP \u2014 Dashboard/Firebase only. Canonical status pending cross-platform alignment.",
3299
+ "description": "FK \u2192 ProductVariant ID within the product. Dashboard/Firebase only.",
3300
+ "type": [
3301
+ "string",
3302
+ "null"
3303
+ ]
3304
+ },
3305
+ "supplierId": {
3306
+ "x-note": "TBD/WIP \u2014 B2B orders only. Canonical status pending cross-platform alignment.",
3307
+ "description": "FK \u2192 Supplier document ID. B2B orders only.",
3308
+ "type": [
3309
+ "string",
3310
+ "null"
3311
+ ]
3312
+ },
3313
+ "supplierName": {
3314
+ "denormalized": true,
3315
+ "x-note": "TBD/WIP \u2014 B2B orders only. Canonical status pending cross-platform alignment.",
3316
+ "description": "(Denormalized) From Supplier.name at write time. B2B orders only.",
3317
+ "type": [
3318
+ "string",
3319
+ "null"
3320
+ ]
3321
+ },
3322
+ "sentAt": {
3323
+ "anyOf": [
3324
+ {
3325
+ "$ref": "#/definitions/firestore-timestamp"
3326
+ },
3327
+ {
3328
+ "type": "null"
3329
+ }
3330
+ ],
3331
+ "readOnly": true,
3332
+ "x-note": "TBD/WIP \u2014 Originally from the archived Couchbase Lite (CBL) restaurant flow. Canonical inclusion in Firestore model is pending review.",
3333
+ "description": "(Read-only) Timestamp when the item was sent to the kitchen. TBD/WIP \u2014 archived CBL flow."
3334
+ },
3335
+ "startedCookingAt": {
3336
+ "anyOf": [
3337
+ {
3338
+ "$ref": "#/definitions/firestore-timestamp"
3339
+ },
3340
+ {
3341
+ "type": "null"
3342
+ }
3343
+ ],
3344
+ "readOnly": true,
3345
+ "x-note": "TBD/WIP \u2014 Originally from the archived Couchbase Lite (CBL) restaurant flow. Canonical inclusion in Firestore model is pending review.",
3346
+ "description": "(Read-only) Timestamp when the kitchen started preparing this item. TBD/WIP \u2014 archived CBL flow."
3347
+ },
3348
+ "readyAt": {
3349
+ "anyOf": [
3350
+ {
3351
+ "$ref": "#/definitions/firestore-timestamp"
3352
+ },
3353
+ {
3354
+ "type": "null"
3355
+ }
3356
+ ],
3357
+ "readOnly": true,
3358
+ "x-note": "TBD/WIP \u2014 Originally from the archived Couchbase Lite (CBL) restaurant flow. Canonical inclusion in Firestore model is pending review.",
3359
+ "description": "(Read-only) Timestamp when the item was ready for pickup or service. TBD/WIP \u2014 archived CBL flow."
3360
+ },
3361
+ "servedAt": {
3362
+ "anyOf": [
3363
+ {
3364
+ "$ref": "#/definitions/firestore-timestamp"
3365
+ },
3366
+ {
3367
+ "type": "null"
3368
+ }
3369
+ ],
3370
+ "readOnly": true,
3371
+ "x-note": "TBD/WIP \u2014 Originally from the archived Couchbase Lite (CBL) restaurant flow. Canonical inclusion in Firestore model is pending review.",
3372
+ "description": "(Read-only) Timestamp when the item was served to the customer. TBD/WIP \u2014 archived CBL flow."
3373
+ }
3374
+ },
3375
+ "required": [
3376
+ "name",
3377
+ "quantity",
3378
+ "price"
3379
+ ],
3380
+ "additionalProperties": false,
3381
+ "description": "Line item within an Order or Sale/Purchase. Canonical fields: name, quantity, price, productId. Optional commerce fields (increment, variantId, supplierId, supplierName) and kitchen tracking fields (sentAt, startedCookingAt, readyAt, servedAt) are TBD/WIP pending cross-platform alignment.",
3382
+ "example": {
3383
+ "name": "Shea Butter Body Lotion",
3384
+ "quantity": 3,
3385
+ "price": 15000,
3386
+ "productId": null,
3387
+ "increment": null,
3388
+ "variantId": null,
3389
+ "supplierId": null,
3390
+ "supplierName": null,
3391
+ "sentAt": "sentAt",
3392
+ "startedCookingAt": "startedCookingAt",
3393
+ "readyAt": "readyAt",
3394
+ "servedAt": "servedAt"
3395
+ }
3396
+ },
3397
+ "order-status": {
3398
+ "type": "string",
3399
+ "enum": [
3400
+ "PENDING",
3401
+ "CONFIRMED",
3402
+ "PROCESSING",
3403
+ "READY",
3404
+ "COMPLETED",
3405
+ "CANCELLED",
3406
+ "EXPIRED"
3407
+ ],
3408
+ "description": "Core order lifecycle status (D03, D34). Universal across all business types. Replaces the Dashboard's legacy 20-value flat enum (MIG-11).",
3409
+ "x-migration": {
3410
+ "source": "Dashboard flat 20-value OrderStatus",
3411
+ "migration": "MIG-11",
3412
+ "note": "REFUND_PROCESSING and REFUNDED map to paymentStatus, not returnStatus \u2014 an order can be refunded without a physical return.",
3413
+ "mapping": [
3414
+ {
3415
+ "old": "PENDING",
3416
+ "status": "PENDING"
3417
+ },
3418
+ {
3419
+ "old": "CONFIRMED",
3420
+ "status": "CONFIRMED"
3421
+ },
3422
+ {
3423
+ "old": "PROCESSING",
3424
+ "status": "PROCESSING"
3425
+ },
3426
+ {
3427
+ "old": "READY",
3428
+ "status": "READY"
3429
+ },
3430
+ {
3431
+ "old": "COMPLETED",
3432
+ "status": "COMPLETED"
3433
+ },
3434
+ {
3435
+ "old": "CANCELLED",
3436
+ "status": "CANCELLED"
3437
+ },
3438
+ {
3439
+ "old": "EXPIRED",
3440
+ "status": "EXPIRED"
3441
+ },
3442
+ {
3443
+ "old": "AWAITING_PAYMENT",
3444
+ "status": "PENDING",
3445
+ "paymentStatus": "PENDING"
3446
+ },
3447
+ {
3448
+ "old": "SHIPPED",
3449
+ "status": "PROCESSING",
3450
+ "fulfillmentStatus": "SHIPPED"
3451
+ },
3452
+ {
3453
+ "old": "PARTIALLY_SHIPPED",
3454
+ "status": "PROCESSING",
3455
+ "fulfillmentStatus": "PARTIALLY_SHIPPED"
3456
+ },
3457
+ {
3458
+ "old": "ON_THE_WAY",
3459
+ "status": "PROCESSING",
3460
+ "fulfillmentStatus": "IN_TRANSIT"
3461
+ },
3462
+ {
3463
+ "old": "DELIVERED",
3464
+ "status": "COMPLETED",
3465
+ "fulfillmentStatus": "DELIVERED"
3466
+ },
3467
+ {
3468
+ "old": "PICKED_UP",
3469
+ "status": "COMPLETED",
3470
+ "fulfillmentStatus": "PICKED_UP"
3471
+ },
3472
+ {
3473
+ "old": "RETURN_REQUESTED",
3474
+ "status": "COMPLETED",
3475
+ "returnStatus": "RETURN_REQUESTED"
3476
+ },
3477
+ {
3478
+ "old": "RETURN_PROCESSING",
3479
+ "status": "COMPLETED",
3480
+ "returnStatus": "RETURN_PROCESSING"
3481
+ },
3482
+ {
3483
+ "old": "RETURNED",
3484
+ "status": "COMPLETED",
3485
+ "returnStatus": "RETURNED"
3486
+ },
3487
+ {
3488
+ "old": "EXCHANGE_REQUESTED",
3489
+ "status": "COMPLETED",
3490
+ "returnStatus": "EXCHANGE_REQUESTED"
3491
+ },
3492
+ {
3493
+ "old": "EXCHANGE_PROCESSING",
3494
+ "status": "COMPLETED",
3495
+ "returnStatus": "EXCHANGE_PROCESSING"
3496
+ },
3497
+ {
3498
+ "old": "EXCHANGE_COMPLETED",
3499
+ "status": "COMPLETED",
3500
+ "returnStatus": "EXCHANGE_COMPLETED"
3501
+ },
3502
+ {
3503
+ "old": "REFUND_PROCESSING",
3504
+ "status": "COMPLETED",
3505
+ "paymentStatus": "REFUND_PROCESSING"
3506
+ },
3507
+ {
3508
+ "old": "REFUNDED",
3509
+ "status": "COMPLETED",
3510
+ "paymentStatus": "REFUNDED"
3511
+ }
3512
+ ]
3513
+ }
3514
+ },
3515
+ "payment-method": {
3516
+ "type": "string",
3517
+ "enum": [
3518
+ "CASH",
3519
+ "CREDIT_CARD",
3520
+ "ORANGE_MONEY",
3521
+ "WAVE",
3522
+ "MTN_MONEY",
3523
+ "MOOV_MONEY",
3524
+ "BANK_TRANSFER",
3525
+ "PAYPAL",
3526
+ "STRIPE",
3527
+ "OTHER"
3528
+ ],
3529
+ "description": "Unified payment method set with African + global methods (D02)."
3530
+ },
3531
+ "payment-proof-status": {
3532
+ "type": "string",
3533
+ "enum": [
3534
+ "PENDING",
3535
+ "APPROVED",
3536
+ "REJECTED"
3537
+ ],
3538
+ "description": "Payment proof review status. Used by Order and Booking payment proof workflows."
3539
+ },
3540
+ "payment-status": {
3541
+ "type": "string",
3542
+ "enum": [
3543
+ "PENDING",
3544
+ "PAID",
3545
+ "PARTIALLY_PAID",
3546
+ "FAILED",
3547
+ "REFUND_PROCESSING",
3548
+ "REFUNDED",
3549
+ "PARTIALLY_REFUNDED"
3550
+ ],
3551
+ "description": "Payment lifecycle status (D01 amended). Used by Order, Sale/Purchase, Booking."
3552
+ },
3553
+ "return-status": {
3554
+ "type": "string",
3555
+ "enum": [
3556
+ "RETURN_REQUESTED",
3557
+ "RETURN_PROCESSING",
3558
+ "RETURNED",
3559
+ "EXCHANGE_REQUESTED",
3560
+ "EXCHANGE_PROCESSING",
3561
+ "EXCHANGE_COMPLETED"
3562
+ ],
3563
+ "description": "Post-sale return/exchange lifecycle (D34). Optional \u2014 null until return or exchange initiated."
3564
+ },
3565
+ "sale": {
3566
+ "type": "object",
3567
+ "properties": {
3568
+ "id": {
3569
+ "readOnly": true,
3570
+ "description": "(Read-only) Firestore document ID. Note: optional in current schema \u2014 some legacy docs may lack this field.",
3571
+ "type": [
3572
+ "string",
3573
+ "null"
3574
+ ]
3575
+ },
3576
+ "companyId": {
3577
+ "x-immutable": true,
3578
+ "description": "(Immutable) FK \u2192 Company document ID. Note: optional in current schema \u2014 should be required (see ID consistency audit).",
3579
+ "type": [
3580
+ "string",
3581
+ "null"
3582
+ ]
3583
+ },
3584
+ "customerId": {
3585
+ "description": "FK \u2192 Customer.id (Firestore doc ID).",
3586
+ "type": [
3587
+ "string",
3588
+ "null"
3589
+ ]
3590
+ },
3591
+ "customerName": {
3592
+ "denormalized": true,
3593
+ "description": "(Denormalized) From Customer.name at write time.",
3594
+ "type": [
3595
+ "string",
3596
+ "null"
3597
+ ]
3598
+ },
3599
+ "amount": {
3600
+ "type": [
3601
+ "number",
3602
+ "null"
3603
+ ]
3604
+ },
3605
+ "items": {
3606
+ "description": "Line items. Reuses Order item schema.",
3607
+ "type": [
3608
+ "array",
3609
+ "null"
3610
+ ],
3611
+ "items": {
3612
+ "$ref": "#/definitions/order-item",
3613
+ "description": "Line item within an Order or Sale/Purchase. Canonical fields: name, quantity, price, productId. Optional commerce fields (increment, variantId, supplierId, supplierName) and kitchen tracking fields (sentAt, startedCookingAt, readyAt, servedAt) are TBD/WIP pending cross-platform alignment."
3614
+ }
3615
+ },
3616
+ "imageUrl": {
3617
+ "type": [
3618
+ "string",
3619
+ "null"
3620
+ ]
3621
+ },
3622
+ "notes": {
3623
+ "type": [
3624
+ "string",
3625
+ "null"
3626
+ ]
3627
+ },
3628
+ "orderId": {
3629
+ "description": "FK \u2192 Order.id. Link to associated Order document.",
3630
+ "type": [
3631
+ "string",
3632
+ "null"
3633
+ ]
3634
+ },
3635
+ "bookingId": {
3636
+ "description": "FK \u2192 Booking.id. Link to associated Booking document (IG-12).",
3637
+ "type": [
3638
+ "string",
3639
+ "null"
3640
+ ]
3641
+ },
3642
+ "purchaseDate": {
3643
+ "$ref": "#/definitions/firestore-timestamp",
3644
+ "description": "Firestore Timestamp serialized representation"
3645
+ },
3646
+ "createdAt": {
3647
+ "anyOf": [
3648
+ {
3649
+ "$ref": "#/definitions/firestore-timestamp"
3650
+ },
3651
+ {
3652
+ "type": "null"
3653
+ }
3654
+ ],
3655
+ "readOnly": true,
3656
+ "description": "(Read-only) Server-generated creation timestamp."
3657
+ }
3658
+ },
3659
+ "required": [
3660
+ "purchaseDate"
3661
+ ],
3662
+ "additionalProperties": false,
3663
+ "description": "Sale/Purchase model (D06). Collection: companies/{companyId}/purchases/{purchaseId}. Code alias: SalesService. [Deprecated path: customers/{custId}/purchases/{purchaseId} \u2014 migrate to company-wide path per MIG-04/IG-5]",
3664
+ "example": {
3665
+ "id": null,
3666
+ "companyId": null,
3667
+ "customerId": null,
3668
+ "customerName": null,
3669
+ "amount": null,
3670
+ "items": null,
3671
+ "imageUrl": null,
3672
+ "notes": null,
3673
+ "orderId": null,
3674
+ "bookingId": null,
3675
+ "purchaseDate": "purchaseDate",
3676
+ "createdAt": "createdAt"
3677
+ }
3678
+ },
3679
+ "session-status": {
3680
+ "type": "string",
3681
+ "enum": [
3682
+ "PENDING",
3683
+ "CONFIRMED",
3684
+ "CANCELLATION_REQUESTED",
3685
+ "COMPLETED",
3686
+ "NO_SHOW",
3687
+ "CANCELLED"
3688
+ ],
3689
+ "description": "Per-date/per-slot booking session status (D19). Dashboard is sole writer; Mobile is read-only."
3690
+ },
3691
+ "ticket": {
3692
+ "type": "object",
3693
+ "properties": {
3694
+ "id": {
3695
+ "type": "string",
3696
+ "readOnly": true,
3697
+ "description": "(Read-only) Firestore document ID. Note: Ticket does not have a uid field."
3698
+ },
3699
+ "eventId": {
3700
+ "type": "string",
3701
+ "x-immutable": true,
3702
+ "description": "(Immutable) FK \u2192 Event.id. Parent event this ticket belongs to. Set at creation."
3703
+ },
3704
+ "companyId": {
3705
+ "type": "string",
3706
+ "x-immutable": true,
3707
+ "denormalized": true,
3708
+ "description": "(Immutable, Denormalized) FK \u2192 Company document ID. Denormalized from parent Event for direct queries."
3709
+ },
3710
+ "customerId": {
3711
+ "x-see": {
3712
+ "decisions": [
3713
+ "D29"
3714
+ ]
3715
+ },
3716
+ "x-note": "Added additively per D29. Denormalized customerName/email/phone are kept for backwards compatibility. Null on tickets created before D29 was applied.",
3717
+ "description": "FK \u2192 Customer.id. Optional structured link to Customer document (D29). Added additively alongside denormalized fields.",
3718
+ "type": [
3719
+ "string",
3720
+ "null"
3721
+ ]
3722
+ },
3723
+ "customerName": {
3724
+ "denormalized": true,
3725
+ "description": "(Denormalized) From Customer.name at write time.",
3726
+ "type": [
3727
+ "string",
3728
+ "null"
3729
+ ]
3730
+ },
3731
+ "customerEmail": {
3732
+ "denormalized": true,
3733
+ "description": "(Denormalized) From Customer.email at write time.",
3734
+ "type": [
3735
+ "string",
3736
+ "null"
3737
+ ]
3738
+ },
3739
+ "customerPhone": {
3740
+ "denormalized": true,
3741
+ "description": "(Denormalized) From Customer.phone at write time.",
3742
+ "type": [
3743
+ "string",
3744
+ "null"
3745
+ ]
3746
+ },
3747
+ "status": {
3748
+ "$ref": "#/definitions/ticket-status",
3749
+ "description": "Ticket lifecycle (D32). SCREAMING_SNAKE per D04. MIG-10 migrates legacy lowercase values."
3750
+ },
3751
+ "usedAt": {
3752
+ "anyOf": [
3753
+ {
3754
+ "$ref": "#/definitions/firestore-timestamp"
3755
+ },
3756
+ {
3757
+ "type": "null"
3758
+ }
3759
+ ],
3760
+ "readOnly": true,
3761
+ "description": "(Read-only) Timestamp when ticket was scanned/used. Set by scan operation."
3762
+ },
3763
+ "usedBy": {
3764
+ "readOnly": true,
3765
+ "description": "(Read-only) FK \u2192 User/staff UID who scanned the ticket.",
3766
+ "type": [
3767
+ "string",
3768
+ "null"
3769
+ ]
3770
+ },
3771
+ "usedByName": {
3772
+ "readOnly": true,
3773
+ "denormalized": true,
3774
+ "description": "(Read-only, Denormalized) From User display name at scan time.",
3775
+ "type": [
3776
+ "string",
3777
+ "null"
3778
+ ]
3779
+ },
3780
+ "price": {
3781
+ "type": [
3782
+ "number",
3783
+ "null"
3784
+ ]
3785
+ },
3786
+ "notes": {
3787
+ "type": [
3788
+ "string",
3789
+ "null"
3790
+ ]
3791
+ },
3792
+ "createdAt": {
3793
+ "$ref": "#/definitions/firestore-timestamp",
3794
+ "description": "(Read-only) Server-generated creation timestamp.",
3795
+ "readOnly": true
3796
+ },
3797
+ "updatedAt": {
3798
+ "$ref": "#/definitions/firestore-timestamp",
3799
+ "description": "(Read-only) Server-generated update timestamp.",
3800
+ "readOnly": true
3801
+ },
3802
+ "createdBy": {
3803
+ "x-immutable": true,
3804
+ "description": "(Immutable) FK \u2192 User/staff UID who created this ticket.",
3805
+ "type": [
3806
+ "string",
3807
+ "null"
3808
+ ]
3809
+ }
3810
+ },
3811
+ "required": [
3812
+ "id",
3813
+ "eventId",
3814
+ "companyId",
3815
+ "status",
3816
+ "createdAt",
3817
+ "updatedAt"
3818
+ ],
3819
+ "additionalProperties": false,
3820
+ "description": "Ticket model (D32). Collection: companies/{companyId}/events/{eventId}/tickets/{ticketId}. Mobile-only today; Dashboard in Wave 4.",
3821
+ "example": {
3822
+ "id": "bk_abc123def456",
3823
+ "eventId": "eve_ref123",
3824
+ "companyId": "comp_xyz789",
3825
+ "customerId": null,
3826
+ "customerName": null,
3827
+ "customerEmail": null,
3828
+ "customerPhone": null,
3829
+ "status": "status",
3830
+ "usedAt": "usedAt",
3831
+ "usedBy": null,
3832
+ "usedByName": null,
3833
+ "price": null,
3834
+ "notes": null,
3835
+ "createdAt": "createdAt",
3836
+ "updatedAt": "updatedAt",
3837
+ "createdBy": null
3838
+ }
3839
+ },
3840
+ "ticket-status": {
3841
+ "type": "string",
3842
+ "enum": [
3843
+ "VALID",
3844
+ "USED",
3845
+ "CANCELLED"
3846
+ ],
3847
+ "description": "Event ticket status (D32). VALID = active and unused."
3848
+ },
3849
+ "attention-status": {
3850
+ "type": "string",
3851
+ "enum": [
3852
+ "ACTIVE",
3853
+ "STALE",
3854
+ "ON_HOLD",
3855
+ "ESCALATED"
3856
+ ],
3857
+ "description": "Operational attention status for Order and Booking (D39). Controls home-page queue assignment. Server-owned \u2014 set via triggers or callable fn only.",
3858
+ "x-note": "ACTIVE = normal urgent queue (default). STALE = idle past threshold, moved to review queue \u2014 excluded from pendingOrdersCount. ON_HOLD = intentionally paused by manager via callable fn. ESCALATED = ignored past second threshold.",
3859
+ "x-see": {
3860
+ "decisions": [
3861
+ "D39"
3862
+ ]
3863
+ },
3864
+ "x-when": "Set by server automation or callable fn. Mobile and Dashboard read this field; never write it directly. ON_HOLD is the only human-initiated transition \u2014 goes through a dedicated callable function.",
3865
+ "x-status": "proposed"
3866
+ },
3867
+ "payment-summary": {
3868
+ "type": "object",
3869
+ "properties": {
3870
+ "id": {
3871
+ "type": "string",
3872
+ "readOnly": true,
3873
+ "description": "(Read-only) Firestore document ID. Matches the period key (e.g. \"2026-03-09\" for DAILY, \"2026-03\" for MONTHLY)."
3874
+ },
3875
+ "companyId": {
3876
+ "type": "string",
3877
+ "x-immutable": true,
3878
+ "description": "(Immutable) FK \u2192 Company document ID. Scopes all queries."
3879
+ },
3880
+ "period": {
3881
+ "type": "string",
3882
+ "x-note": "Document ID matches this value. Format: YYYY-MM-DD (DAILY), YYYY-Www (WEEKLY), YYYY-MM (MONTHLY).",
3883
+ "description": "Period key. Determines the document ID and the time window covered."
3884
+ },
3885
+ "periodType": {
3886
+ "type": "string",
3887
+ "enum": [
3888
+ "DAILY",
3889
+ "WEEKLY",
3890
+ "MONTHLY"
3891
+ ],
3892
+ "description": "Granularity of this summary document."
3893
+ },
3894
+ "paymentsByMethod": {
3895
+ "type": "object",
3896
+ "propertyNames": {
3897
+ "type": "string",
3898
+ "enum": [
3899
+ "CASH",
3900
+ "CREDIT_CARD",
3901
+ "ORANGE_MONEY",
3902
+ "WAVE",
3903
+ "MTN_MONEY",
3904
+ "MOOV_MONEY",
3905
+ "BANK_TRANSFER",
3906
+ "PAYPAL",
3907
+ "STRIPE",
3908
+ "OTHER"
3909
+ ]
3910
+ },
3911
+ "additionalProperties": {
3912
+ "type": "object",
3913
+ "properties": {
3914
+ "total": {
3915
+ "type": "number",
3916
+ "description": "Sum of payment amounts for this method in the period. Updated via FieldValue.increment()."
3917
+ },
3918
+ "count": {
3919
+ "type": "integer",
3920
+ "minimum": -9007199254740991,
3921
+ "maximum": 9007199254740991,
3922
+ "description": "Number of individual payments for this method in the period. Updated via FieldValue.increment()."
3923
+ }
3924
+ },
3925
+ "required": [
3926
+ "total",
3927
+ "count"
3928
+ ],
3929
+ "additionalProperties": false
3930
+ },
3931
+ "required": [
3932
+ "CASH",
3933
+ "CREDIT_CARD",
3934
+ "ORANGE_MONEY",
3935
+ "WAVE",
3936
+ "MTN_MONEY",
3937
+ "MOOV_MONEY",
3938
+ "BANK_TRANSFER",
3939
+ "PAYPAL",
3940
+ "STRIPE",
3941
+ "OTHER"
3942
+ ],
3943
+ "x-note": "Keys are PaymentMethod enum values (CASH, WAVE, ORANGE_MONEY, etc.). Missing keys imply zero for that method. Updated incrementally via FieldValue.increment().",
3944
+ "x-see": {
3945
+ "decisions": [
3946
+ "D02",
3947
+ "D12"
3948
+ ]
3949
+ },
3950
+ "description": "Payment totals broken down by PaymentMethod. Each entry holds the total amount and transaction count for the period."
3951
+ },
3952
+ "grandTotal": {
3953
+ "type": "number",
3954
+ "readOnly": true,
3955
+ "description": "(Read-only) Sum of all paymentsByMethod[*].total. Server-calculated."
3956
+ },
3957
+ "totalCount": {
3958
+ "type": "integer",
3959
+ "minimum": -9007199254740991,
3960
+ "maximum": 9007199254740991,
3961
+ "readOnly": true,
3962
+ "description": "(Read-only) Sum of all paymentsByMethod[*].count. Server-calculated."
3963
+ },
3964
+ "currency": {
3965
+ "type": "string",
3966
+ "const": "XOF",
3967
+ "description": "Currency code. Locked to XOF (West African CFA franc)."
3968
+ },
3969
+ "lastUpdatedAt": {
3970
+ "readOnly": true,
3971
+ "description": "(Read-only) Server timestamp of the last increment write.",
3972
+ "type": "object",
3973
+ "properties": {
3974
+ "_seconds": {
3975
+ "type": "integer",
3976
+ "minimum": -9007199254740991,
3977
+ "maximum": 9007199254740991
3978
+ },
3979
+ "_nanoseconds": {
3980
+ "type": "integer",
3981
+ "minimum": -9007199254740991,
3982
+ "maximum": 9007199254740991
3983
+ }
3984
+ },
3985
+ "required": [
3986
+ "_seconds",
3987
+ "_nanoseconds"
3988
+ ],
3989
+ "additionalProperties": false
3990
+ },
3991
+ "createdAt": {
3992
+ "type": "object",
3993
+ "properties": {
3994
+ "_seconds": {
3995
+ "type": "integer",
3996
+ "minimum": -9007199254740991,
3997
+ "maximum": 9007199254740991
3998
+ },
3999
+ "_nanoseconds": {
4000
+ "type": "integer",
4001
+ "minimum": -9007199254740991,
4002
+ "maximum": 9007199254740991
4003
+ }
4004
+ },
4005
+ "required": [
4006
+ "_seconds",
4007
+ "_nanoseconds"
4008
+ ],
4009
+ "additionalProperties": false,
4010
+ "description": "(Read-only) Server-generated creation timestamp.",
4011
+ "readOnly": true
4012
+ }
4013
+ },
4014
+ "required": [
4015
+ "id",
4016
+ "companyId",
4017
+ "period",
4018
+ "periodType",
4019
+ "paymentsByMethod",
4020
+ "grandTotal",
4021
+ "totalCount",
4022
+ "currency",
4023
+ "createdAt"
4024
+ ],
4025
+ "additionalProperties": false,
4026
+ "description": "Aggregated payment totals by method for a given period. Collection: companies/{companyId}/paymentSummaries/{period}. Server-owned \u2014 all fields are set by Cloud Function triggers on Order.payments[] and CustomerPayment writes. Clients must never write to this collection.",
4027
+ "x-status": "proposed"
4028
+ },
4029
+ "pending-issue": {
4030
+ "type": "string",
4031
+ "enum": [
4032
+ "PAYMENT_PROOF_PENDING",
4033
+ "PAYMENT_PROOF_REJECTED",
4034
+ "AMOUNT_DISCREPANCY",
4035
+ "RETURN_UNRESOLVED",
4036
+ "OVERDUE_DELIVERY",
4037
+ "SESSION_OVERDUE",
4038
+ "PAYMENT_INCOMPLETE",
4039
+ "CANCELLATION_REQUESTED",
4040
+ "UNREFUNDED_CANCELLATION",
4041
+ "UPCOMING_UNPAID",
4042
+ "NO_SHOW_UNRESOLVED"
4043
+ ],
4044
+ "description": "Specific detected conditions on an Order or Booking requiring human action (D39). Stored as an array \u2014 multiple issues can be active simultaneously. Server-owned.",
4045
+ "x-note": "Shared: PAYMENT_PROOF_PENDING (proof uploaded, awaiting review), PAYMENT_PROOF_REJECTED (proof rejected, needs resubmission), AMOUNT_DISCREPANCY (amountPaid does not reconcile with amount/paymentStatus). Order-only: RETURN_UNRESOLVED (return/exchange stuck), OVERDUE_DELIVERY (estimatedDeliveryDate past, not yet delivered). Booking-only: SESSION_OVERDUE (session date passed, status not updated), PAYMENT_INCOMPLETE (sessions completed but payment not recorded), CANCELLATION_REQUESTED (cancellation not actioned), UNREFUNDED_CANCELLATION (cancelled with amountPaid > 0 and no refund), UPCOMING_UNPAID (session within 48h, payment not recorded), NO_SHOW_UNRESOLVED (no-show without follow-up).",
4046
+ "x-see": {
4047
+ "decisions": [
4048
+ "D39"
4049
+ ]
4050
+ },
4051
+ "x-when": "Set and cleared by server automation or triggers. Never affects pendingOrdersCount or pendingBookingsCount \u2014 orthogonal to urgency. Multiple values can be active at once (stored as PendingIssue[] on Order/Booking).",
4052
+ "x-status": "proposed"
4053
+ }
4054
+ }
4055
+ }