@tallyui/pos 0.1.0 → 2.0.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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +1770 -42
- package/dist/index.js +3422 -204
- package/package.json +21 -10
- package/src/currency/currency-provider.tsx +12 -4
- package/src/currency/index.ts +1 -2
- package/src/index.ts +45 -4
- package/src/order/allocate-order-discount.ts +34 -0
- package/src/order/index.ts +5 -0
- package/src/order/order-builder.ts +240 -95
- package/src/order/order-manager.ts +23 -42
- package/src/order/types.ts +67 -15
- package/src/outbox/http-transport.ts +66 -0
- package/src/outbox/index.ts +7 -0
- package/src/outbox/order-outbox.ts +198 -0
- package/src/outbox/types.ts +22 -0
- package/src/outbox/use-order-outbox.ts +161 -0
- package/src/pos-order/command.ts +35 -0
- package/src/pos-order/device-id.ts +23 -0
- package/src/pos-order/finalize.ts +81 -0
- package/src/pos-order/index.ts +10 -0
- package/src/pos-order/needs-attention.ts +11 -0
- package/src/pos-order/open.ts +206 -0
- package/src/pos-order/same-sale.ts +26 -0
- package/src/pos-order/schema.ts +100 -0
- package/src/pos-order/types.ts +67 -0
- package/src/pos-order/uuidv7.ts +54 -0
- package/src/product/index.ts +2 -0
- package/src/product/search-products.ts +29 -0
- package/src/product/stock.ts +25 -0
- package/src/receipt/build-receipt-data.ts +36 -22
- package/src/receipt/types.ts +16 -10
- package/src/register/__fixtures__/closure-local-row.json +61 -0
- package/src/register/__fixtures__/closure.json +197 -0
- package/src/register/__fixtures__/corrections-dst.json +63 -0
- package/src/register/closure-document.ts +270 -0
- package/src/register/closure-rows.ts +65 -0
- package/src/register/document-labels.ts +46 -0
- package/src/register/expected.ts +66 -0
- package/src/register/export-csv.ts +74 -0
- package/src/register/facts.ts +109 -0
- package/src/register/index.ts +30 -0
- package/src/register/money.ts +18 -0
- package/src/register/movement-input.ts +59 -0
- package/src/register/register-count.denominations.ts +11 -0
- package/src/register/register-count.helpers.ts +64 -0
- package/src/register/register-document.ts +243 -0
- package/src/register/schemas.ts +175 -0
- package/src/register/session-store.ts +541 -0
- package/src/register/settled-figures.ts +98 -0
- package/src/register/use-register-session.ts +425 -0
- package/src/rxdb/index.ts +2 -0
- package/src/sale/cart.ts +22 -0
- package/src/sale/catalogue.ts +21 -0
- package/src/sale/index.ts +5 -0
- package/src/sale/use-sale.ts +337 -0
- package/src/store-settings/index.ts +5 -0
- package/src/store-settings/map-store-settings.ts +13 -0
- package/src/store-settings/resolve-store-settings.ts +65 -0
- package/src/store-settings/use-store-settings.ts +69 -0
- package/src/tax/exact.ts +155 -0
- package/src/tax/index.ts +3 -2
- package/src/tax/tax-provider.tsx +13 -12
- package/src/tax/types.ts +3 -7
- package/src/tender/index.ts +2 -0
- package/src/tender/tender-state.ts +326 -0
- package/src/currency/currency-provider.test.tsx +0 -36
- package/src/currency/format-currency.test.ts +0 -36
- package/src/currency/format-currency.ts +0 -18
- package/src/logging/logger.test.ts +0 -154
- package/src/logging/sinks.test.ts +0 -60
- package/src/order/discount-engine.test.ts +0 -152
- package/src/order/order-builder.test.ts +0 -246
- package/src/order/order-manager.test.ts +0 -209
- package/src/order/payment.test.ts +0 -91
- package/src/receipt/build-receipt-data.test.ts +0 -166
- package/src/repository/create-repository.test.ts +0 -166
- package/src/tax/calculate.test.ts +0 -59
- package/src/tax/calculate.ts +0 -32
- package/src/tax/tax-provider.test.tsx +0 -51
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The till's identity and counters: one RxDB local document, `register`. Port provenance
|
|
3
|
+
* (ADR-032 amendment 1): WCPOS `next` `3b5331b5c`.
|
|
4
|
+
*
|
|
5
|
+
* The host is whatever carries local documents: `register_sessions`, created with
|
|
6
|
+
* `registerSessionCollection` (a Tally database has no database-level local documents). WCPOS
|
|
7
|
+
* keys its buckets by site UUID plus a numeric WooCommerce store id; TallyUI keys them by one
|
|
8
|
+
* neutral `storeKey` the app derives (for example the connector id plus the base URL). WCPOS's
|
|
9
|
+
* module-level snapshots (`getRegisterSnapshot`, `getRegisterId`, `getCurrentBoundRegisterId`)
|
|
10
|
+
* are not ported: callers pass the host and read the document explicitly.
|
|
11
|
+
*/
|
|
12
|
+
import type { RxLocalDocumentMutation } from 'rxdb';
|
|
13
|
+
import { map } from 'rxjs';
|
|
14
|
+
import type { Closure } from './schemas';
|
|
15
|
+
|
|
16
|
+
export type RegisterHost = RxLocalDocumentMutation<any>;
|
|
17
|
+
|
|
18
|
+
export type RegisterCounters = {
|
|
19
|
+
last_closure_number: number;
|
|
20
|
+
perpetual_sales_total_minor: number;
|
|
21
|
+
perpetual_refunds_total_minor: number;
|
|
22
|
+
counters_started_at?: string | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* One register's counters, plus the closure being written: the draft is reserved here, in the
|
|
27
|
+
* same atomic write as its number, and `applied` once its period is in the perpetual totals.
|
|
28
|
+
*/
|
|
29
|
+
export type RegisterBucket = Partial<RegisterCounters> & {
|
|
30
|
+
closure_reservation?: { row: Closure; applied: boolean };
|
|
31
|
+
/** Closure ids the orphan-stamp sweep has already checked (ADR-032, the #158 follow-ups): a
|
|
32
|
+
* local-document field, so bounding the sweep needs no schema bump. */
|
|
33
|
+
swept_closure_ids?: string[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export interface RegisterStore {
|
|
37
|
+
sale_counter: number;
|
|
38
|
+
registers?: Record<string, RegisterBucket>;
|
|
39
|
+
/** The register (drawer) this till is bound to in this store. */
|
|
40
|
+
register_id?: string | null;
|
|
41
|
+
register_name?: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface RegisterDocument {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
/** The caller's platform, for example `'ios'` or `'web'`. */
|
|
48
|
+
platform: string;
|
|
49
|
+
created_at: string;
|
|
50
|
+
/** Keyed by `storeKey`. */
|
|
51
|
+
stores: Record<string, RegisterStore>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const REGISTER = 'register';
|
|
55
|
+
|
|
56
|
+
export function mintUuid(): string {
|
|
57
|
+
const id = globalThis.crypto?.randomUUID?.();
|
|
58
|
+
if (id) return id;
|
|
59
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
60
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
61
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
62
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
63
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function readRegister(host: RegisterHost): Promise<RegisterDocument | null> {
|
|
67
|
+
return ((await host.getLocal<RegisterDocument>(REGISTER))?.toJSON(true).data as RegisterDocument | undefined) ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Reads the register document, minting it on first use; concurrent callers get the same one. */
|
|
71
|
+
export async function ensureRegister(host: RegisterHost, platform: string): Promise<RegisterDocument> {
|
|
72
|
+
const existing = await readRegister(host);
|
|
73
|
+
if (existing) return existing;
|
|
74
|
+
const id = mintUuid().toLowerCase();
|
|
75
|
+
const data: RegisterDocument = {
|
|
76
|
+
id,
|
|
77
|
+
name: `Register ${id.slice(-4).toUpperCase()}`,
|
|
78
|
+
platform,
|
|
79
|
+
created_at: new Date().toISOString(),
|
|
80
|
+
stores: {},
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
await host.insertLocal(REGISTER, data);
|
|
84
|
+
return data;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const winner = await readRegister(host);
|
|
87
|
+
if (winner) return winner;
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function observeRegister$(host: RegisterHost) {
|
|
93
|
+
return host
|
|
94
|
+
.getLocal$<RegisterDocument>(REGISTER)
|
|
95
|
+
.pipe(map((doc) => (doc?.toJSON(true).data as RegisterDocument | undefined) ?? null));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Applies `modify` to one store's bucket in a single atomic write; `undefined` leaves the document unchanged. */
|
|
99
|
+
async function modifyStore(
|
|
100
|
+
host: RegisterHost,
|
|
101
|
+
storeKey: string,
|
|
102
|
+
modify: (store: RegisterStore | undefined) => RegisterStore | undefined,
|
|
103
|
+
) {
|
|
104
|
+
const doc = await host.getLocal<RegisterDocument>(REGISTER);
|
|
105
|
+
if (!doc) throw new Error('Register is not initialized');
|
|
106
|
+
// RxDB can batch modifiers and return the same final document to each caller, so callers
|
|
107
|
+
// capture their own result inside `modify`.
|
|
108
|
+
await doc.incrementalModify((data) => {
|
|
109
|
+
const store = modify(data.stores[storeKey]);
|
|
110
|
+
return store ? { ...data, stores: { ...data.stores, [storeKey]: store } } : data;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function getBoundRegisterId(register: RegisterDocument | null, storeKey: string): string | null {
|
|
115
|
+
return register?.stores?.[storeKey]?.register_id ?? null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function readBoundRegister(host: RegisterHost, storeKey: string) {
|
|
119
|
+
const register = await readRegister(host);
|
|
120
|
+
const id = getBoundRegisterId(register, storeKey);
|
|
121
|
+
return id ? { id, name: register?.stores[storeKey]?.register_name ?? '' } : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function bindRegister(host: RegisterHost, storeKey: string, register: { id: string | null; name: string | null }) {
|
|
125
|
+
return modifyStore(host, storeKey, (store) => ({
|
|
126
|
+
...(store ?? { sale_counter: 0 }),
|
|
127
|
+
register_id: register.id,
|
|
128
|
+
register_name: register.name,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function unbindRegister(host: RegisterHost, storeKey: string) {
|
|
133
|
+
return modifyStore(host, storeKey, (store) => store && { ...store, register_id: null, register_name: null });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function nextSaleCounter(host: RegisterHost, storeKey: string): Promise<number> {
|
|
137
|
+
let counter = 0;
|
|
138
|
+
await modifyStore(host, storeKey, (store) => {
|
|
139
|
+
counter = (store?.sale_counter ?? 0) + 1;
|
|
140
|
+
return { ...(store ?? {}), sale_counter: counter };
|
|
141
|
+
});
|
|
142
|
+
return counter;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function withRegister(store: RegisterStore | undefined, registerId: string, bucket: RegisterBucket): RegisterStore {
|
|
146
|
+
const base = store ?? { sale_counter: 0 };
|
|
147
|
+
return { ...base, registers: { ...base.registers, [registerId]: bucket } };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Mints the register's next closure number. Given the closure draft, it reserves the draft with
|
|
152
|
+
* its number and perpetual totals in the same atomic write, and a retry of the same draft gets the
|
|
153
|
+
* same number back. A different draft is refused while an earlier one is reserved but not yet
|
|
154
|
+
* applied (`closure_write_incomplete`).
|
|
155
|
+
*/
|
|
156
|
+
export async function mintClosureNumber(
|
|
157
|
+
host: RegisterHost,
|
|
158
|
+
storeKey: string,
|
|
159
|
+
registerId: string,
|
|
160
|
+
closure?: Closure,
|
|
161
|
+
): Promise<number> {
|
|
162
|
+
let number = 0;
|
|
163
|
+
await modifyStore(host, storeKey, (store) => {
|
|
164
|
+
const register = store?.registers?.[registerId] ?? {};
|
|
165
|
+
const reservation = register.closure_reservation;
|
|
166
|
+
if (closure && reservation?.row.id === closure.id && !!reservation.row.number_retried === !!closure.number_retried) {
|
|
167
|
+
number = reservation.row.number;
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
if (closure && reservation && reservation.row.id !== closure.id && !reservation.applied) {
|
|
171
|
+
throw new Error('closure_write_incomplete');
|
|
172
|
+
}
|
|
173
|
+
number = (register.last_closure_number ?? 0) + 1;
|
|
174
|
+
// A re-mint of a closure whose period was already applied to the register must not
|
|
175
|
+
// add it again: the register totals already carry it.
|
|
176
|
+
const applied = closure && reservation?.row.id === closure.id && reservation.applied ? reservation.row : null;
|
|
177
|
+
const sales = closure
|
|
178
|
+
? (register.perpetual_sales_total_minor ?? 0) - (applied?.period_sales_total_minor ?? 0) + closure.period_sales_total_minor
|
|
179
|
+
: 0;
|
|
180
|
+
const refunds = closure
|
|
181
|
+
? (register.perpetual_refunds_total_minor ?? 0) - (applied?.period_refunds_total_minor ?? 0) + closure.period_refunds_total_minor
|
|
182
|
+
: 0;
|
|
183
|
+
const next = closure
|
|
184
|
+
? {
|
|
185
|
+
row: { ...closure, number, perpetual_sales_total_minor: sales, perpetual_refunds_total_minor: refunds },
|
|
186
|
+
applied: !!applied || !!closure.number_retried,
|
|
187
|
+
}
|
|
188
|
+
: reservation;
|
|
189
|
+
return withRegister(store, registerId, {
|
|
190
|
+
...register,
|
|
191
|
+
last_closure_number: number,
|
|
192
|
+
...(next ? { closure_reservation: next } : {}),
|
|
193
|
+
// An applied period follows the closure row it came from.
|
|
194
|
+
...(applied ? { perpetual_sales_total_minor: sales, perpetual_refunds_total_minor: refunds } : {}),
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
return number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Adds a closed period to the register's perpetual totals. With `closureId`, it applies that
|
|
202
|
+
* reserved closure once: the totals become the reservation's, and a repeat does nothing.
|
|
203
|
+
*/
|
|
204
|
+
export function advancePerpetual(
|
|
205
|
+
host: RegisterHost,
|
|
206
|
+
storeKey: string,
|
|
207
|
+
registerId: string,
|
|
208
|
+
period: { salesMinor: number; refundsMinor: number; closureId?: string },
|
|
209
|
+
) {
|
|
210
|
+
return modifyStore(host, storeKey, (store) => {
|
|
211
|
+
const register = store?.registers?.[registerId] ?? {};
|
|
212
|
+
const reservation = register.closure_reservation;
|
|
213
|
+
if (period.closureId && (!reservation || reservation.row.id !== period.closureId || reservation.applied)) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
const sales = register.perpetual_sales_total_minor ?? 0;
|
|
217
|
+
const refunds = register.perpetual_refunds_total_minor ?? 0;
|
|
218
|
+
return withRegister(store, registerId, {
|
|
219
|
+
...register,
|
|
220
|
+
...(period.closureId && reservation ? { closure_reservation: { ...reservation, applied: true } } : {}),
|
|
221
|
+
perpetual_sales_total_minor: period.closureId && reservation
|
|
222
|
+
? Math.max(sales, reservation.row.perpetual_sales_total_minor)
|
|
223
|
+
: sales + period.salesMinor,
|
|
224
|
+
perpetual_refunds_total_minor: period.closureId && reservation
|
|
225
|
+
? Math.max(refunds, reservation.row.perpetual_refunds_total_minor)
|
|
226
|
+
: refunds + period.refundsMinor,
|
|
227
|
+
counters_started_at: register.counters_started_at ?? new Date().toISOString(),
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Adds closure ids to the register's swept set (ADR-032, the #158 follow-ups), once each: the
|
|
234
|
+
* orphan-stamp sweep's bound, so a closure already checked costs the sweep no `pos_orders` query.
|
|
235
|
+
*/
|
|
236
|
+
export function markClosuresSwept(host: RegisterHost, storeKey: string, registerId: string, closureIds: readonly string[]) {
|
|
237
|
+
return modifyStore(host, storeKey, (store) => {
|
|
238
|
+
const register = store?.registers?.[registerId] ?? {};
|
|
239
|
+
const swept = new Set(register.swept_closure_ids);
|
|
240
|
+
closureIds.forEach((closureId) => swept.add(closureId));
|
|
241
|
+
return withRegister(store, registerId, { ...register, swept_closure_ids: [...swept] });
|
|
242
|
+
});
|
|
243
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The register's local collections (ADR-032): sessions, cash movements and closures. Port
|
|
3
|
+
* provenance (ADR-032 amendment 1): WCPOS `next` `3b5331b5c`, where they are at versions 1, 0
|
|
4
|
+
* and 1; each starts at version 0 here.
|
|
5
|
+
*
|
|
6
|
+
* All three are local only, like `pos_orders`: the app creates them and never replicates them
|
|
7
|
+
* (a replicated collection must never take local writes, #53). WCPOS's outbox fields
|
|
8
|
+
* (`sync_status`, attempts, next time, error) and closures' server and receipt fields are gone;
|
|
9
|
+
* sending sessions to a server is registers job c.
|
|
10
|
+
*
|
|
11
|
+
* Money is integer minor units: WCPOS's decimal strings become `*_minor` integers (`amountMinor`
|
|
12
|
+
* on a movement, matching `Movement`), and a tender map (`counted`, `expected`, `variance`, keyed
|
|
13
|
+
* by method) holds integers. People (`opened_by`, `created_by`, ...) are the backend's user id
|
|
14
|
+
* as a string, since not every backend's is a number.
|
|
15
|
+
*/
|
|
16
|
+
import { addRxPlugin, type JsonSchema, type RxJsonSchema } from 'rxdb';
|
|
17
|
+
import { RxDBLocalDocumentsPlugin } from 'rxdb/plugins/local-documents';
|
|
18
|
+
|
|
19
|
+
type TenderMap = Record<string, number>;
|
|
20
|
+
|
|
21
|
+
export interface RegisterSession {
|
|
22
|
+
id: string;
|
|
23
|
+
register_id: string;
|
|
24
|
+
/** The app's neutral store key (see `bindRegister`). */
|
|
25
|
+
store_key?: string | null;
|
|
26
|
+
status: 'open' | 'counting' | 'closed';
|
|
27
|
+
/** The store's day the session opened, `yyyy-MM-dd`. */
|
|
28
|
+
business_day?: string;
|
|
29
|
+
opened_at_gmt: string;
|
|
30
|
+
opened_by?: string | null;
|
|
31
|
+
expected_float_minor?: number | null;
|
|
32
|
+
counted_float_minor: number;
|
|
33
|
+
opening_variance_minor?: number | null;
|
|
34
|
+
counting_started_at_gmt?: string | null;
|
|
35
|
+
closed_at_gmt?: string | null;
|
|
36
|
+
closed_by?: string | null;
|
|
37
|
+
approval_required?: boolean;
|
|
38
|
+
approved_by?: string | null;
|
|
39
|
+
counted?: TenderMap | null;
|
|
40
|
+
closure_id?: string | null;
|
|
41
|
+
/** The last local transition, kept for a server to acknowledge (job c). */
|
|
42
|
+
pending_status?: string | null;
|
|
43
|
+
server_status?: string | null;
|
|
44
|
+
status_at?: string | null;
|
|
45
|
+
approver_token?: string | null;
|
|
46
|
+
/** A server's expected figures; cleared before every money action (`requireOpenSession`). */
|
|
47
|
+
server_expected?: TenderMap | null;
|
|
48
|
+
server_sales_count?: number | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CashMovement {
|
|
52
|
+
id: string;
|
|
53
|
+
session_id: string;
|
|
54
|
+
type: 'paid_in' | 'paid_out' | 'no_sale' | 'void';
|
|
55
|
+
amountMinor: number;
|
|
56
|
+
reason: string;
|
|
57
|
+
created_at_gmt: string;
|
|
58
|
+
created_by?: string | null;
|
|
59
|
+
/** On a `void` row: the movement it reverses. */
|
|
60
|
+
voids?: string | null;
|
|
61
|
+
/** On a reversed movement: its `void` row. */
|
|
62
|
+
voided_by?: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Closure {
|
|
66
|
+
id: string;
|
|
67
|
+
session_id: string;
|
|
68
|
+
register_id: string;
|
|
69
|
+
store_key?: string | null;
|
|
70
|
+
business_day?: string;
|
|
71
|
+
closed_by?: string | null;
|
|
72
|
+
corrections_count?: number;
|
|
73
|
+
number: number;
|
|
74
|
+
opened_at: string;
|
|
75
|
+
closed_at: string;
|
|
76
|
+
till_expected: TenderMap;
|
|
77
|
+
expected: TenderMap;
|
|
78
|
+
counted: TenderMap;
|
|
79
|
+
variance: TenderMap;
|
|
80
|
+
period_sales_total_minor: number;
|
|
81
|
+
/** Always 0 until TallyUI has a refund model (ADR-032 amendment 1). */
|
|
82
|
+
period_refunds_total_minor: number;
|
|
83
|
+
perpetual_sales_total_minor: number;
|
|
84
|
+
perpetual_refunds_total_minor: number;
|
|
85
|
+
/** This session's orders still waiting for the outbox, and their payments' total. */
|
|
86
|
+
unsynced_count: number;
|
|
87
|
+
unsynced_total_minor: number;
|
|
88
|
+
software_version: string;
|
|
89
|
+
breakdowns: Record<string, unknown>;
|
|
90
|
+
order_ids: string[];
|
|
91
|
+
movement_ids: string[];
|
|
92
|
+
server_closure_id?: string | null;
|
|
93
|
+
printed_at?: string | null;
|
|
94
|
+
print_count: number;
|
|
95
|
+
number_retried?: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const id: JsonSchema = { type: 'string', maxLength: 36 };
|
|
99
|
+
const text: JsonSchema = { type: 'string' };
|
|
100
|
+
const nullableText: JsonSchema = { type: ['string', 'null'] };
|
|
101
|
+
const minor: JsonSchema = { type: 'integer' };
|
|
102
|
+
const nullableMinor: JsonSchema = { type: ['integer', 'null'] };
|
|
103
|
+
const tenders: JsonSchema = { type: 'object', additionalProperties: minor };
|
|
104
|
+
|
|
105
|
+
export const registerSessionSchema: RxJsonSchema<RegisterSession> = {
|
|
106
|
+
title: 'Register sessions', version: 0, primaryKey: 'id', type: 'object', additionalProperties: false,
|
|
107
|
+
properties: {
|
|
108
|
+
id, register_id: id, store_key: nullableText,
|
|
109
|
+
status: { type: 'string', enum: ['open', 'counting', 'closed'], maxLength: 8 },
|
|
110
|
+
business_day: { type: 'string', maxLength: 10 },
|
|
111
|
+
opened_at_gmt: text, opened_by: nullableText,
|
|
112
|
+
expected_float_minor: nullableMinor, counted_float_minor: minor, opening_variance_minor: nullableMinor,
|
|
113
|
+
counting_started_at_gmt: nullableText, closed_at_gmt: nullableText, closed_by: nullableText,
|
|
114
|
+
approval_required: { type: 'boolean', default: false }, approved_by: nullableText,
|
|
115
|
+
counted: { type: ['object', 'null'], additionalProperties: minor },
|
|
116
|
+
closure_id: nullableText,
|
|
117
|
+
pending_status: { ...nullableText, default: null },
|
|
118
|
+
server_status: { ...nullableText, default: null },
|
|
119
|
+
status_at: { ...nullableText, default: null },
|
|
120
|
+
approver_token: { ...nullableText, default: null },
|
|
121
|
+
server_expected: { type: ['object', 'null'], additionalProperties: minor, default: null },
|
|
122
|
+
server_sales_count: { type: ['integer', 'null'], default: null },
|
|
123
|
+
},
|
|
124
|
+
required: ['id', 'register_id', 'status', 'opened_at_gmt', 'counted_float_minor'],
|
|
125
|
+
indexes: [['register_id', 'status']],
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create `register_sessions` with this: the register document (`register-document.ts`) is a
|
|
130
|
+
* local document on this collection, since a Tally database has no database-level ones.
|
|
131
|
+
*/
|
|
132
|
+
export function registerSessionCollection() {
|
|
133
|
+
// addRxPlugin ignores a plugin it already has.
|
|
134
|
+
addRxPlugin(RxDBLocalDocumentsPlugin);
|
|
135
|
+
return { schema: registerSessionSchema, localDocuments: true } as const;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const cashMovementSchema: RxJsonSchema<CashMovement> = {
|
|
139
|
+
title: 'Cash movements', version: 0, primaryKey: 'id', type: 'object', additionalProperties: false,
|
|
140
|
+
properties: {
|
|
141
|
+
id, session_id: id,
|
|
142
|
+
type: { type: 'string', enum: ['paid_in', 'paid_out', 'no_sale', 'void'] },
|
|
143
|
+
amountMinor: minor, reason: text, created_at_gmt: text, created_by: nullableText,
|
|
144
|
+
voids: nullableText, voided_by: nullableText,
|
|
145
|
+
},
|
|
146
|
+
required: ['id', 'session_id', 'type', 'amountMinor', 'reason', 'created_at_gmt'],
|
|
147
|
+
indexes: [['session_id']],
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const closureSchema: RxJsonSchema<Closure> = {
|
|
151
|
+
title: 'Register closures', version: 0, primaryKey: 'id', type: 'object', additionalProperties: false,
|
|
152
|
+
properties: {
|
|
153
|
+
id, session_id: id, register_id: id, store_key: nullableText,
|
|
154
|
+
business_day: { type: 'string', maxLength: 10 },
|
|
155
|
+
closed_by: nullableText, corrections_count: { type: 'integer' },
|
|
156
|
+
number: { type: 'integer' }, opened_at: text, closed_at: text,
|
|
157
|
+
till_expected: tenders, expected: tenders, counted: tenders, variance: tenders,
|
|
158
|
+
period_sales_total_minor: minor, period_refunds_total_minor: minor,
|
|
159
|
+
perpetual_sales_total_minor: minor, perpetual_refunds_total_minor: minor,
|
|
160
|
+
unsynced_count: { type: 'integer' }, unsynced_total_minor: minor,
|
|
161
|
+
software_version: text,
|
|
162
|
+
breakdowns: { type: 'object', additionalProperties: true },
|
|
163
|
+
order_ids: { type: 'array', items: text },
|
|
164
|
+
movement_ids: { type: 'array', items: text },
|
|
165
|
+
server_closure_id: { ...nullableText, default: null },
|
|
166
|
+
printed_at: { ...nullableText, default: null },
|
|
167
|
+
print_count: { type: 'integer', default: 0 },
|
|
168
|
+
number_retried: { type: 'boolean', default: false },
|
|
169
|
+
},
|
|
170
|
+
required: ['id', 'session_id', 'register_id', 'number', 'opened_at', 'closed_at', 'till_expected', 'expected',
|
|
171
|
+
'counted', 'variance', 'period_sales_total_minor', 'period_refunds_total_minor', 'perpetual_sales_total_minor',
|
|
172
|
+
'perpetual_refunds_total_minor', 'unsynced_count', 'unsynced_total_minor', 'software_version', 'breakdowns',
|
|
173
|
+
'order_ids', 'movement_ids', 'print_count'],
|
|
174
|
+
indexes: [['register_id'], ['session_id']],
|
|
175
|
+
};
|