@tallyui/pos 0.1.0 → 3.0.0-next.0

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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +2031 -43
  3. package/dist/index.js +4629 -205
  4. package/package.json +23 -12
  5. package/src/currency/currency-provider.tsx +12 -4
  6. package/src/currency/index.ts +1 -2
  7. package/src/index.ts +53 -4
  8. package/src/order/allocate-order-discount.ts +34 -0
  9. package/src/order/index.ts +8 -0
  10. package/src/order/order-builder.ts +256 -95
  11. package/src/order/order-manager.ts +26 -43
  12. package/src/order/tax-figures.ts +23 -0
  13. package/src/order/types.ts +73 -15
  14. package/src/outbox/backend-not-found.ts +27 -0
  15. package/src/outbox/http-transport.ts +67 -0
  16. package/src/outbox/index.ts +9 -0
  17. package/src/outbox/logger.ts +3 -0
  18. package/src/outbox/order-outbox.ts +502 -0
  19. package/src/outbox/register-outbox.ts +250 -0
  20. package/src/outbox/types.test-d.ts +22 -0
  21. package/src/outbox/types.ts +36 -0
  22. package/src/outbox/use-order-outbox.ts +175 -0
  23. package/src/pos-order/__fixtures__/order-create-v3.json +97 -0
  24. package/src/pos-order/command.ts +123 -0
  25. package/src/pos-order/device-id.ts +23 -0
  26. package/src/pos-order/finalize.ts +219 -0
  27. package/src/pos-order/index.ts +10 -0
  28. package/src/pos-order/needs-attention.ts +16 -0
  29. package/src/pos-order/open.ts +269 -0
  30. package/src/pos-order/same-sale.ts +26 -0
  31. package/src/pos-order/schema.ts +130 -0
  32. package/src/pos-order/types.ts +93 -0
  33. package/src/pos-order/uuidv7.ts +54 -0
  34. package/src/product/index.ts +2 -0
  35. package/src/product/search-products.ts +32 -0
  36. package/src/product/stock.ts +25 -0
  37. package/src/receipt/build-receipt-data.ts +39 -23
  38. package/src/receipt/types.ts +17 -10
  39. package/src/register/__fixtures__/closure-local-row.json +61 -0
  40. package/src/register/__fixtures__/closure.json +197 -0
  41. package/src/register/__fixtures__/corrections-dst.json +63 -0
  42. package/src/register/closure-document.ts +277 -0
  43. package/src/register/closure-rows.ts +65 -0
  44. package/src/register/document-labels.ts +46 -0
  45. package/src/register/expected.ts +66 -0
  46. package/src/register/export-csv.ts +74 -0
  47. package/src/register/facts.ts +109 -0
  48. package/src/register/index.ts +32 -0
  49. package/src/register/money.ts +18 -0
  50. package/src/register/movement-input.ts +59 -0
  51. package/src/register/register-commands.ts +147 -0
  52. package/src/register/register-count.denominations.ts +11 -0
  53. package/src/register/register-count.helpers.ts +64 -0
  54. package/src/register/register-document.ts +268 -0
  55. package/src/register/schemas.ts +175 -0
  56. package/src/register/session-store.ts +570 -0
  57. package/src/register/settled-figures.ts +98 -0
  58. package/src/register/use-register-session.ts +469 -0
  59. package/src/rxdb/index.ts +2 -0
  60. package/src/sale/cart.ts +23 -0
  61. package/src/sale/catalogue.ts +21 -0
  62. package/src/sale/index.ts +5 -0
  63. package/src/sale/use-sale.ts +394 -0
  64. package/src/store-settings/index.ts +5 -0
  65. package/src/store-settings/map-store-settings.ts +18 -0
  66. package/src/store-settings/resolve-store-settings.ts +65 -0
  67. package/src/store-settings/use-store-settings.ts +84 -0
  68. package/src/tax/exact.ts +218 -0
  69. package/src/tax/index.ts +4 -3
  70. package/src/tax/tax-provider.tsx +35 -12
  71. package/src/tax/types.ts +8 -6
  72. package/src/tender/index.ts +2 -0
  73. package/src/tender/tender-state.ts +326 -0
  74. package/src/currency/currency-provider.test.tsx +0 -36
  75. package/src/currency/format-currency.test.ts +0 -36
  76. package/src/currency/format-currency.ts +0 -18
  77. package/src/logging/logger.test.ts +0 -154
  78. package/src/logging/sinks.test.ts +0 -60
  79. package/src/order/discount-engine.test.ts +0 -152
  80. package/src/order/order-builder.test.ts +0 -246
  81. package/src/order/order-manager.test.ts +0 -209
  82. package/src/order/payment.test.ts +0 -91
  83. package/src/receipt/build-receipt-data.test.ts +0 -166
  84. package/src/repository/create-repository.test.ts +0 -166
  85. package/src/tax/calculate.test.ts +0 -59
  86. package/src/tax/calculate.ts +0 -32
  87. package/src/tax/tax-provider.test.tsx +0 -51
@@ -1,18 +1,27 @@
1
- import type { Order } from '../order/types';
1
+ import type { Discount, SentOrder } from '../order/types';
2
+ import { roundMicrosToMinor, taxLinesByRate } from '../tax/exact';
2
3
  import type { ReceiptConfig, ReceiptData } from './types';
3
4
 
4
- export function buildReceiptData(order: Order, config: ReceiptConfig): ReceiptData {
5
- const taxByRate = new Map<number, number>();
6
- for (const line of order.lineItems) {
7
- const existing = taxByRate.get(line.taxRate) ?? 0;
8
- taxByRate.set(line.taxRate, existing + line.taxAmount);
9
- }
5
+ export function buildReceiptData(order: SentOrder, config: ReceiptConfig): ReceiptData {
6
+ // Drops the shared helper's `netMinor` (the Z report's own use): the receipt's tax summary
7
+ // never showed it, and an exact-shape test elsewhere in this package pins that.
8
+ const taxLines = taxLinesByRate(order.lineItems, order.taxMinor, config.taxLabels, order.taxRounding).map(
9
+ ({ label, code, ratePpm, amountMinor }) => ({ label, code, ratePpm, amountMinor }),
10
+ );
10
11
 
11
- const taxLines = Array.from(taxByRate.entries()).map(([rate, amount]) => ({
12
- label: config.taxLabels?.[rate] ?? `Tax ${Math.round(rate * 100)}%`,
13
- rate,
14
- amount: Math.round(amount * 100) / 100,
15
- }));
12
+ // Every receipt line is shown in the order's mode. A line whose price had the other mode is
13
+ // converted by its tax share as the order total rounds it: the rounded running tax sum,
14
+ // started from the exclusive lines the order already rounds, so the shares add up exactly.
15
+ let taxSoFar = order.lineItems.reduce(
16
+ (sum, li) => li.taxInclusive || li.priceTaxModeConverted ? sum : sum + BigInt(li.taxMicros), 0n);
17
+ const lineTotals = order.lineItems.map((li) => {
18
+ if (!li.priceTaxModeConverted) return li.netMinor;
19
+ const share = roundMicrosToMinor(taxSoFar + BigInt(li.taxMicros)) - roundMicrosToMinor(taxSoFar);
20
+ taxSoFar += BigInt(li.taxMicros);
21
+ return li.taxInclusive ? li.netMinor - share : li.netMinor + share;
22
+ });
23
+ const label = (d: Discount) => d.label ?? d.couponCode ?? `${d.type} discount`;
24
+ const customer = order.customer?.name || order.customer?.email;
16
25
 
17
26
  return {
18
27
  header: {
@@ -21,32 +30,39 @@ export function buildReceiptData(order: Order, config: ReceiptConfig): ReceiptDa
21
30
  orderNumber: order.id,
22
31
  date: order.createdAt,
23
32
  cashier: config.cashier,
33
+ ...(customer ? { customer } : {}),
24
34
  register: config.register,
25
35
  },
26
- lineItems: order.lineItems.map((li) => ({
36
+ lineItems: order.lineItems.map((li, index) => ({
27
37
  name: li.name,
28
38
  sku: li.sku,
29
39
  quantity: li.quantity,
30
- unitPrice: li.price,
31
- lineTotal: li.lineTotal,
40
+ unitPriceMinor: li.unitPriceMinor,
41
+ lineTotalMinor: lineTotals[index],
42
+ // Line plus allocated order discounts, in the line's own mode; lineTotalMinor is already after it (ADR-062).
43
+ ...(li.discountMinor > 0 ? { discountMinor: li.discountMinor } : {}),
44
+ displayAmountMinor: order.display.lines[index].amountMinor,
45
+ displayDiscounts: order.display.lines[index].discounts.map((row, i) => ({ label: label(li.discounts[i]), amountMinor: row.amountMinor })),
32
46
  })),
33
47
  discounts: order.discounts.map((d) => ({
34
- label: d.label ?? d.couponCode ?? `${d.type} discount`,
35
- amount: d.amount,
48
+ label: label(d),
49
+ amountMinor: d.amountMinor,
36
50
  })),
51
+ orderDiscountMinor: order.display.orderDiscountMinor,
37
52
  totals: {
38
- subtotal: order.subtotal,
39
- discountTotal: order.discountTotal,
53
+ taxInclusive: order.display.taxInclusive,
54
+ subtotalMinor: order.display.subtotalMinor,
55
+ discountMinor: order.display.discountMinor,
40
56
  taxLines,
41
- taxTotal: order.taxTotal,
42
- total: order.total,
57
+ taxMinor: order.display.taxMinor,
58
+ totalMinor: order.display.totalMinor,
43
59
  },
44
60
  payments: order.payments.map((p) => ({
45
61
  method: p.method,
46
- amount: p.amount,
62
+ amountMinor: p.amountMinor,
47
63
  reference: p.reference,
48
64
  })),
49
- changeDue: order.changeDue,
65
+ changeDueMinor: order.changeDueMinor,
50
66
  footer: {
51
67
  note: order.note || undefined,
52
68
  barcode: order.id,
@@ -2,8 +2,12 @@ export interface ReceiptLineItem {
2
2
  name: string;
3
3
  sku: string;
4
4
  quantity: number;
5
- unitPrice: number;
6
- lineTotal: number;
5
+ unitPriceMinor: number;
6
+ /** After every discount, including the line's order share, in the order's mode. Not the figure to print above the subtotal: use displayAmountMinor. */
7
+ lineTotalMinor: number;
8
+ discountMinor?: number; // this line's discounts, already taken off lineTotalMinor; absent when 0
9
+ displayAmountMinor: number; // before any discount, in the display mode; Σ = totals.subtotalMinor (ADR-063)
10
+ displayDiscounts: { label: string; amountMinor: number }[]; // this line's own discounts, in the display mode
7
11
  }
8
12
 
9
13
  export interface ReceiptData {
@@ -13,19 +17,22 @@ export interface ReceiptData {
13
17
  orderNumber: string;
14
18
  date: string;
15
19
  cashier?: string;
20
+ customer?: string;
16
21
  register?: string;
17
22
  };
18
23
  lineItems: ReceiptLineItem[];
19
- discounts: { label: string; amount: number }[];
24
+ discounts: { label: string; amountMinor: number }[];
25
+ orderDiscountMinor: number; // the order discounts as one row, in the display mode; + Σ displayDiscounts = totals.discountMinor
20
26
  totals: {
21
- subtotal: number;
22
- discountTotal: number;
23
- taxLines: { label: string; rate: number; amount: number }[];
24
- taxTotal: number;
25
- total: number;
27
+ taxInclusive: boolean; // from order.display: tax is added (false) or included (true)
28
+ subtotalMinor: number; // order.display.subtotalMinor: before discounts, in the display mode
29
+ discountMinor: number; // order.display.discountMinor: every discount, in the display mode (>= 0)
30
+ taxLines: { label: string; code?: string; ratePpm: number; amountMinor: number }[];
31
+ taxMinor: number; // order.display.taxMinor
32
+ totalMinor: number; // order.display.totalMinor
26
33
  };
27
- payments: { method: string; amount: number; reference?: string }[];
28
- changeDue: number;
34
+ payments: { method: string; amountMinor: number; reference?: string }[];
35
+ changeDueMinor: number;
29
36
  footer: {
30
37
  note?: string;
31
38
  barcode?: string;
@@ -0,0 +1,61 @@
1
+ {
2
+ "id": "closure-fixture",
3
+ "session_id": "session-fixture",
4
+ "register_id": "register-fixture",
5
+ "store_key": "1",
6
+ "business_day": "2026-09-11",
7
+ "closed_by": "1",
8
+ "corrections_count": 0,
9
+ "number": 42,
10
+ "opened_at": "2026-09-11T08:00:00Z",
11
+ "closed_at": "2026-09-11T17:00:00Z",
12
+ "till_expected": { "cash": 18000, "card": 12000 },
13
+ "expected": { "cash": 18000, "card": 12000 },
14
+ "counted": { "cash": 17800, "card": 12000 },
15
+ "variance": { "cash": -200, "card": 0 },
16
+ "period_sales_total_minor": 25000,
17
+ "period_refunds_total_minor": 5000,
18
+ "perpetual_sales_total_minor": 525000,
19
+ "perpetual_refunds_total_minor": 25000,
20
+ "unsynced_count": 2,
21
+ "unsynced_total_minor": 1200,
22
+ "software_version": "preview",
23
+ "breakdowns": {
24
+ "currency": "USD",
25
+ "transaction_count": 12,
26
+ "refund_count": 2,
27
+ "cashiers": [{ "id": "1", "name": "Alex" }],
28
+ "register_name": "Main register",
29
+ "opened_by": "1",
30
+ "approved_by": "1",
31
+ "opened_by_name": "Alex",
32
+ "closed_by_name": "Alex",
33
+ "approved_by_name": "Alex",
34
+ "payment_methods": {
35
+ "cash": { "sales_minor": 13000, "refunds_minor": 5000 },
36
+ "card": { "sales_minor": 12000, "refunds_minor": 0 }
37
+ },
38
+ "tax_rates": {
39
+ "vat": { "name": "VAT 20%", "net_minor": 16667, "tax_minor": 3333, "gross_minor": 20000 }
40
+ },
41
+ "opening_float": { "expected_minor": 10000, "counted_minor": 10000, "variance_minor": 0 },
42
+ "movements": [
43
+ {
44
+ "id": "movement-fixture",
45
+ "type": "paid_out",
46
+ "amountMinor": 500,
47
+ "reason": "Petty cash",
48
+ "created_at_gmt": "2026-09-11T10:00:00Z",
49
+ "created_by": "1",
50
+ "voids": null,
51
+ "voided_by": "sample-void"
52
+ }
53
+ ]
54
+ },
55
+ "order_ids": [],
56
+ "movement_ids": [],
57
+ "server_closure_id": null,
58
+ "printed_at": null,
59
+ "print_count": 0,
60
+ "number_retried": false
61
+ }
@@ -0,0 +1,197 @@
1
+ {
2
+ "closure": {
3
+ "number": 42,
4
+ "has_sales": true,
5
+ "has_payment_methods": true,
6
+ "has_tax_rates": true,
7
+ "has_perpetual": true,
8
+ "has_movements": true,
9
+
10
+ "store_id": 1,
11
+ "opened_at_gmt": "2026-09-11 08:00:00",
12
+ "closed_at_gmt": "2026-09-11 17:00:00",
13
+ "opened_by": 1,
14
+ "closed_by": 1,
15
+ "approved_by": 1,
16
+ "expected": {
17
+ "cash": "180.0000",
18
+ "card": "120.0000"
19
+ },
20
+ "counted": {
21
+ "cash": "178.0000",
22
+ "card": "120.0000"
23
+ },
24
+ "variance": {
25
+ "cash": "-2.0000",
26
+ "card": "0.0000"
27
+ },
28
+ "period_sales_total": "250.0000",
29
+ "period_refunds_total": "50.0000",
30
+ "perpetual_sales_total": "5250.0000",
31
+ "perpetual_refunds_total": "250.0000",
32
+ "unsynced_count": 2,
33
+ "unsynced_total": "12.0000",
34
+ "breakdowns": {
35
+ "currency": "USD",
36
+ "payment_methods": [
37
+ {
38
+ "method": "cash",
39
+ "name": "Cash",
40
+ "sales": "130.0000",
41
+ "refunds": "50.0000",
42
+ "sales_display": "$130.00",
43
+ "refunds_display": "$50.00"
44
+ },
45
+ {
46
+ "method": "card",
47
+ "name": "Card",
48
+ "sales": "120.0000",
49
+ "refunds": "0.0000",
50
+ "sales_display": "$120.00",
51
+ "refunds_display": "$0.00"
52
+ }
53
+ ],
54
+ "tax_rates": [
55
+ {
56
+ "name": "VAT 20%",
57
+ "net": "166.6667",
58
+ "tax": "33.3333",
59
+ "gross": "200.0000",
60
+ "net_display": "$166.67",
61
+ "tax_display": "$33.33",
62
+ "gross_display": "$200.00"
63
+ }
64
+ ],
65
+ "opening_float": {
66
+ "expected": "100.0000",
67
+ "counted": "100.0000",
68
+ "variance": "0.0000",
69
+ "expected_display": "$100.00",
70
+ "counted_display": "$100.00",
71
+ "variance_display": "$0.00"
72
+ },
73
+ "movements": [
74
+ {
75
+ "created_at_gmt": "2026-09-11 10:00:00",
76
+ "type": "paid_out",
77
+ "amount": "5.0000",
78
+ "reason": "Petty cash",
79
+ "voided_by": "sample-void",
80
+ "amount_display": "$5.00",
81
+ "created_at": {"datetime": "Sep 11, 2026, 12:00", "date": "Sep 11, 2026", "time": "12:00", "datetime_short": "9/11/26, 12:00", "datetime_long": "September 11, 2026 at 12:00 GMT+2", "datetime_full": "Friday, September 11, 2026 at 12:00 Central European Summer Time", "date_short": "9/11/26", "date_long": "September 11, 2026", "date_full": "Friday, September 11, 2026", "date_ymd": "2026-09-11", "date_dmy": "11/09/2026", "date_mdy": "09/11/2026", "weekday_short": "Fri", "weekday_long": "Friday", "day": "11", "month": "09", "month_short": "Sep", "month_long": "September", "year": "2026"},
82
+ "type_label": "Paid out",
83
+ "voided": true
84
+ }
85
+ ],
86
+ "transaction_count": 12,
87
+ "refund_count": 2,
88
+ "cashiers": [
89
+ {
90
+ "id": 1,
91
+ "name": "Alex"
92
+ }
93
+ ],
94
+ "labels": {
95
+ "opened_by_name": "Alex",
96
+ "closed_by_name": "Alex",
97
+ "approved_by_name": "Alex"
98
+ }
99
+ },
100
+ "tenders": [
101
+ {
102
+ "name": "cash",
103
+ "label": "Cash",
104
+ "has_variance": true,
105
+ "variance_absolute_display": "$2.00",
106
+ "expected": "180.0000",
107
+ "counted": "178.0000",
108
+ "variance": "-2.0000",
109
+ "variance_label": "Short",
110
+ "expected_display": "$180.00",
111
+ "counted_display": "$178.00",
112
+ "variance_display": "-$2.00"
113
+ },
114
+ {
115
+ "name": "card",
116
+ "label": "Card",
117
+ "has_variance": false,
118
+ "variance_absolute_display": "$0.00",
119
+ "expected": "120.0000",
120
+ "counted": "120.0000",
121
+ "variance": "0.0000",
122
+ "variance_label": "Exact",
123
+ "expected_display": "$120.00",
124
+ "counted_display": "$120.00",
125
+ "variance_display": "$0.00"
126
+ }
127
+ ],
128
+ "period_sales_total_display": "$250.00",
129
+ "period_refunds_total_display": "$50.00",
130
+ "perpetual_sales_total_display": "$5,250.00",
131
+ "perpetual_refunds_total_display": "$250.00",
132
+ "unsynced_total_display": "$12.00",
133
+ "opened_at": {"datetime": "Sep 11, 2026, 10:00", "date": "Sep 11, 2026", "time": "10:00", "datetime_short": "9/11/26, 10:00", "datetime_long": "September 11, 2026 at 10:00 GMT+2", "datetime_full": "Friday, September 11, 2026 at 10:00 Central European Summer Time", "date_short": "9/11/26", "date_long": "September 11, 2026", "date_full": "Friday, September 11, 2026", "date_ymd": "2026-09-11", "date_dmy": "11/09/2026", "date_mdy": "09/11/2026", "weekday_short": "Fri", "weekday_long": "Friday", "day": "11", "month": "09", "month_short": "Sep", "month_long": "September", "year": "2026"},
134
+ "closed_at": {"datetime": "Sep 11, 2026, 19:00", "date": "Sep 11, 2026", "time": "19:00", "datetime_short": "9/11/26, 19:00", "datetime_long": "September 11, 2026 at 19:00 GMT+2", "datetime_full": "Friday, September 11, 2026 at 19:00 Central European Summer Time", "date_short": "9/11/26", "date_long": "September 11, 2026", "date_full": "Friday, September 11, 2026", "date_ymd": "2026-09-11", "date_dmy": "11/09/2026", "date_mdy": "09/11/2026", "weekday_short": "Fri", "weekday_long": "Friday", "day": "11", "month": "09", "month_short": "Sep", "month_long": "September", "year": "2026"}
135
+ },
136
+ "order": { "currency": "USD" },
137
+ "i18n": {
138
+ "x_report": "X-report",
139
+ "closure": "Closure",
140
+ "opened": "Opened",
141
+ "closed": "Closed",
142
+ "approver": "Approver",
143
+ "opening_float": "Opening float",
144
+ "expected": "Expected",
145
+ "counted": "Counted",
146
+ "variance": "Variance",
147
+ "tenders": "Tenders",
148
+ "tender": "Tender",
149
+ "cash_movements": "Cash movements",
150
+ "time": "Time",
151
+ "type": "Type",
152
+ "amount": "Amount",
153
+ "reason": "Reason",
154
+ "sales": "Sales",
155
+ "period_sales": "Period sales",
156
+ "period_refunds": "Period refunds",
157
+ "transactions": "Transactions",
158
+ "refunds": "Refunds",
159
+ "payment_method": "Payment method",
160
+ "cashiers": "Cashiers",
161
+ "tax_rates": "Tax rates",
162
+ "tax_rate": "Tax rate",
163
+ "net": "Net",
164
+ "tax": "Tax",
165
+ "gross": "Gross",
166
+ "perpetual_totals": "Perpetual totals",
167
+ "perpetual_sales": "Perpetual sales",
168
+ "perpetual_refunds": "Perpetual refunds",
169
+ "unsynced_sales": "sales not yet on the server",
170
+ "short": "Short",
171
+ "over": "Over",
172
+ "exact": "Exact",
173
+ "voided": "Voided",
174
+ "paid_in": "Paid in",
175
+ "paid_out": "Paid out",
176
+ "no_sale": "No sale",
177
+ "void": "Void",
178
+ "copy": "COPY"
179
+ },
180
+ "register": {
181
+ "name": "Main register"
182
+ },
183
+ "software": {
184
+ "name": "WCPOS",
185
+ "plugin_version": "preview"
186
+ },
187
+ "fiscal": {
188
+ "document_type": "closure",
189
+ "receipt_number": "42",
190
+ "is_sale_document": false,
191
+ "is_refund_document": false,
192
+ "is_closure_document": true,
193
+ "is_x_report": false,
194
+ "is_reprint": false,
195
+ "reprint_count": 0
196
+ }
197
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "timezone": "America/Los_Angeles",
3
+ "now": "2026-11-01T20:00:00Z",
4
+ "closures": [
5
+ {
6
+ "id": "before-fallback",
7
+ "number": 42,
8
+ "business_day": "2026-10-31",
9
+ "opened_at": "2026-10-31T23:00:00Z",
10
+ "closed_at": "2026-11-01T08:30:00Z"
11
+ },
12
+ {
13
+ "id": "after-fallback",
14
+ "number": 43,
15
+ "business_day": "2026-11-01",
16
+ "opened_at": "2026-11-01T07:00:00Z",
17
+ "closed_at": "2026-11-01T09:30:00Z"
18
+ }
19
+ ],
20
+ "corrections": [
21
+ {
22
+ "id": 1,
23
+ "type": "late_sale",
24
+ "created_at": "2026-11-01 08:45:00",
25
+ "actor": { "id": "1", "name": "Alex" },
26
+ "approver": null,
27
+ "reason": "Overnight sale synced",
28
+ "figures": {
29
+ "expected_delta": { "cash": 1 },
30
+ "sales_delta": 1,
31
+ "refunds_delta": 0
32
+ }
33
+ },
34
+ {
35
+ "id": 2,
36
+ "type": "recount",
37
+ "created_at": "2026-11-01 09:45:00",
38
+ "actor": { "id": "1", "name": "Alex" },
39
+ "approver": { "id": "2", "name": "Manager" },
40
+ "reason": "Drawer recounted after the clock change",
41
+ "figures": {
42
+ "counted": { "cash": 18000 },
43
+ "variance": { "cash": 0 }
44
+ }
45
+ }
46
+ ],
47
+ "settled": {
48
+ "expected": { "cash": 18001, "card": 12000 },
49
+ "counted": { "cash": 18000, "card": 12000 },
50
+ "variance": { "cash": -1, "card": 0 },
51
+ "period_sales_total_minor": 25001,
52
+ "period_refunds_total_minor": 5000,
53
+ "perpetual_sales_total_minor": 525001,
54
+ "perpetual_refunds_total_minor": 25000
55
+ },
56
+ "touched": [
57
+ "counted.cash",
58
+ "expected.cash",
59
+ "period_sales_total_minor",
60
+ "perpetual_sales_total_minor",
61
+ "variance.cash"
62
+ ]
63
+ }