@retalia/pos-components 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/fesm2022/retalia-pos-components-services.mjs +171 -0
- package/fesm2022/retalia-pos-components-services.mjs.map +1 -0
- package/fesm2022/retalia-pos-components.mjs +1089 -0
- package/fesm2022/retalia-pos-components.mjs.map +1 -0
- package/package.json +43 -17
- package/types/retalia-pos-components-services.d.ts +168 -0
- package/types/retalia-pos-components.d.ts +300 -0
- package/eslint.config.js +0 -32
- package/ng-package.json +0 -14
- package/src/lib/basket/pos-basket.html +0 -124
- package/src/lib/basket/pos-basket.scss +0 -285
- package/src/lib/basket/pos-basket.spec.ts +0 -198
- package/src/lib/basket/pos-basket.stories.ts +0 -114
- package/src/lib/basket/pos-basket.ts +0 -142
- package/src/lib/item-entry/pos-item-entry.html +0 -41
- package/src/lib/item-entry/pos-item-entry.scss +0 -145
- package/src/lib/item-entry/pos-item-entry.spec.ts +0 -123
- package/src/lib/item-entry/pos-item-entry.stories.ts +0 -31
- package/src/lib/item-entry/pos-item-entry.ts +0 -81
- package/src/lib/login/pos-login.html +0 -82
- package/src/lib/login/pos-login.scss +0 -238
- package/src/lib/login/pos-login.spec.ts +0 -89
- package/src/lib/login/pos-login.stories.ts +0 -36
- package/src/lib/login/pos-login.ts +0 -150
- package/src/lib/receipt/pos-receipt.html +0 -116
- package/src/lib/receipt/pos-receipt.scss +0 -367
- package/src/lib/receipt/pos-receipt.spec.ts +0 -188
- package/src/lib/receipt/pos-receipt.stories.ts +0 -163
- package/src/lib/receipt/pos-receipt.ts +0 -129
- package/src/lib/tender/pos-tender.html +0 -79
- package/src/lib/tender/pos-tender.scss +0 -318
- package/src/lib/tender/pos-tender.spec.ts +0 -150
- package/src/lib/tender/pos-tender.stories.ts +0 -47
- package/src/lib/tender/pos-tender.ts +0 -154
- package/src/lib/tokens/pos-theme-css.spec.ts +0 -75
- package/src/lib/tokens/pos-theme-css.ts +0 -123
- package/src/lib/tokens/pos-token-catalog.ts +0 -514
- package/src/lib/tokens/pos-tokens.html +0 -351
- package/src/lib/tokens/pos-tokens.scss +0 -437
- package/src/lib/tokens/pos-tokens.spec.ts +0 -164
- package/src/lib/tokens/pos-tokens.stories.ts +0 -22
- package/src/lib/tokens/pos-tokens.ts +0 -208
- package/src/public-api.ts +0 -37
- package/tsconfig.lib.json +0 -14
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -11
- /package/{src/styles → styles}/styles.css +0 -0
- /package/{src/styles → styles}/tokens.css +0 -0
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Presentational Adaptive POS Angular components. State in, typed intents out — no service calls.
|
|
4
4
|
|
|
5
|
+
Optional HTTP clients live behind a **separate entry point**: `@retalia/pos-components/services`. Components never import that entry. The host calls a service, then passes display state into the UI.
|
|
6
|
+
|
|
5
7
|
Requires **Angular 21** (`@angular/core` and `@angular/common` `^21.2.0`).
|
|
6
8
|
|
|
7
9
|
## Install
|
|
@@ -10,6 +12,27 @@ Requires **Angular 21** (`@angular/core` and `@angular/common` `^21.2.0`).
|
|
|
10
12
|
npm i @retalia/pos-components
|
|
11
13
|
```
|
|
12
14
|
|
|
15
|
+
## Services
|
|
16
|
+
|
|
17
|
+
Import from `@retalia/pos-components/services`, never from a `pos-*` component.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
ClerkAuthService,
|
|
22
|
+
POS_API_CONFIG,
|
|
23
|
+
PosCommandsClient,
|
|
24
|
+
posApiInterceptor,
|
|
25
|
+
} from '@retalia/pos-components/services';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| | |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `posApiInterceptor` | Reads `portalApiToken` + `contextShopId` cookies and sets `Authorization` / `Shop-Context`. Provide via `provideHttpClient(withInterceptors([posApiInterceptor]))`. |
|
|
31
|
+
| `ClerkAuthService` | `signIn(clerkNumber, password)` — GET `/api/v1.0/clerks/{number}`, password check in the service (stub until ID-647). Call this from the host on `login.submit`. |
|
|
32
|
+
| `PosCommandsClient` | `addItem` / `changeQuantity` / `clearBasket` / `tenderCash` / `newSale` — each returns a `PosStateEnvelope`. |
|
|
33
|
+
|
|
34
|
+
In local Adaptive POS, paste a Portal Bearer into the `portalApiToken` cookie and the shop id into `contextShopId`. Do not send clerk credentials to `POST /token`.
|
|
35
|
+
|
|
13
36
|
## Styles
|
|
14
37
|
|
|
15
38
|
Add the package stylesheet once in the host app (e.g. `angular.json` `styles`):
|
|
@@ -83,7 +106,7 @@ export class App {
|
|
|
83
106
|
if (intent.type !== 'login.submit') {
|
|
84
107
|
return;
|
|
85
108
|
}
|
|
86
|
-
// Host (or
|
|
109
|
+
// Host (or package ClerkAuthService) calls the API, then:
|
|
87
110
|
// this.session.set({ status: 'busy' });
|
|
88
111
|
// this.session.set({ status: 'signedIn', displayName: clerk.name });
|
|
89
112
|
// this.errorMessage.set('Invalid clerk or password');
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable } from '@angular/core';
|
|
3
|
+
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
|
4
|
+
import { throwError, map, catchError } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
/** Read a document cookie by name. Returns null when missing or not in a browser. */
|
|
7
|
+
function readCookie(name) {
|
|
8
|
+
if (typeof document === 'undefined' || !name) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const encodedName = encodeURIComponent(name);
|
|
12
|
+
for (const part of document.cookie.split(';')) {
|
|
13
|
+
const trimmed = part.trim();
|
|
14
|
+
const separator = trimmed.indexOf('=');
|
|
15
|
+
if (separator < 0) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const key = trimmed.slice(0, separator);
|
|
19
|
+
if (key !== name && key !== encodedName) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
return decodeURIComponent(trimmed.slice(separator + 1));
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DEFAULT_POS_API_CONFIG = {
|
|
28
|
+
apiBaseUrl: '',
|
|
29
|
+
tokenCookieName: 'portalApiToken',
|
|
30
|
+
shopContextCookieName: 'contextShopId',
|
|
31
|
+
};
|
|
32
|
+
const POS_API_CONFIG = new InjectionToken('POS_API_CONFIG', {
|
|
33
|
+
providedIn: 'root',
|
|
34
|
+
factory: () => DEFAULT_POS_API_CONFIG,
|
|
35
|
+
});
|
|
36
|
+
function readAccessToken(config) {
|
|
37
|
+
return config.getAccessToken?.() ?? readCookie(config.tokenCookieName);
|
|
38
|
+
}
|
|
39
|
+
function readShopContext(config) {
|
|
40
|
+
return config.getShopContext?.() ?? readCookie(config.shopContextCookieName);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function joinUrl(baseUrl, path) {
|
|
44
|
+
if (!baseUrl || /^https?:\/\//i.test(path)) {
|
|
45
|
+
return path;
|
|
46
|
+
}
|
|
47
|
+
const origin = baseUrl.replace(/\/+$/, '');
|
|
48
|
+
const suffix = path.replace(/^\/+/, '');
|
|
49
|
+
return `${origin}/${suffix}`;
|
|
50
|
+
}
|
|
51
|
+
/** Attaches Bearer + Shop-Context from cookies (or host overrides). */
|
|
52
|
+
const posApiInterceptor = (req, next) => {
|
|
53
|
+
const config = inject(POS_API_CONFIG);
|
|
54
|
+
const token = readAccessToken(config);
|
|
55
|
+
const shopContext = readShopContext(config);
|
|
56
|
+
let headers = req.headers;
|
|
57
|
+
if (token && !headers.has('Authorization')) {
|
|
58
|
+
headers = headers.set('Authorization', `Bearer ${token}`);
|
|
59
|
+
}
|
|
60
|
+
if (shopContext && !headers.has('Shop-Context')) {
|
|
61
|
+
headers = headers.set('Shop-Context', shopContext);
|
|
62
|
+
}
|
|
63
|
+
return next(req.clone({
|
|
64
|
+
url: joinUrl(config.apiBaseUrl, req.url),
|
|
65
|
+
headers,
|
|
66
|
+
}));
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
class ClerkAuthError extends Error {
|
|
70
|
+
name = 'ClerkAuthError';
|
|
71
|
+
constructor(message) {
|
|
72
|
+
super(message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function displayName(clerk, clerkNumber) {
|
|
76
|
+
const name = `${clerk.firstName ?? ''} ${clerk.lastName ?? ''}`.trim();
|
|
77
|
+
return name || clerk.clerkNumber || clerkNumber;
|
|
78
|
+
}
|
|
79
|
+
function passwordsMatch(submitted, stored) {
|
|
80
|
+
return submitted === (stored ?? '');
|
|
81
|
+
}
|
|
82
|
+
class ClerkAuthService {
|
|
83
|
+
http = inject(HttpClient);
|
|
84
|
+
/**
|
|
85
|
+
* Looks up the clerk and compares the keypad password in this service.
|
|
86
|
+
* Not a dedicated sign-in endpoint — replace when ID-647 / PosIdentity lands.
|
|
87
|
+
*/
|
|
88
|
+
signIn(clerkNumber, password) {
|
|
89
|
+
const number = clerkNumber.trim();
|
|
90
|
+
if (!number) {
|
|
91
|
+
return throwError(() => new ClerkAuthError('Invalid clerk or password'));
|
|
92
|
+
}
|
|
93
|
+
return this.http.get(`/api/v1.0/clerks/${encodeURIComponent(number)}`).pipe(map((clerk) => {
|
|
94
|
+
if (!passwordsMatch(password, clerk.password)) {
|
|
95
|
+
throw new ClerkAuthError('Invalid clerk or password');
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
clerkNumber: clerk.clerkNumber || number,
|
|
99
|
+
displayName: displayName(clerk, number),
|
|
100
|
+
languageCode: clerk.languageCode || 'en',
|
|
101
|
+
isManager: clerk.isManager === true,
|
|
102
|
+
};
|
|
103
|
+
}), catchError((error) => {
|
|
104
|
+
if (error instanceof ClerkAuthError) {
|
|
105
|
+
return throwError(() => error);
|
|
106
|
+
}
|
|
107
|
+
if (error instanceof HttpErrorResponse) {
|
|
108
|
+
if (error.status === 401) {
|
|
109
|
+
return throwError(() => new ClerkAuthError('API token missing or expired. Paste portalApiToken into cookies.'));
|
|
110
|
+
}
|
|
111
|
+
if (error.status === 404) {
|
|
112
|
+
return throwError(() => new ClerkAuthError('Invalid clerk or password'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return throwError(() => new ClerkAuthError('Unable to sign in'));
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ClerkAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
119
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ClerkAuthService, providedIn: 'root' });
|
|
120
|
+
}
|
|
121
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ClerkAuthService, decorators: [{
|
|
122
|
+
type: Injectable,
|
|
123
|
+
args: [{ providedIn: 'root' }]
|
|
124
|
+
}] });
|
|
125
|
+
|
|
126
|
+
function idempotencyKey() {
|
|
127
|
+
return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Thin HTTP client for open-till sale commands (command in → PosStateEnvelope out).
|
|
131
|
+
*
|
|
132
|
+
* Keep as one client while the surface is small. When basket, tender, and sale
|
|
133
|
+
* lifecycle each grow their own handful of methods, split by subdomain, e.g.:
|
|
134
|
+
* - basket: addItem / changeQuantity / clearBasket (+ discounts, modifiers, …)
|
|
135
|
+
* - tender: tenderCash (+ card, void payment, …)
|
|
136
|
+
* - sale: newSale (+ suspend, recall, …)
|
|
137
|
+
* Extract is cheap; avoid splitting early into near-empty clients.
|
|
138
|
+
*/
|
|
139
|
+
class PosCommandsClient {
|
|
140
|
+
http = inject(HttpClient);
|
|
141
|
+
addItem(command, key = idempotencyKey()) {
|
|
142
|
+
return this.http.post('/api/v1.0/pos/commands/add-item', command, {
|
|
143
|
+
headers: { 'Idempotency-Key': key },
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
changeQuantity(command, key = idempotencyKey()) {
|
|
147
|
+
return this.http.post('/api/v1.0/pos/commands/change-quantity', command, { headers: { 'Idempotency-Key': key } });
|
|
148
|
+
}
|
|
149
|
+
clearBasket(command, key = idempotencyKey()) {
|
|
150
|
+
return this.http.post('/api/v1.0/pos/commands/clear-basket', command, { headers: { 'Idempotency-Key': key } });
|
|
151
|
+
}
|
|
152
|
+
tenderCash(command, key = idempotencyKey()) {
|
|
153
|
+
return this.http.post('/api/v1.0/pos/commands/tender-cash', command, { headers: { 'Idempotency-Key': key } });
|
|
154
|
+
}
|
|
155
|
+
newSale(command, key = idempotencyKey()) {
|
|
156
|
+
return this.http.post('/api/v1.0/pos/commands/new-sale', command, { headers: { 'Idempotency-Key': key } });
|
|
157
|
+
}
|
|
158
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosCommandsClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
159
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosCommandsClient, providedIn: 'root' });
|
|
160
|
+
}
|
|
161
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosCommandsClient, decorators: [{
|
|
162
|
+
type: Injectable,
|
|
163
|
+
args: [{ providedIn: 'root' }]
|
|
164
|
+
}] });
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Generated bundle index. Do not edit.
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
export { ClerkAuthError, ClerkAuthService, DEFAULT_POS_API_CONFIG, POS_API_CONFIG, PosCommandsClient, posApiInterceptor, readAccessToken, readShopContext };
|
|
171
|
+
//# sourceMappingURL=retalia-pos-components-services.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retalia-pos-components-services.mjs","sources":["../../../projects/pos-components/services/read-cookie.ts","../../../projects/pos-components/services/pos-api-config.ts","../../../projects/pos-components/services/pos-api.interceptor.ts","../../../projects/pos-components/services/clerk-auth.service.ts","../../../projects/pos-components/services/pos-commands.client.ts","../../../projects/pos-components/services/retalia-pos-components-services.ts"],"sourcesContent":["/** Read a document cookie by name. Returns null when missing or not in a browser. */\r\nexport function readCookie(name: string): string | null {\r\n if (typeof document === 'undefined' || !name) {\r\n return null;\r\n }\r\n\r\n const encodedName = encodeURIComponent(name);\r\n for (const part of document.cookie.split(';')) {\r\n const trimmed = part.trim();\r\n const separator = trimmed.indexOf('=');\r\n if (separator < 0) {\r\n continue;\r\n }\r\n\r\n const key = trimmed.slice(0, separator);\r\n if (key !== name && key !== encodedName) {\r\n continue;\r\n }\r\n\r\n return decodeURIComponent(trimmed.slice(separator + 1));\r\n }\r\n\r\n return null;\r\n}\r\n","import { InjectionToken } from '@angular/core';\r\n\r\nimport { readCookie } from './read-cookie';\r\n\r\nexport interface PosApiConfig {\r\n /** Origin for HeadlessAPI. Empty string when the host app proxies `/api`. */\r\n readonly apiBaseUrl: string;\r\n /** Cookie holding the Portal Bearer token (paste in local dev). */\r\n readonly tokenCookieName: string;\r\n /** Cookie holding the shop id sent as `Shop-Context`. */\r\n readonly shopContextCookieName: string;\r\n readonly getAccessToken?: () => string | null;\r\n readonly getShopContext?: () => string | null;\r\n}\r\n\r\nexport const DEFAULT_POS_API_CONFIG: PosApiConfig = {\r\n apiBaseUrl: '',\r\n tokenCookieName: 'portalApiToken',\r\n shopContextCookieName: 'contextShopId',\r\n};\r\n\r\nexport const POS_API_CONFIG = new InjectionToken<PosApiConfig>('POS_API_CONFIG', {\r\n providedIn: 'root',\r\n factory: () => DEFAULT_POS_API_CONFIG,\r\n});\r\n\r\nexport function readAccessToken(config: PosApiConfig): string | null {\r\n return config.getAccessToken?.() ?? readCookie(config.tokenCookieName);\r\n}\r\n\r\nexport function readShopContext(config: PosApiConfig): string | null {\r\n return config.getShopContext?.() ?? readCookie(config.shopContextCookieName);\r\n}\r\n","import { HttpInterceptorFn } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\n\r\nimport {\r\n POS_API_CONFIG,\r\n readAccessToken,\r\n readShopContext,\r\n} from './pos-api-config';\r\n\r\nfunction joinUrl(baseUrl: string, path: string): string {\r\n if (!baseUrl || /^https?:\\/\\//i.test(path)) {\r\n return path;\r\n }\r\n\r\n const origin = baseUrl.replace(/\\/+$/, '');\r\n const suffix = path.replace(/^\\/+/, '');\r\n return `${origin}/${suffix}`;\r\n}\r\n\r\n/** Attaches Bearer + Shop-Context from cookies (or host overrides). */\r\nexport const posApiInterceptor: HttpInterceptorFn = (req, next) => {\r\n const config = inject(POS_API_CONFIG);\r\n const token = readAccessToken(config);\r\n const shopContext = readShopContext(config);\r\n\r\n let headers = req.headers;\r\n if (token && !headers.has('Authorization')) {\r\n headers = headers.set('Authorization', `Bearer ${token}`);\r\n }\r\n if (shopContext && !headers.has('Shop-Context')) {\r\n headers = headers.set('Shop-Context', shopContext);\r\n }\r\n\r\n return next(\r\n req.clone({\r\n url: joinUrl(config.apiBaseUrl, req.url),\r\n headers,\r\n }),\r\n );\r\n};\r\n","import { HttpClient, HttpErrorResponse } from '@angular/common/http';\r\nimport { inject, Injectable } from '@angular/core';\r\nimport { catchError, map, Observable, throwError } from 'rxjs';\r\n\r\n/** HeadlessAPI clerk payload. Password check is a stub until ID-647. */\r\ninterface ClerkRecord {\r\n identifier?: number;\r\n firstName?: string;\r\n lastName?: string;\r\n password?: string | null;\r\n languageCode?: string;\r\n clerkNumber?: string;\r\n isManager?: boolean;\r\n}\r\n\r\nexport interface ClerkSession {\r\n readonly clerkNumber: string;\r\n readonly displayName: string;\r\n readonly languageCode: string;\r\n readonly isManager: boolean;\r\n}\r\n\r\nexport class ClerkAuthError extends Error {\r\n override readonly name = 'ClerkAuthError';\r\n\r\n constructor(message: string) {\r\n super(message);\r\n }\r\n}\r\n\r\nfunction displayName(clerk: ClerkRecord, clerkNumber: string): string {\r\n const name = `${clerk.firstName ?? ''} ${clerk.lastName ?? ''}`.trim();\r\n return name || clerk.clerkNumber || clerkNumber;\r\n}\r\n\r\nfunction passwordsMatch(submitted: string, stored: string | null | undefined): boolean {\r\n return submitted === (stored ?? '');\r\n}\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class ClerkAuthService {\r\n private readonly http = inject(HttpClient);\r\n\r\n /**\r\n * Looks up the clerk and compares the keypad password in this service.\r\n * Not a dedicated sign-in endpoint — replace when ID-647 / PosIdentity lands.\r\n */\r\n signIn(clerkNumber: string, password: string): Observable<ClerkSession> {\r\n const number = clerkNumber.trim();\r\n if (!number) {\r\n return throwError(() => new ClerkAuthError('Invalid clerk or password'));\r\n }\r\n\r\n return this.http.get<ClerkRecord>(`/api/v1.0/clerks/${encodeURIComponent(number)}`).pipe(\r\n map((clerk) => {\r\n if (!passwordsMatch(password, clerk.password)) {\r\n throw new ClerkAuthError('Invalid clerk or password');\r\n }\r\n\r\n return {\r\n clerkNumber: clerk.clerkNumber || number,\r\n displayName: displayName(clerk, number),\r\n languageCode: clerk.languageCode || 'en',\r\n isManager: clerk.isManager === true,\r\n };\r\n }),\r\n catchError((error: unknown) => {\r\n if (error instanceof ClerkAuthError) {\r\n return throwError(() => error);\r\n }\r\n\r\n if (error instanceof HttpErrorResponse) {\r\n if (error.status === 401) {\r\n return throwError(\r\n () =>\r\n new ClerkAuthError(\r\n 'API token missing or expired. Paste portalApiToken into cookies.',\r\n ),\r\n );\r\n }\r\n\r\n if (error.status === 404) {\r\n return throwError(() => new ClerkAuthError('Invalid clerk or password'));\r\n }\r\n }\r\n\r\n return throwError(() => new ClerkAuthError('Unable to sign in'));\r\n }),\r\n );\r\n }\r\n}\r\n","import { HttpClient } from '@angular/common/http';\r\nimport { inject, Injectable } from '@angular/core';\r\nimport { Observable } from 'rxjs';\r\n\r\nimport { PosStateEnvelope } from './pos-state-envelope';\r\n\r\nexport interface AddItemCommand {\r\n readonly basketId: string;\r\n readonly sku: string;\r\n readonly qty: number;\r\n}\r\n\r\nexport interface ChangeQuantityCommand {\r\n readonly basketId: string;\r\n readonly lineId: number;\r\n readonly qty: number;\r\n}\r\n\r\nexport interface ClearBasketCommand {\r\n readonly basketId: string;\r\n}\r\n\r\nexport interface TenderCashCommand {\r\n readonly basketId: string;\r\n readonly amount: number;\r\n}\r\n\r\nexport interface NewSaleCommand {\r\n readonly basketId: string;\r\n}\r\n\r\nfunction idempotencyKey(): string {\r\n return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;\r\n}\r\n\r\n/**\r\n * Thin HTTP client for open-till sale commands (command in → PosStateEnvelope out).\r\n *\r\n * Keep as one client while the surface is small. When basket, tender, and sale\r\n * lifecycle each grow their own handful of methods, split by subdomain, e.g.:\r\n * - basket: addItem / changeQuantity / clearBasket (+ discounts, modifiers, …)\r\n * - tender: tenderCash (+ card, void payment, …)\r\n * - sale: newSale (+ suspend, recall, …)\r\n * Extract is cheap; avoid splitting early into near-empty clients.\r\n */\r\n@Injectable({ providedIn: 'root' })\r\nexport class PosCommandsClient {\r\n private readonly http = inject(HttpClient);\r\n\r\n addItem(\r\n command: AddItemCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>('/api/v1.0/pos/commands/add-item', command, {\r\n headers: { 'Idempotency-Key': key },\r\n });\r\n }\r\n\r\n changeQuantity(\r\n command: ChangeQuantityCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>(\r\n '/api/v1.0/pos/commands/change-quantity',\r\n command,\r\n { headers: { 'Idempotency-Key': key } },\r\n );\r\n }\r\n\r\n clearBasket(\r\n command: ClearBasketCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>(\r\n '/api/v1.0/pos/commands/clear-basket',\r\n command,\r\n { headers: { 'Idempotency-Key': key } },\r\n );\r\n }\r\n\r\n tenderCash(\r\n command: TenderCashCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>(\r\n '/api/v1.0/pos/commands/tender-cash',\r\n command,\r\n { headers: { 'Idempotency-Key': key } },\r\n );\r\n }\r\n\r\n newSale(\r\n command: NewSaleCommand,\r\n key = idempotencyKey(),\r\n ): Observable<PosStateEnvelope> {\r\n return this.http.post<PosStateEnvelope>(\r\n '/api/v1.0/pos/commands/new-sale',\r\n command,\r\n { headers: { 'Idempotency-Key': key } },\r\n );\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|