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

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 (42) hide show
  1. package/data/docs/collections/firestore-paths.md +48 -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 +95 -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/sale.md +540 -0
  35. package/data/docs/models/ticket.md +405 -0
  36. package/data/docs/triggers/event-ticket-triggers.md +204 -0
  37. package/data/docs/triggers/loyalty-automation.md +123 -0
  38. package/data/static/decisions.json +966 -0
  39. package/data/static/llms.txt +1046 -0
  40. package/data/static/openapi.yaml +3090 -0
  41. package/data/static/schemas.json +4012 -0
  42. package/package.json +1 -1
@@ -0,0 +1,1046 @@
1
+ # @valets/schema
2
+
3
+ > Canonical data model for the Valets platform.
4
+ > Source of truth: Zod schemas in packages/schema/src/
5
+ > All Firestore documents are scoped under companies/{companyId}/
6
+
7
+ > Generated: 2026-03-09
8
+
9
+ ---
10
+
11
+ ## Enums
12
+
13
+ ### BookingStatus
14
+ Booking lifecycle status. COMPLETED_MIXED = some sessions completed, others cancelled/no-show.
15
+ Values: PENDING, CONFIRMED, COMPLETED, CANCELLED, CANCELLATION_REQUESTED, COMPLETED_MIXED
16
+
17
+ ### CustomerPaymentStatus
18
+ Customer payment lifecycle status (D22). Tracks allocation progress of received payments.
19
+ Values: PENDING, CONFIRMED, PARTIALLY_APPLIED, FULLY_APPLIED, REFUNDED, CANCELLED
20
+
21
+ ### CustomerPaymentTargetType
22
+ Target document type for customer payment allocation.
23
+ Values: BOOKING, ORDER, PURCHASE
24
+
25
+ ### DeliveryType
26
+ 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.
27
+ Values: ON_SITE, PICK_UP, DELIVERY
28
+
29
+ ### EventStatus
30
+ Ticketed event lifecycle (D32). Mobile-only today; Dashboard in Wave 4.
31
+ Values: DRAFT, ACTIVE, CANCELLED, COMPLETED
32
+
33
+ ### FulfillmentStatus
34
+ Delivery/fulfillment lifecycle (D34). Optional — null for in-person orders.
35
+ Values: PREPARING, PARTIALLY_SHIPPED, SHIPPED, IN_TRANSIT, DELIVERED, PICKED_UP
36
+
37
+ ### LoyaltyTransactionType
38
+ Loyalty point transaction type (D07). SCREAMING_SNAKE past tense.
39
+ Values: EARNED, REDEEMED, ADJUSTED, EXPIRED, BONUS, REFUND
40
+
41
+ ### OrderStatus
42
+ Core order lifecycle status (D03, D34). Universal across all business types. Replaces the Dashboard's legacy 20-value flat enum (MIG-11).
43
+ Values: PENDING, CONFIRMED, PROCESSING, READY, COMPLETED, CANCELLED, EXPIRED
44
+
45
+ ### PaymentMethod
46
+ Unified payment method set with African + global methods (D02).
47
+ Values: CASH, CREDIT_CARD, ORANGE_MONEY, WAVE, MTN_MONEY, MOOV_MONEY, BANK_TRANSFER, PAYPAL, STRIPE, OTHER
48
+
49
+ ### PaymentProofStatus
50
+ Payment proof review status. Used by Order and Booking payment proof workflows.
51
+ Values: PENDING, APPROVED, REJECTED
52
+
53
+ ### PaymentStatus
54
+ Payment lifecycle status (D01 amended). Used by Order, Sale/Purchase, Booking.
55
+ Values: PENDING, PAID, PARTIALLY_PAID, FAILED, REFUND_PROCESSING, REFUNDED, PARTIALLY_REFUNDED
56
+
57
+ ### ReturnStatus
58
+ Post-sale return/exchange lifecycle (D34). Optional — null until return or exchange initiated.
59
+ Values: RETURN_REQUESTED, RETURN_PROCESSING, RETURNED, EXCHANGE_REQUESTED, EXCHANGE_PROCESSING, EXCHANGE_COMPLETED
60
+
61
+ ### SessionStatus
62
+ Per-date/per-slot booking session status (D19). Dashboard is sole writer; Mobile is read-only.
63
+ Values: PENDING, CONFIRMED, CANCELLATION_REQUESTED, COMPLETED, NO_SHOW, CANCELLED
64
+
65
+ ### TicketStatus
66
+ Event ticket status (D32). VALID = active and unused.
67
+ Values: VALID, USED, CANCELLED
68
+
69
+ ---
70
+
71
+ ## Models
72
+
73
+ ### Booking
74
+ Fields: 52 (7 required)
75
+
76
+ | Field | Type | Required | Description |
77
+ |-------|------|----------|-------------|
78
+ | id | string | yes | (Read-only) Firestore document ID. |
79
+ | uid | string | yes | (Read-only) Entity UID. Often mirrors id. |
80
+ | companyId | ['string', 'null'] | no | (Immutable) FK → Company document ID. Note: optional in current schema — should be required (see ID consistency audit). |
81
+ | status | BookingStatus | yes | Booking lifecycle status. COMPLETED_MIXED = some sessions completed, others cancelled/no-show. |
82
+ | totalAmount | number | yes | |
83
+ | bookingDates | array<object> | yes | |
84
+ | bookingDates[].date | string | yes | |
85
+ | bookingDates[].selectedTimeSlots | array<object> | yes | |
86
+ | bookingDates[].selectedTimeSlots[].id | number | yes | |
87
+ | bookingDates[].selectedTimeSlots[].name | string | yes | |
88
+ | bookingDates[].selectedTimeSlots[].timeRange | string | yes | |
89
+ | bookingDates[].selectedTimeSlots[].price | number | yes | Canonical price field (D11). Replaces legacy standardPrice/fullPrice (MIG-08). |
90
+ | bookingDates[].selectedTimeSlots[].notMainPurposePrice | number | yes | Price when slot is not the main purpose (D11). Canonical name; replaces legacy aliases (MIG-08). |
91
+ | bookingDates[].selectedTimeSlots[].hours | string | yes | |
92
+ | bookingDates[].selectedTimeSlots[].startTime | string | yes | |
93
+ | bookingDates[].selectedTimeSlots[].endTime | string | yes | |
94
+ | bookingDates[].selectedTimeSlots[].isNightSlot | boolean | no | |
95
+ | bookingDates[].selectedTimeSlots[].isNotMainPurpose | boolean | no | |
96
+ | bookingDates[].selectedTimeSlots[].isNotMainPurposeReason | string | no | |
97
+ | bookingDates[].selectedTimeSlots[].isNotMainPurposeOtherReason | string | no | |
98
+ | bookingDates[].selectedTimeSlots[]._deleted | boolean | no | Soft-delete flag. Dashboard writes; Mobile filters (IG-6). |
99
+ | bookingDates[].selectedTimeSlots[]._deletedAt | FirestoreTimestamp | no | Firestore Timestamp serialized representation |
100
+ | bookingDates[].selectedTimeSlots[]._deletedBy | string | no | FK → User/staff UID who soft-deleted this item. |
101
+ | bookingDates[].selectedTimeSlots[]._lastModifiedAt | FirestoreTimestamp | no | Firestore Timestamp serialized representation |
102
+ | bookingDates[].selectedTimeSlots[]._lastModifiedBy | string | no | FK → User/staff UID who last modified this item. |
103
+ | bookingDates[].selectedTimeSlots[]._version | number | no | Monotonic version counter for conflict detection. |
104
+ | bookingDates[].slotKitTypes | record<string,string> | yes | Kit type per slot (IG-11). Firebase parity gap — exists in Firestore but Firebase type lacked it. |
105
+ | bookingDates[].slotAddOns | record<string,array<string>> | yes | Add-on IDs per slot. Numeric IDs to be normalized to descriptive names (MIG-07/D10). |
106
+ | bookingDates[].extraHours | number | yes | |
107
+ | bookingDates[].status | SessionStatus | no | Per-date session status (D19). Dashboard is sole writer; Mobile is read-only. |
108
+ | bookingDates[].statusUpdatedAt | FirestoreTimestamp | no | (Read-only) Timestamp of last status change. |
109
+ | bookingDates[].statusUpdatedBy | string | no | FK → User/staff UID who updated this date status. |
110
+ | bookingDates[].slotStatuses | record<string,SessionStatus> | no | Per-slot session statuses (D19). Keyed by slot ID string. |
111
+ | bookingDates[].slotStatusUpdatedAt | record<string,FirestoreTimestamp> | no | |
112
+ | bookingDates[].slotStatusUpdatedBy | record<string,string> | no | |
113
+ | bookingDates[]._deleted | boolean | no | Soft-delete flag. Dashboard writes; Mobile filters (IG-6). |
114
+ | bookingDates[]._deletedAt | FirestoreTimestamp | no | Firestore Timestamp serialized representation |
115
+ | bookingDates[]._deletedBy | string | no | FK → User/staff UID who soft-deleted this item. |
116
+ | bookingDates[]._lastModifiedAt | FirestoreTimestamp | no | Firestore Timestamp serialized representation |
117
+ | bookingDates[]._lastModifiedBy | string | no | FK → User/staff UID who last modified this item. |
118
+ | bookingDates[]._version | number | no | Monotonic version counter for conflict detection. |
119
+ | customerId | ['string', 'null'] | no | FK → Customer.id (Firestore doc ID). Links booking to customer record. |
120
+ | client | ['object', 'null'] | no | (Denormalized) Embedded client snapshot from Customer at write time. |
121
+ | customerName | ['string', 'null'] | no | (Denormalized) From Customer.name at write time. Canonical field per D24. |
122
+ | customerEmail | ['string', 'null'] | no | (Denormalized) From Customer.email at write time. Canonical field per D24. |
123
+ | customerPhone | ['string', 'null'] | no | (Denormalized) From Customer.phone at write time. Canonical field per D24. |
124
+ | clientName | ['string', 'null'] | no | (Denormalized) Legacy — use `customerName`. D24 standardized to customer* prefix. |
125
+ | clientReference | string | yes | |
126
+ | clientEmail | ['string', 'null'] | no | (Denormalized) Legacy — use `customerEmail`. D24 standardized to customer* prefix. |
127
+ | clientPhone | ['string', 'null'] | no | (Denormalized) Legacy — use `customerPhone`. D24 standardized to customer* prefix. |
128
+ | service | ['object', 'null'] | no | |
129
+ | serviceId | ['string', 'null'] | no | FK → Service document ID. |
130
+ | serviceName | ['string', 'null'] | no | (Denormalized) From Service.name at write time. |
131
+ | timeSlot | ['string', 'null'] | no | |
132
+ | date | ['string', 'null'] | no | |
133
+ | startDate | ['string', 'null'] | no | |
134
+ | endDate | ['string', 'null'] | no | |
135
+ | startTime | ['string', 'null'] | no | |
136
+ | endTime | ['string', 'null'] | no | |
137
+ | notes | ['array', 'null'] | no | |
138
+ | technicalInfo | ['string', 'null'] | no | |
139
+ | paymentStatus | any | no | Payment lifecycle status (D01 amended). Used by Order, Sale/Purchase, Booking. |
140
+ | amountPaid | ['number', 'null'] | no | |
141
+ | amountRefunded | ['number', 'null'] | no | |
142
+ | amountPending | ['number', 'null'] | no | |
143
+ | purchaseId | ['string', 'null'] | no | FK → Sale.id. Link to associated Sale document. |
144
+ | paymentStatusChangeReason | ['string', 'null'] | no | |
145
+ | paymentStatusChangedBy | ['string', 'null'] | no | FK → User/staff UID who changed payment status. |
146
+ | paymentStatusChangedAt | any | no | Firestore Timestamp serialized representation |
147
+ | paymentProofUrl | ['string', 'null'] | no | URL to uploaded payment proof image/document. |
148
+ | paymentProofStatus | any | no | Payment proof review status. Used by Order and Booking payment proof workflows. |
149
+ | paymentProofAddedAt | any | no | Firestore Timestamp serialized representation |
150
+ | paymentProofAddedBy | ['string', 'null'] | no | FK → User/staff UID who uploaded the payment proof. |
151
+ | paymentProofReviewedBy | ['string', 'null'] | no | FK → User/staff UID who reviewed the payment proof. |
152
+ | paymentProofReviewedAt | any | no | Firestore Timestamp serialized representation |
153
+ | paymentProofRejectionReason | ['string', 'null'] | no | |
154
+ | cancellationRequestedById | ['string', 'null'] | no | FK → User/staff UID who requested cancellation. |
155
+ | cancellationRequestReason | ['string', 'null'] | no | |
156
+ | cancellationProcessedById | ['string', 'null'] | no | FK → User/staff UID who processed the cancellation. |
157
+ | cancellationProcessedAt | any | no | Firestore Timestamp serialized representation |
158
+ | cancelledByRole | ['string', 'null'] | no | |
159
+ | cancellationReason | ['string', 'null'] | no | |
160
+ | createdAt | FirestoreTimestamp | yes | (Read-only) Server-generated creation timestamp. |
161
+ | updatedAt | any | no | (Read-only) Server-generated update timestamp. |
162
+ | createdBy | ['string', 'null'] | no | (Immutable) FK → User/staff UID who created this booking. |
163
+ | updatedBy | ['string', 'null'] | no | FK → User/staff UID who last updated this booking. |
164
+ | createdFromBackend | ['boolean', 'null'] | no | When true, suppresses Firebase notification triggers (D20/IG-8). |
165
+
166
+ Example:
167
+ ```json
168
+ {
169
+ "id": "bk_abc123def456",
170
+ "uid": "user_u8x92kqm",
171
+ "companyId": null,
172
+ "status": "status",
173
+ "totalAmount": 45000,
174
+ "bookingDates": [
175
+ {
176
+ "date": "2026-02-15",
177
+ "selectedTimeSlots": [
178
+ {
179
+ "id": 0,
180
+ "name": "Morning Full Session",
181
+ "timeRange": "09:00 - 11:00",
182
+ "price": 15000,
183
+ "notMainPurposePrice": 10000,
184
+ "hours": "2h",
185
+ "startTime": "09:00",
186
+ "endTime": "11:00"
187
+ }
188
+ ],
189
+ "slotKitTypes": {},
190
+ "slotAddOns": {},
191
+ "extraHours": 0
192
+ }
193
+ ],
194
+ "customerId": null,
195
+ "client": null,
196
+ "customerName": null,
197
+ "customerEmail": null,
198
+ "customerPhone": null,
199
+ "clientName": null,
200
+ "clientReference": "CLI-2026-0017",
201
+ "clientEmail": null,
202
+ "clientPhone": null,
203
+ "service": null,
204
+ "serviceId": null,
205
+ "serviceName": null,
206
+ "timeSlot": null,
207
+ "date": null,
208
+ "startDate": null,
209
+ "endDate": null,
210
+ "startTime": null,
211
+ "endTime": null,
212
+ "notes": null,
213
+ "technicalInfo": null,
214
+ "paymentStatus": "paymentStatus",
215
+ "amountPaid": null,
216
+ "amountRefunded": null,
217
+ "amountPending": null,
218
+ "purchaseId": null,
219
+ "paymentStatusChangeReason": null,
220
+ "paymentStatusChangedBy": null,
221
+ "paymentStatusChangedAt": "paymentStatusChangedAt",
222
+ "paymentProofUrl": null,
223
+ "paymentProofStatus": "paymentProofStatus",
224
+ "paymentProofAddedAt": "paymentProofAddedAt",
225
+ "paymentProofAddedBy": null,
226
+ "paymentProofReviewedBy": null,
227
+ "paymentProofReviewedAt": "paymentProofReviewedAt",
228
+ "paymentProofRejectionReason": null,
229
+ "cancellationRequestedById": null,
230
+ "cancellationRequestReason": null,
231
+ "cancellationProcessedById": null,
232
+ "cancellationProcessedAt": "cancellationProcessedAt",
233
+ "cancelledByRole": null,
234
+ "cancellationReason": null,
235
+ "createdAt": "createdAt",
236
+ "updatedAt": "updatedAt",
237
+ "createdBy": null,
238
+ "updatedBy": null,
239
+ "createdFromBackend": null
240
+ }
241
+ ```
242
+
243
+ ### BookingVersion
244
+ Fields: 9 (6 required)
245
+
246
+ | Field | Type | Required | Description |
247
+ |-------|------|----------|-------------|
248
+ | id | string | yes | (Read-only) Firestore document ID. Server-generated version record. |
249
+ | bookingId | string | yes | (Immutable) FK → Booking.id. Parent booking this version records. |
250
+ | companyId | string | yes | (Immutable) FK → Company document ID. Denormalized for direct queries. |
251
+ | changeType | enum(3) | yes | (Immutable) What caused this version record: CREATE, UPDATE, or DELETE. |
252
+ | changedAt | FirestoreTimestamp | yes | (Read-only) Timestamp of the write that created this version. Server-set. |
253
+ | changedBy | ['string', 'null'] | no | (Immutable) FK → User/staff UID who made the change. Null for server-originated writes. |
254
+ | changedByRole | enum(5) | no | (Immutable) Which platform/context made the write: DASHBOARD, MOBILE, FIREBASE (trigger), CLI (migration script), or UNKNOWN. SCREAMING_SNAKE per D04. |
255
+ | fieldsChanged | ['array', 'null'] | no | (Immutable, Read-only) List of field paths that changed in this write. Populated by the Firebase trigger diff. Null for CREATE records. |
256
+ | snapshot | object | yes | (Immutable, Read-only) Full snapshot of the Booking document immediately after the write. Untyped to avoid circular schema dependency — cast to Booking at runtime. |
257
+
258
+ Example:
259
+ ```json
260
+ {
261
+ "id": "bk_abc123def456",
262
+ "bookingId": "boo_ref123",
263
+ "companyId": "comp_xyz789",
264
+ "changeType": "CREATE",
265
+ "changedAt": "changedAt",
266
+ "changedBy": null,
267
+ "changedByRole": "DASHBOARD",
268
+ "fieldsChanged": null,
269
+ "snapshot": {}
270
+ }
271
+ ```
272
+
273
+ ### Customer
274
+ Fields: 17 (4 required)
275
+
276
+ | Field | Type | Required | Description |
277
+ |-------|------|----------|-------------|
278
+ | id | string | yes | (Read-only) Firestore document ID. This is the canonical FK target — other models reference Customer via this field. |
279
+ | uid | string | yes | (Read-only) Entity UID. Often mirrors id. |
280
+ | name | string | yes | |
281
+ | email | ['string', 'null'] | no | |
282
+ | phone | ['string', 'null'] | no | |
283
+ | address | ['string', 'null'] | no | |
284
+ | notes | ['string', 'null'] | no | |
285
+ | tags | ['array', 'null'] | no | |
286
+ | communicationEntries | ['array', 'null'] | no | Embedded CRM log (D25). Stays as array, not subcollection. |
287
+ | createdAt | FirestoreTimestamp | yes | (Read-only) Server-generated creation timestamp. |
288
+ | lastOrderDate | any | no | (Read-only) Updated by server when orders are placed. |
289
+ | balance | ['number', 'null'] | no | (Read-only) Outstanding balance. Dashboard-originated, server-calculated. |
290
+ | creditLimit | ['number', 'null'] | no | |
291
+ | lastPaymentDate | any | no | (Read-only) Updated by server when payments are recorded. |
292
+ | totalPaid | ['number', 'null'] | no | (Read-only) Server-calculated total paid amount. |
293
+ | totalOwed | ['number', 'null'] | no | (Read-only) Server-calculated total owed amount. |
294
+ | loyaltyPoints | ['number', 'null'] | no | (Read-only, Denormalized) Derived summary from loyalty/status subcollection (D08). Source of truth is LoyaltyStatus.pointsBalance. |
295
+
296
+ Example:
297
+ ```json
298
+ {
299
+ "id": "bk_abc123def456",
300
+ "uid": "user_u8x92kqm",
301
+ "name": "Amadou Diallo",
302
+ "email": null,
303
+ "phone": null,
304
+ "address": null,
305
+ "notes": null,
306
+ "tags": null,
307
+ "communicationEntries": null,
308
+ "createdAt": "createdAt",
309
+ "lastOrderDate": "lastOrderDate",
310
+ "balance": null,
311
+ "creditLimit": null,
312
+ "lastPaymentDate": "lastPaymentDate",
313
+ "totalPaid": null,
314
+ "totalOwed": null,
315
+ "loyaltyPoints": null
316
+ }
317
+ ```
318
+
319
+ ### CustomerPayment
320
+ Fields: 17 (12 required)
321
+
322
+ | Field | Type | Required | Description |
323
+ |-------|------|----------|-------------|
324
+ | id | ['string', 'null'] | no | (Read-only) Firestore document ID. Note: optional in current schema — some legacy docs may lack this field. |
325
+ | companyId | string | yes | (Immutable) FK → Company document ID. Scopes all queries. |
326
+ | customerId | string | yes | (Immutable) FK → Customer.id (Firestore doc ID). Links payment to customer. Set at creation. |
327
+ | customerName | ['string', 'null'] | no | (Denormalized) From Customer.name at write time. |
328
+ | amount | number | yes | |
329
+ | currency | string | yes | Currency code. Locked to XOF (West African CFA franc) for now. |
330
+ | paymentDate | FirestoreTimestamp | yes | Firestore Timestamp serialized representation |
331
+ | paymentMethod | PaymentMethod | yes | Unified payment method set with African + global methods (D02). |
332
+ | referenceNumber | string | yes | Unique payment reference (receipt number, transaction ID, etc.). |
333
+ | allocatedAmount | number | yes | (Read-only) Total amount allocated to bookings/orders/purchases via allocations. Server-calculated. |
334
+ | unappliedAmount | number | yes | (Read-only) Remaining unallocated amount (amount - allocatedAmount). Server-calculated. |
335
+ | status | CustomerPaymentStatus | yes | Customer payment lifecycle status (D22). Tracks allocation progress of received payments. |
336
+ | notes | ['string', 'null'] | no | |
337
+ | recordedBy | string | yes | (Immutable) FK → User/staff UID who recorded the payment. Required audit field (D22/IG-7). |
338
+ | recordedByName | ['string', 'null'] | no | (Immutable, Denormalized) From User display name at creation time. |
339
+ | createdAt | FirestoreTimestamp | yes | (Read-only) Server-generated creation timestamp. |
340
+ | updatedAt | any | no | (Read-only) Server-generated update timestamp. |
341
+
342
+ Example:
343
+ ```json
344
+ {
345
+ "id": null,
346
+ "companyId": "comp_xyz789",
347
+ "customerId": "cus_ref123",
348
+ "customerName": null,
349
+ "amount": 45000,
350
+ "currency": "XOF",
351
+ "paymentDate": "paymentDate",
352
+ "paymentMethod": "paymentMethod",
353
+ "referenceNumber": "PAY-2026-0099",
354
+ "allocatedAmount": 30000,
355
+ "unappliedAmount": 15000,
356
+ "status": "status",
357
+ "notes": null,
358
+ "recordedBy": "staff_k0f1",
359
+ "recordedByName": null,
360
+ "createdAt": "createdAt",
361
+ "updatedAt": "updatedAt"
362
+ }
363
+ ```
364
+
365
+ ### CustomerPaymentAllocation
366
+ Fields: 14 (8 required)
367
+
368
+ | Field | Type | Required | Description |
369
+ |-------|------|----------|-------------|
370
+ | id | ['string', 'null'] | no | (Read-only) Firestore document ID. Note: optional in current schema. |
371
+ | companyId | string | yes | (Immutable) FK → Company document ID. |
372
+ | paymentId | string | yes | (Immutable) FK → CustomerPayment.id. Parent payment this allocation draws from. |
373
+ | customerId | string | yes | (Immutable) FK → Customer.id (Firestore doc ID). |
374
+ | targetId | string | yes | (Immutable) FK → Booking.id, Order.id, or Sale.id (polymorphic). See targetType for discriminator. |
375
+ | targetType | CustomerPaymentTargetType | yes | (Immutable) Discriminator for targetId: BOOKING, ORDER, or PURCHASE. |
376
+ | targetReference | ['string', 'null'] | no | |
377
+ | allocatedAmount | number | yes | (Immutable) Amount allocated in this allocation. Set at creation. |
378
+ | transferredToAllocationId | ['string', 'null'] | no | FK → CustomerPaymentAllocation.id. Self-reference for transfer chains. |
379
+ | transferredFromAllocationId | ['string', 'null'] | no | FK → CustomerPaymentAllocation.id. Self-reference for transfer chains. |
380
+ | transferredAt | any | no | Firestore Timestamp serialized representation |
381
+ | createdBy | string | yes | (Immutable) FK → User/staff UID who created this allocation. |
382
+ | createdByName | ['string', 'null'] | no | (Immutable, Denormalized) From User display name at creation time. |
383
+ | createdAt | FirestoreTimestamp | yes | (Read-only) Server-generated creation timestamp. |
384
+
385
+ Example:
386
+ ```json
387
+ {
388
+ "id": null,
389
+ "companyId": "comp_xyz789",
390
+ "paymentId": "pay_ref123",
391
+ "customerId": "cus_ref123",
392
+ "targetId": "tar_ref123",
393
+ "targetType": "targetType",
394
+ "targetReference": null,
395
+ "allocatedAmount": 30000,
396
+ "transferredToAllocationId": null,
397
+ "transferredFromAllocationId": null,
398
+ "transferredAt": "transferredAt",
399
+ "createdBy": "staff_k0f1",
400
+ "createdByName": null,
401
+ "createdAt": "createdAt"
402
+ }
403
+ ```
404
+
405
+ ### Event
406
+ Fields: 15 (9 required)
407
+
408
+ | Field | Type | Required | Description |
409
+ |-------|------|----------|-------------|
410
+ | id | string | yes | (Read-only) Firestore document ID. Note: Event does not have a uid field. |
411
+ | companyId | string | yes | (Immutable) FK → Company document ID. Scopes all queries. |
412
+ | name | string | yes | |
413
+ | description | ['string', 'null'] | no | |
414
+ | location | ['string', 'null'] | no | |
415
+ | startDate | FirestoreTimestamp | yes | Firestore Timestamp serialized representation |
416
+ | endDate | any | no | Firestore Timestamp serialized representation |
417
+ | status | EventStatus | yes | Event lifecycle status (D32). SCREAMING_SNAKE per D04. MIG-09 migrates legacy lowercase values. |
418
+ | maxTickets | ['integer', 'null'] | no | |
419
+ | ticketsSold | integer | yes | (Read-only) Counter: total tickets sold. Updated by Firebase triggers (D28). |
420
+ | ticketsUsed | integer | yes | (Read-only) Counter: tickets scanned/used. Updated by Firebase triggers (D28). |
421
+ | ticketPrice | ['number', 'null'] | no | |
422
+ | createdAt | FirestoreTimestamp | yes | (Read-only) Server-generated creation timestamp. |
423
+ | updatedAt | FirestoreTimestamp | yes | (Read-only) Server-generated update timestamp. |
424
+ | createdBy | ['string', 'null'] | no | (Immutable) FK → User/staff UID who created this event. |
425
+
426
+ Example:
427
+ ```json
428
+ {
429
+ "id": "bk_abc123def456",
430
+ "companyId": "comp_xyz789",
431
+ "name": "Amadou Diallo",
432
+ "description": null,
433
+ "location": null,
434
+ "startDate": "2026-02-15",
435
+ "endDate": "2026-02-20",
436
+ "status": "status",
437
+ "maxTickets": null,
438
+ "ticketsSold": 47,
439
+ "ticketsUsed": 12,
440
+ "ticketPrice": null,
441
+ "createdAt": "createdAt",
442
+ "updatedAt": "updatedAt",
443
+ "createdBy": null
444
+ }
445
+ ```
446
+
447
+ ### LoyaltyConfig
448
+ Fields: 11 (2 required)
449
+
450
+ | Field | Type | Required | Description |
451
+ |-------|------|----------|-------------|
452
+ | id | ['string', 'null'] | no | (Read-only) Firestore document ID. Singleton doc — typically "config". |
453
+ | isEnabled | boolean | yes | |
454
+ | pointSystem | enum(3) | yes | How points are earned: SPENDING (per currency spent), PRODUCT (per product purchased), or VISIT (per visit). SCREAMING_SNAKE per D04. |
455
+ | pointsPerCurrency | ['number', 'null'] | no | [Deprecated alias: pointsPerCurrencyUnit — renamed by MIG-06] |
456
+ | pointsPerVisit | ['number', 'null'] | no | Points earned per visit (when pointSystem is visit). |
457
+ | pointValue | ['number', 'null'] | no | Monetary value of one point for redemption. |
458
+ | minimumRedeemPoints | ['integer', 'null'] | no | Minimum points balance required before redemption is allowed. |
459
+ | welcomeBonusPoints | ['integer', 'null'] | no | One-time points bonus for new loyalty enrollees. |
460
+ | pointsExpirationDays | any | no | [Deprecated alias: pointsExpiryMonths — renamed and converted months*30 to days by MIG-06] |
461
+ | createdAt | any | no | (Read-only) Server-generated creation timestamp. |
462
+ | updatedAt | any | no | (Read-only) Server-generated update timestamp. |
463
+
464
+ Example:
465
+ ```json
466
+ {
467
+ "id": null,
468
+ "isEnabled": true,
469
+ "pointSystem": "SPENDING",
470
+ "pointsPerCurrency": null,
471
+ "pointsPerVisit": null,
472
+ "pointValue": null,
473
+ "minimumRedeemPoints": null,
474
+ "welcomeBonusPoints": null,
475
+ "pointsExpirationDays": "pointsExpirationDays",
476
+ "createdAt": "createdAt",
477
+ "updatedAt": "updatedAt"
478
+ }
479
+ ```
480
+
481
+ ### LoyaltyReward
482
+ Fields: 11 (5 required)
483
+
484
+ | Field | Type | Required | Description |
485
+ |-------|------|----------|-------------|
486
+ | id | string | yes | (Read-only) Firestore document ID. |
487
+ | name | string | yes | |
488
+ | description | ['string', 'null'] | no | |
489
+ | pointsRequired | integer | yes | Points cost to redeem this reward. |
490
+ | isActive | boolean | yes | Whether this reward is currently available for redemption. |
491
+ | imageUrl | ['string', 'null'] | no | |
492
+ | rewardType | ['string', 'null'] | no | Category of reward (e.g. discount, free_item, service). |
493
+ | discountValue | ['number', 'null'] | no | Discount amount when rewardType is discount. |
494
+ | productId | ['string', 'null'] | no | FK → Product.id. Linked product when rewardType is free_item. |
495
+ | createdAt | FirestoreTimestamp | yes | (Read-only) Server-generated creation timestamp. |
496
+ | updatedAt | any | no | (Read-only) Server-generated update timestamp. |
497
+
498
+ Example:
499
+ ```json
500
+ {
501
+ "id": "bk_abc123def456",
502
+ "name": "Free Braiding Session",
503
+ "description": null,
504
+ "pointsRequired": 200,
505
+ "isActive": true,
506
+ "imageUrl": null,
507
+ "rewardType": null,
508
+ "discountValue": null,
509
+ "productId": null,
510
+ "createdAt": "createdAt",
511
+ "updatedAt": "updatedAt"
512
+ }
513
+ ```
514
+
515
+ ### LoyaltyStatus
516
+ Fields: 9 (2 required)
517
+
518
+ | Field | Type | Required | Description |
519
+ |-------|------|----------|-------------|
520
+ | customerId | ['string', 'null'] | no | (Immutable) FK → Customer.id. Note: optional despite being in customer subcollection (path already contains custId). |
521
+ | pointsBalance | integer | yes | (Read-only) Current available points balance. Canonical name (D08). Dashboard: pointsBalance, Mobile: currentPoints. Updated by transaction triggers. |
522
+ | totalPointsEarned | integer | yes | (Read-only) Lifetime total points earned. Canonical name (D08). Dashboard: totalPointsEarned, Mobile: lifetimePoints. Updated by transaction triggers. |
523
+ | redeemedPoints | ['integer', 'null'] | no | (Read-only) Total redeemed points. Updated by transaction triggers. |
524
+ | lastActivityDate | any | no | (Read-only) Last earn/redeem activity. Canonical name (D08). Mobile alias: lastEarnedAt. |
525
+ | lastRedeemedAt | any | no | (Read-only) Last redemption timestamp. |
526
+ | tier | ['string', 'null'] | no | |
527
+ | createdAt | any | no | (Read-only) Server-generated creation timestamp. |
528
+ | updatedAt | any | no | (Read-only) Server-generated update timestamp. |
529
+
530
+ Example:
531
+ ```json
532
+ {
533
+ "customerId": null,
534
+ "pointsBalance": 340,
535
+ "totalPointsEarned": 1200,
536
+ "redeemedPoints": null,
537
+ "lastActivityDate": "lastActivityDate",
538
+ "lastRedeemedAt": "lastRedeemedAt",
539
+ "tier": null,
540
+ "createdAt": "createdAt",
541
+ "updatedAt": "updatedAt"
542
+ }
543
+ ```
544
+
545
+ ### LoyaltyTransaction
546
+ Fields: 16 (4 required)
547
+
548
+ | Field | Type | Required | Description |
549
+ |-------|------|----------|-------------|
550
+ | id | string | yes | (Read-only) Firestore document ID. |
551
+ | customerId | ['string', 'null'] | no | (Immutable) FK → Customer.id. Note: optional despite being in customer subcollection. |
552
+ | type | LoyaltyTransactionType | yes | (Immutable) Transaction type (D07). SCREAMING_SNAKE per D04. MIG-05 migrates legacy lowercase values. |
553
+ | pointsChange | integer | yes | (Immutable) Points delta (+/-). Canonical name (D07). Dashboard: pointsChange, Mobile: points. |
554
+ | description | ['string', 'null'] | no | |
555
+ | reason | ['string', 'null'] | no | |
556
+ | relatedPurchaseId | ['string', 'null'] | no | (Immutable) FK → Sale.id. Linked sale that triggered this transaction. |
557
+ | relatedOrderId | ['string', 'null'] | no | (Immutable) FK → Order.id. Linked order that triggered this transaction. |
558
+ | relatedRewardId | ['string', 'null'] | no | (Immutable) FK → LoyaltyReward.id. Reward redeemed in this transaction. |
559
+ | orderId | ['string', 'null'] | no | (Immutable) FK → Order.id. Note: may overlap with relatedOrderId — naming inconsistency from Mobile. |
560
+ | sessionId | ['string', 'null'] | no | Session/booking reference. Context-dependent identifier. |
561
+ | orderAmount | ['number', 'null'] | no | |
562
+ | transactionDate | FirestoreTimestamp | yes | (Immutable) When the transaction occurred. Canonical name (D07). Dashboard: transactionDate, Mobile: createdAt. |
563
+ | pointsBalanceAfter | ['integer', 'null'] | no | (Read-only) Running balance after this transaction. Canonical name (D07). Dashboard: pointsBalanceAfter, Mobile: balanceAfter. Server-calculated. |
564
+ | createdBy | ['string', 'null'] | no | (Immutable) FK → User/staff UID who created this transaction. |
565
+ | createdByName | ['string', 'null'] | no | (Immutable, Denormalized) From User display name at creation time. |
566
+
567
+ Example:
568
+ ```json
569
+ {
570
+ "id": "bk_abc123def456",
571
+ "customerId": null,
572
+ "type": "phone",
573
+ "pointsChange": 50,
574
+ "description": null,
575
+ "reason": null,
576
+ "relatedPurchaseId": null,
577
+ "relatedOrderId": null,
578
+ "relatedRewardId": null,
579
+ "orderId": null,
580
+ "sessionId": null,
581
+ "orderAmount": null,
582
+ "transactionDate": "transactionDate",
583
+ "pointsBalanceAfter": null,
584
+ "createdBy": null,
585
+ "createdByName": null
586
+ }
587
+ ```
588
+
589
+ ### MetricsCurrent
590
+ Fields: 25 (25 required)
591
+
592
+ | Field | Type | Required | Description |
593
+ |-------|------|----------|-------------|
594
+ | todayOrdersCount | integer | yes | (Read-only) Orders created today (UTC). Resets at midnight. |
595
+ | pendingOrdersCount | integer | yes | (Read-only) Orders currently in PENDING status (all-time cumulative). Corrected by full recalc on order updates. |
596
+ | orderCompletionRate30d | number | yes | (Read-only) Percentage of orders completed or delivered in the last 30 days. Always full recalc. |
597
+ | todayPurchasesCount | integer | yes | (Read-only) Purchases created today (UTC). Resets at midnight. |
598
+ | todayPurchasesSum | number | yes | (Read-only) Total value of purchases created today (UTC). Resets at midnight. |
599
+ | averagePurchaseAmount | number | yes | (Read-only) Average purchase value across last 200 purchases (rolling, not time-windowed). |
600
+ | monthlyRevenue | number | yes | (Read-only) Total purchase value in the current calendar month (UTC). |
601
+ | monthlyPurchasesCount | integer | yes | (Read-only) Number of purchases in the current calendar month (UTC). |
602
+ | todayBookingsCount | integer | yes | (Read-only) Bookings with date == today AND status in [PENDING, CONFIRMED]. Resets at midnight. |
603
+ | bookingsCreatedToday | integer | yes | (Read-only) Bookings with createdAt today (all statuses). Resets at midnight. |
604
+ | todayBookingsConfirmedAmount | number | yes | (Read-only) Sum of totalAmount for bookings created today with status CONFIRMED or COMPLETED. Resets at midnight. |
605
+ | monthlyBookingsConfirmedAmount | number | yes | (Read-only) Sum of totalAmount for bookings created this month with status CONFIRMED or COMPLETED. |
606
+ | todayCollectedAmount | number | yes | (Read-only) Sum of totalAmount for bookings where PAYMENT_PAID_AT is today. Resets at midnight. |
607
+ | todayRevenue | number | yes | (Read-only) Sum of totalAmount for COMPLETED bookings where startDate == endDate == today (numeric YYYYMMDD). Resets at midnight. |
608
+ | bookingsPendingPaymentVerification | integer | yes | (Read-only) Bookings with status PENDING/CANCELLATION_REQUESTED and a payment proof uploaded but not yet verified. Current state, always full recalc. |
609
+ | bookingsPendingValidation | integer | yes | (Read-only) PENDING bookings with startDate within ±30 days of today. Rolling window, always full recalc. |
610
+ | bookingsPendingValidation24h | integer | yes | (Read-only) PENDING bookings created in the last 24h with startDate >= tomorrow. Always full recalc. |
611
+ | customersCount | integer | yes | (Read-only) Total customer count (all-time cumulative). |
612
+ | newCustomersThisMonth | integer | yes | (Read-only) Customers with createdAt in the current calendar month (UTC). |
613
+ | averageRating | number | yes | (Read-only) Average rating from the last 200 reviews (rolling). Always full recalc. |
614
+ | lowStockItemsCount | integer | yes | (Read-only) Active stock items where currentQuantity <= minimumQuantity. Current state, always full recalc. |
615
+ | activeRecurringPaymentsCount | integer | yes | (Read-only) Recurring payments with status ACTIVE. Current state, always full recalc. |
616
+ | monthlyRecurringRevenue | number | yes | (Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc. |
617
+ | computedForDay | string | yes | (Read-only) YYYY-MM-DD string indicating which day these metrics reflect. Used to detect day boundaries and trigger midnight resets. |
618
+ | generatedAt | FirestoreTimestamp | yes | (Read-only) Server timestamp of the last metrics write. |
619
+
620
+ Example:
621
+ ```json
622
+ {
623
+ "todayOrdersCount": 2,
624
+ "pendingOrdersCount": 2,
625
+ "orderCompletionRate30d": 0,
626
+ "todayPurchasesCount": 2,
627
+ "todayPurchasesSum": 0,
628
+ "averagePurchaseAmount": 15000,
629
+ "monthlyRevenue": 0,
630
+ "monthlyPurchasesCount": 2,
631
+ "todayBookingsCount": 2,
632
+ "bookingsCreatedToday": 0,
633
+ "todayBookingsConfirmedAmount": 15000,
634
+ "monthlyBookingsConfirmedAmount": 15000,
635
+ "todayCollectedAmount": 15000,
636
+ "todayRevenue": 0,
637
+ "bookingsPendingPaymentVerification": 0,
638
+ "bookingsPendingValidation": 0,
639
+ "bookingsPendingValidation24h": 0,
640
+ "customersCount": 2,
641
+ "newCustomersThisMonth": 0,
642
+ "averageRating": 0,
643
+ "lowStockItemsCount": 2,
644
+ "activeRecurringPaymentsCount": 2,
645
+ "monthlyRecurringRevenue": 0,
646
+ "computedForDay": "computedForDay",
647
+ "generatedAt": "generatedAt"
648
+ }
649
+ ```
650
+
651
+ ### MetricsDaily
652
+ Fields: 26 (26 required)
653
+
654
+ | Field | Type | Required | Description |
655
+ |-------|------|----------|-------------|
656
+ | todayOrdersCount | integer | yes | (Read-only) Orders created today (UTC). Resets at midnight. |
657
+ | pendingOrdersCount | integer | yes | (Read-only) Orders currently in PENDING status (all-time cumulative). Corrected by full recalc on order updates. |
658
+ | orderCompletionRate30d | number | yes | (Read-only) Percentage of orders completed or delivered in the last 30 days. Always full recalc. |
659
+ | todayPurchasesCount | integer | yes | (Read-only) Purchases created today (UTC). Resets at midnight. |
660
+ | todayPurchasesSum | number | yes | (Read-only) Total value of purchases created today (UTC). Resets at midnight. |
661
+ | averagePurchaseAmount | number | yes | (Read-only) Average purchase value across last 200 purchases (rolling, not time-windowed). |
662
+ | monthlyRevenue | number | yes | (Read-only) Total purchase value in the current calendar month (UTC). |
663
+ | monthlyPurchasesCount | integer | yes | (Read-only) Number of purchases in the current calendar month (UTC). |
664
+ | todayBookingsCount | integer | yes | (Read-only) Bookings with date == today AND status in [PENDING, CONFIRMED]. Resets at midnight. |
665
+ | bookingsCreatedToday | integer | yes | (Read-only) Bookings with createdAt today (all statuses). Resets at midnight. |
666
+ | todayBookingsConfirmedAmount | number | yes | (Read-only) Sum of totalAmount for bookings created today with status CONFIRMED or COMPLETED. Resets at midnight. |
667
+ | monthlyBookingsConfirmedAmount | number | yes | (Read-only) Sum of totalAmount for bookings created this month with status CONFIRMED or COMPLETED. |
668
+ | todayCollectedAmount | number | yes | (Read-only) Sum of totalAmount for bookings where PAYMENT_PAID_AT is today. Resets at midnight. |
669
+ | todayRevenue | number | yes | (Read-only) Sum of totalAmount for COMPLETED bookings where startDate == endDate == today (numeric YYYYMMDD). Resets at midnight. |
670
+ | bookingsPendingPaymentVerification | integer | yes | (Read-only) Bookings with status PENDING/CANCELLATION_REQUESTED and a payment proof uploaded but not yet verified. Current state, always full recalc. |
671
+ | bookingsPendingValidation | integer | yes | (Read-only) PENDING bookings with startDate within ±30 days of today. Rolling window, always full recalc. |
672
+ | bookingsPendingValidation24h | integer | yes | (Read-only) PENDING bookings created in the last 24h with startDate >= tomorrow. Always full recalc. |
673
+ | customersCount | integer | yes | (Read-only) Total customer count (all-time cumulative). |
674
+ | newCustomersThisMonth | integer | yes | (Read-only) Customers with createdAt in the current calendar month (UTC). |
675
+ | averageRating | number | yes | (Read-only) Average rating from the last 200 reviews (rolling). Always full recalc. |
676
+ | lowStockItemsCount | integer | yes | (Read-only) Active stock items where currentQuantity <= minimumQuantity. Current state, always full recalc. |
677
+ | activeRecurringPaymentsCount | integer | yes | (Read-only) Recurring payments with status ACTIVE. Current state, always full recalc. |
678
+ | monthlyRecurringRevenue | number | yes | (Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc. |
679
+ | computedForDay | string | yes | (Read-only) YYYY-MM-DD string indicating which day these metrics reflect. Used to detect day boundaries and trigger midnight resets. |
680
+ | generatedAt | FirestoreTimestamp | yes | (Read-only) Server timestamp of the last metrics write. |
681
+ | date | string | yes | (Read-only) YYYY-MM-DD document ID repeated as a field. Identifies the day this snapshot covers. |
682
+
683
+ Example:
684
+ ```json
685
+ {
686
+ "todayOrdersCount": 2,
687
+ "pendingOrdersCount": 2,
688
+ "orderCompletionRate30d": 0,
689
+ "todayPurchasesCount": 2,
690
+ "todayPurchasesSum": 0,
691
+ "averagePurchaseAmount": 15000,
692
+ "monthlyRevenue": 0,
693
+ "monthlyPurchasesCount": 2,
694
+ "todayBookingsCount": 2,
695
+ "bookingsCreatedToday": 0,
696
+ "todayBookingsConfirmedAmount": 15000,
697
+ "monthlyBookingsConfirmedAmount": 15000,
698
+ "todayCollectedAmount": 15000,
699
+ "todayRevenue": 0,
700
+ "bookingsPendingPaymentVerification": 0,
701
+ "bookingsPendingValidation": 0,
702
+ "bookingsPendingValidation24h": 0,
703
+ "customersCount": 2,
704
+ "newCustomersThisMonth": 0,
705
+ "averageRating": 0,
706
+ "lowStockItemsCount": 2,
707
+ "activeRecurringPaymentsCount": 2,
708
+ "monthlyRecurringRevenue": 0,
709
+ "computedForDay": "computedForDay",
710
+ "generatedAt": "generatedAt",
711
+ "date": "2026-02-15"
712
+ }
713
+ ```
714
+
715
+ ### MetricsMonthly
716
+ Fields: 26 (26 required)
717
+
718
+ | Field | Type | Required | Description |
719
+ |-------|------|----------|-------------|
720
+ | todayOrdersCount | integer | yes | (Read-only) Orders created today (UTC). Resets at midnight. |
721
+ | pendingOrdersCount | integer | yes | (Read-only) Orders currently in PENDING status (all-time cumulative). Corrected by full recalc on order updates. |
722
+ | orderCompletionRate30d | number | yes | (Read-only) Percentage of orders completed or delivered in the last 30 days. Always full recalc. |
723
+ | todayPurchasesCount | integer | yes | (Read-only) Purchases created today (UTC). Resets at midnight. |
724
+ | todayPurchasesSum | number | yes | (Read-only) Total value of purchases created today (UTC). Resets at midnight. |
725
+ | averagePurchaseAmount | number | yes | (Read-only) Average purchase value across last 200 purchases (rolling, not time-windowed). |
726
+ | monthlyRevenue | number | yes | (Read-only) Total purchase value in the current calendar month (UTC). |
727
+ | monthlyPurchasesCount | integer | yes | (Read-only) Number of purchases in the current calendar month (UTC). |
728
+ | todayBookingsCount | integer | yes | (Read-only) Bookings with date == today AND status in [PENDING, CONFIRMED]. Resets at midnight. |
729
+ | bookingsCreatedToday | integer | yes | (Read-only) Bookings with createdAt today (all statuses). Resets at midnight. |
730
+ | todayBookingsConfirmedAmount | number | yes | (Read-only) Sum of totalAmount for bookings created today with status CONFIRMED or COMPLETED. Resets at midnight. |
731
+ | monthlyBookingsConfirmedAmount | number | yes | (Read-only) Sum of totalAmount for bookings created this month with status CONFIRMED or COMPLETED. |
732
+ | todayCollectedAmount | number | yes | (Read-only) Sum of totalAmount for bookings where PAYMENT_PAID_AT is today. Resets at midnight. |
733
+ | todayRevenue | number | yes | (Read-only) Sum of totalAmount for COMPLETED bookings where startDate == endDate == today (numeric YYYYMMDD). Resets at midnight. |
734
+ | bookingsPendingPaymentVerification | integer | yes | (Read-only) Bookings with status PENDING/CANCELLATION_REQUESTED and a payment proof uploaded but not yet verified. Current state, always full recalc. |
735
+ | bookingsPendingValidation | integer | yes | (Read-only) PENDING bookings with startDate within ±30 days of today. Rolling window, always full recalc. |
736
+ | bookingsPendingValidation24h | integer | yes | (Read-only) PENDING bookings created in the last 24h with startDate >= tomorrow. Always full recalc. |
737
+ | customersCount | integer | yes | (Read-only) Total customer count (all-time cumulative). |
738
+ | newCustomersThisMonth | integer | yes | (Read-only) Customers with createdAt in the current calendar month (UTC). |
739
+ | averageRating | number | yes | (Read-only) Average rating from the last 200 reviews (rolling). Always full recalc. |
740
+ | lowStockItemsCount | integer | yes | (Read-only) Active stock items where currentQuantity <= minimumQuantity. Current state, always full recalc. |
741
+ | activeRecurringPaymentsCount | integer | yes | (Read-only) Recurring payments with status ACTIVE. Current state, always full recalc. |
742
+ | monthlyRecurringRevenue | number | yes | (Read-only) Sum of amount for ACTIVE + MONTHLY recurring payments. Always full recalc. |
743
+ | computedForDay | string | yes | (Read-only) YYYY-MM-DD string indicating which day these metrics reflect. Used to detect day boundaries and trigger midnight resets. |
744
+ | generatedAt | FirestoreTimestamp | yes | (Read-only) Server timestamp of the last metrics write. |
745
+ | month | string | yes | (Read-only) YYYY-MM document ID repeated as a field. Identifies the month this rollup covers. |
746
+
747
+ Example:
748
+ ```json
749
+ {
750
+ "todayOrdersCount": 2,
751
+ "pendingOrdersCount": 2,
752
+ "orderCompletionRate30d": 0,
753
+ "todayPurchasesCount": 2,
754
+ "todayPurchasesSum": 0,
755
+ "averagePurchaseAmount": 15000,
756
+ "monthlyRevenue": 0,
757
+ "monthlyPurchasesCount": 2,
758
+ "todayBookingsCount": 2,
759
+ "bookingsCreatedToday": 0,
760
+ "todayBookingsConfirmedAmount": 15000,
761
+ "monthlyBookingsConfirmedAmount": 15000,
762
+ "todayCollectedAmount": 15000,
763
+ "todayRevenue": 0,
764
+ "bookingsPendingPaymentVerification": 0,
765
+ "bookingsPendingValidation": 0,
766
+ "bookingsPendingValidation24h": 0,
767
+ "customersCount": 2,
768
+ "newCustomersThisMonth": 0,
769
+ "averageRating": 0,
770
+ "lowStockItemsCount": 2,
771
+ "activeRecurringPaymentsCount": 2,
772
+ "monthlyRecurringRevenue": 0,
773
+ "computedForDay": "computedForDay",
774
+ "generatedAt": "generatedAt",
775
+ "month": "month"
776
+ }
777
+ ```
778
+
779
+ ### Order
780
+ Fields: 47 (8 required)
781
+
782
+ | Field | Type | Required | Description |
783
+ |-------|------|----------|-------------|
784
+ | id | string | yes | (Read-only) Firestore document ID. Note: some models also have uid; see ID conventions. |
785
+ | uid | string | yes | (Read-only) Entity UID. Often mirrors id. |
786
+ | companyId | string | yes | (Immutable) FK → Company document ID. Scopes all queries. |
787
+ | orderNumber | string | yes | (Read-only) Server-generated order number. |
788
+ | status | OrderStatus | yes | Core lifecycle status (D34, MIG-11). See OrderStatus enum for the legacy value migration mapping. |
789
+ | paymentStatus | any | no | Payment lifecycle (D34). Null until payment is initiated. |
790
+ | fulfillmentStatus | any | no | Delivery/fulfillment lifecycle (D34). |
791
+ | returnStatus | any | no | Return/exchange lifecycle (D34). Null until a return or exchange is initiated. |
792
+ | deliveryType | any | no | Fulfillment channel for this order (ON_SITE, PICK_UP, DELIVERY). |
793
+ | paymentMethod | any | no | Unified payment method set with African + global methods (D02). |
794
+ | invoiceId | ['string', 'null'] | no | FK → Invoice document ID. |
795
+ | customerId | ['string', 'null'] | no | FK → Customer.id (Firestore doc ID). Used to resolve customer details. |
796
+ | customerName | ['string', 'null'] | no | (Denormalized) From Customer.name at write time. |
797
+ | customerEmail | ['string', 'null'] | no | (Denormalized) From Customer.email at write time. Canonical field per D24. |
798
+ | customerPhone | ['string', 'null'] | no | (Denormalized) From Customer.phone at write time. Canonical field per D24. |
799
+ | clientEmail | ['string', 'null'] | no | (Denormalized) Legacy — use `customerEmail`. D24 standardized to customer* prefix. |
800
+ | clientPhoneNumber | ['string', 'null'] | no | (Denormalized) Legacy — use `customerPhone`. D24 standardized to customer* prefix. |
801
+ | items | ['array', 'null'] | no | |
802
+ | amount | number | yes | Total order amount. Canonical field for the order total. |
803
+ | amountPaid | ['number', 'null'] | no | Amount of `amount` paid to date. Derived from payment allocations. |
804
+ | total | ['number', 'null'] | no | Mobile-only legacy total field. Deprecated — use `amount`. |
805
+ | createdAt | FirestoreTimestamp | yes | (Read-only) Server-generated creation timestamp. |
806
+ | orderDate | FirestoreTimestamp | yes | Firestore Timestamp serialized representation |
807
+ | PROCESSING_ON | any | no | (Read-only) Timestamp when order entered PROCESSING (D04 SCREAMING_SNAKE). MIG-01 renames from camelCase. |
808
+ | COMPLETED_ON | any | no | (Read-only) Timestamp when order completed (D04 SCREAMING_SNAKE). MIG-01 renames from camelCase. |
809
+ | CANCELLED_ON | any | no | (Read-only) Timestamp when order cancelled (D04 SCREAMING_SNAKE). MIG-01 renames from camelCase. |
810
+ | cancellationReason | ['string', 'null'] | no | |
811
+ | shippingCarrier | ['string', 'null'] | no | |
812
+ | trackingNumber | ['string', 'null'] | no | |
813
+ | estimatedDeliveryDate | any | no | Firestore Timestamp serialized representation |
814
+ | shippingCost | ['number', 'null'] | no | |
815
+ | paymentProofUrl | ['string', 'null'] | no | URL to uploaded payment proof image/document. |
816
+ | paymentProofStatus | any | no | Payment proof review status. |
817
+ | paymentProofAddedAt | any | no | (Read-only) Timestamp when proof was uploaded. |
818
+ | paymentProofAddedBy | ['string', 'null'] | no | FK → User/staff UID who uploaded the payment proof. |
819
+ | paymentProofReviewedAt | any | no | (Read-only) Timestamp when proof was reviewed. |
820
+ | paymentProofReviewedBy | ['string', 'null'] | no | FK → User/staff UID who reviewed the payment proof. |
821
+ | paymentProofRejectionReason | ['string', 'null'] | no | |
822
+ | paymentStatusChangeReason | ['string', 'null'] | no | |
823
+ | paymentStatusChangedBy | ['string', 'null'] | no | FK → User/staff UID who changed payment status. |
824
+ | paymentStatusChangedAt | any | no | (Read-only) Timestamp of last payment status change. |
825
+ | payments | ['array', 'null'] | no | [TBD/WIP — IG-4] Denormalized snapshots of CustomerPayments allocated to this order. Sync rules pending IG-4 resolution. |
826
+ | totalOverridden | ['boolean', 'null'] | no | Mobile-only. When true, total was manually overridden by user (D14). |
827
+ | notes | ['array', 'null'] | no | |
828
+ | additionalInfo | ['string', 'null'] | no | |
829
+ | appliedDiscountCode | ['string', 'null'] | no | |
830
+ | purchaseId | ['string', 'null'] | no | FK → Sale.id. Link to associated Sale document. |
831
+
832
+ Example:
833
+ ```json
834
+ {
835
+ "id": "bk_abc123def456",
836
+ "uid": "user_u8x92kqm",
837
+ "companyId": "comp_xyz789",
838
+ "orderNumber": "ORD-2026-0042",
839
+ "status": "status",
840
+ "paymentStatus": "paymentStatus",
841
+ "fulfillmentStatus": "fulfillmentStatus",
842
+ "returnStatus": "returnStatus",
843
+ "deliveryType": "deliveryType",
844
+ "paymentMethod": "paymentMethod",
845
+ "invoiceId": null,
846
+ "customerId": null,
847
+ "customerName": null,
848
+ "customerEmail": null,
849
+ "customerPhone": null,
850
+ "clientEmail": null,
851
+ "clientPhoneNumber": null,
852
+ "items": null,
853
+ "amount": 45000,
854
+ "amountPaid": null,
855
+ "total": null,
856
+ "createdAt": "createdAt",
857
+ "orderDate": "orderDate",
858
+ "PROCESSING_ON": "PROCESSING_ON",
859
+ "COMPLETED_ON": "COMPLETED_ON",
860
+ "CANCELLED_ON": "CANCELLED_ON",
861
+ "cancellationReason": null,
862
+ "shippingCarrier": null,
863
+ "trackingNumber": null,
864
+ "estimatedDeliveryDate": "estimatedDeliveryDate",
865
+ "shippingCost": null,
866
+ "paymentProofUrl": null,
867
+ "paymentProofStatus": "paymentProofStatus",
868
+ "paymentProofAddedAt": "paymentProofAddedAt",
869
+ "paymentProofAddedBy": null,
870
+ "paymentProofReviewedAt": "paymentProofReviewedAt",
871
+ "paymentProofReviewedBy": null,
872
+ "paymentProofRejectionReason": null,
873
+ "paymentStatusChangeReason": null,
874
+ "paymentStatusChangedBy": null,
875
+ "paymentStatusChangedAt": "paymentStatusChangedAt",
876
+ "payments": null,
877
+ "totalOverridden": null,
878
+ "notes": null,
879
+ "additionalInfo": null,
880
+ "appliedDiscountCode": null,
881
+ "purchaseId": null
882
+ }
883
+ ```
884
+
885
+ ### OrderItem
886
+ Fields: 12 (3 required)
887
+
888
+ | Field | Type | Required | Description |
889
+ |-------|------|----------|-------------|
890
+ | name | string | yes | Display name of the product or service. |
891
+ | quantity | number | yes | Number of units ordered. |
892
+ | price | number | yes | Unit price. Canonical name per D11. |
893
+ | productId | ['string', 'null'] | no | FK → Product document ID. |
894
+ | increment | ['number', 'null'] | no | Quantity step for bundle/bulk items (e.g. 5 for a 5-pack). Dashboard/Firebase only. |
895
+ | variantId | ['string', 'null'] | no | FK → ProductVariant ID within the product. Dashboard/Firebase only. |
896
+ | supplierId | ['string', 'null'] | no | FK → Supplier document ID. B2B orders only. |
897
+ | supplierName | ['string', 'null'] | no | (Denormalized) From Supplier.name at write time. B2B orders only. |
898
+ | sentAt | any | no | (Read-only) Timestamp when the item was sent to the kitchen. TBD/WIP — archived CBL flow. |
899
+ | startedCookingAt | any | no | (Read-only) Timestamp when the kitchen started preparing this item. TBD/WIP — archived CBL flow. |
900
+ | readyAt | any | no | (Read-only) Timestamp when the item was ready for pickup or service. TBD/WIP — archived CBL flow. |
901
+ | servedAt | any | no | (Read-only) Timestamp when the item was served to the customer. TBD/WIP — archived CBL flow. |
902
+
903
+ Example:
904
+ ```json
905
+ {
906
+ "name": "Shea Butter Body Lotion",
907
+ "quantity": 3,
908
+ "price": 15000,
909
+ "productId": null,
910
+ "increment": null,
911
+ "variantId": null,
912
+ "supplierId": null,
913
+ "supplierName": null,
914
+ "sentAt": "sentAt",
915
+ "startedCookingAt": "startedCookingAt",
916
+ "readyAt": "readyAt",
917
+ "servedAt": "servedAt"
918
+ }
919
+ ```
920
+
921
+ ### Sale
922
+ Fields: 12 (1 required)
923
+
924
+ | Field | Type | Required | Description |
925
+ |-------|------|----------|-------------|
926
+ | id | ['string', 'null'] | no | (Read-only) Firestore document ID. Note: optional in current schema — some legacy docs may lack this field. |
927
+ | companyId | ['string', 'null'] | no | (Immutable) FK → Company document ID. Note: optional in current schema — should be required (see ID consistency audit). |
928
+ | customerId | ['string', 'null'] | no | FK → Customer.id (Firestore doc ID). |
929
+ | customerName | ['string', 'null'] | no | (Denormalized) From Customer.name at write time. |
930
+ | amount | ['number', 'null'] | no | |
931
+ | items | ['array', 'null'] | no | Line items. Reuses Order item schema. |
932
+ | imageUrl | ['string', 'null'] | no | |
933
+ | notes | ['string', 'null'] | no | |
934
+ | orderId | ['string', 'null'] | no | FK → Order.id. Link to associated Order document. |
935
+ | bookingId | ['string', 'null'] | no | FK → Booking.id. Link to associated Booking document (IG-12). |
936
+ | purchaseDate | FirestoreTimestamp | yes | Firestore Timestamp serialized representation |
937
+ | createdAt | any | no | (Read-only) Server-generated creation timestamp. |
938
+
939
+ Example:
940
+ ```json
941
+ {
942
+ "id": null,
943
+ "companyId": null,
944
+ "customerId": null,
945
+ "customerName": null,
946
+ "amount": null,
947
+ "items": null,
948
+ "imageUrl": null,
949
+ "notes": null,
950
+ "orderId": null,
951
+ "bookingId": null,
952
+ "purchaseDate": "purchaseDate",
953
+ "createdAt": "createdAt"
954
+ }
955
+ ```
956
+
957
+ ### Ticket
958
+ Fields: 16 (6 required)
959
+
960
+ | Field | Type | Required | Description |
961
+ |-------|------|----------|-------------|
962
+ | id | string | yes | (Read-only) Firestore document ID. Note: Ticket does not have a uid field. |
963
+ | eventId | string | yes | (Immutable) FK → Event.id. Parent event this ticket belongs to. Set at creation. |
964
+ | companyId | string | yes | (Immutable, Denormalized) FK → Company document ID. Denormalized from parent Event for direct queries. |
965
+ | customerId | ['string', 'null'] | no | FK → Customer.id. Optional structured link to Customer document (D29). Added additively alongside denormalized fields. |
966
+ | customerName | ['string', 'null'] | no | (Denormalized) From Customer.name at write time. |
967
+ | customerEmail | ['string', 'null'] | no | (Denormalized) From Customer.email at write time. |
968
+ | customerPhone | ['string', 'null'] | no | (Denormalized) From Customer.phone at write time. |
969
+ | status | TicketStatus | yes | Ticket lifecycle (D32). SCREAMING_SNAKE per D04. MIG-10 migrates legacy lowercase values. |
970
+ | usedAt | any | no | (Read-only) Timestamp when ticket was scanned/used. Set by scan operation. |
971
+ | usedBy | ['string', 'null'] | no | (Read-only) FK → User/staff UID who scanned the ticket. |
972
+ | usedByName | ['string', 'null'] | no | (Read-only, Denormalized) From User display name at scan time. |
973
+ | price | ['number', 'null'] | no | |
974
+ | notes | ['string', 'null'] | no | |
975
+ | createdAt | FirestoreTimestamp | yes | (Read-only) Server-generated creation timestamp. |
976
+ | updatedAt | FirestoreTimestamp | yes | (Read-only) Server-generated update timestamp. |
977
+ | createdBy | ['string', 'null'] | no | (Immutable) FK → User/staff UID who created this ticket. |
978
+
979
+ Example:
980
+ ```json
981
+ {
982
+ "id": "bk_abc123def456",
983
+ "eventId": "eve_ref123",
984
+ "companyId": "comp_xyz789",
985
+ "customerId": null,
986
+ "customerName": null,
987
+ "customerEmail": null,
988
+ "customerPhone": null,
989
+ "status": "status",
990
+ "usedAt": "usedAt",
991
+ "usedBy": null,
992
+ "usedByName": null,
993
+ "price": null,
994
+ "notes": null,
995
+ "createdAt": "createdAt",
996
+ "updatedAt": "updatedAt",
997
+ "createdBy": null
998
+ }
999
+ ```
1000
+
1001
+ ---
1002
+
1003
+ ## Proposed (Work In Progress)
1004
+
1005
+ > These schemas are under review and not yet part of @valets/schema. Shape may change.
1006
+
1007
+ ### Proposed Models
1008
+
1009
+ #### PaymentSummary [proposed]
1010
+ Fields: 10 (9 required)
1011
+
1012
+ | Field | Type | Required | Description |
1013
+ |-------|------|----------|-------------|
1014
+ | id | string | yes | (Read-only) Firestore document ID. Matches the period key (e.g. "2026-03-09" for DAILY, "2026-03" for MONTHLY). |
1015
+ | companyId | string | yes | (Immutable) FK → Company document ID. Scopes all queries. |
1016
+ | period | string | yes | Period key. Determines the document ID and the time window covered. |
1017
+ | periodType | enum(3) | yes | Granularity of this summary document. |
1018
+ | paymentsByMethod | record<string,object> | yes | Payment totals broken down by PaymentMethod. Each entry holds the total amount and transaction count for the period. |
1019
+ | grandTotal | number | yes | (Read-only) Sum of all paymentsByMethod[*].total. Server-calculated. |
1020
+ | totalCount | integer | yes | (Read-only) Sum of all paymentsByMethod[*].count. Server-calculated. |
1021
+ | currency | string | yes | Currency code. Locked to XOF (West African CFA franc). |
1022
+ | lastUpdatedAt | Timestamp | no | (Read-only) Server timestamp of the last increment write. |
1023
+ | createdAt | Timestamp | yes | (Read-only) Server-generated creation timestamp. |
1024
+
1025
+ Example:
1026
+ ```json
1027
+ {
1028
+ "id": "bk_abc123def456",
1029
+ "companyId": "comp_xyz789",
1030
+ "period": "period",
1031
+ "periodType": "DAILY",
1032
+ "paymentsByMethod": {},
1033
+ "grandTotal": 0,
1034
+ "totalCount": 2,
1035
+ "currency": "XOF",
1036
+ "lastUpdatedAt": {
1037
+ "_seconds": 1768478400,
1038
+ "_nanoseconds": 0
1039
+ },
1040
+ "createdAt": {
1041
+ "_seconds": 1768478400,
1042
+ "_nanoseconds": 0
1043
+ }
1044
+ }
1045
+ ```
1046
+