@retalia/pos-components 0.0.7 → 0.0.9

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 CHANGED
@@ -27,12 +27,24 @@ import {
27
27
 
28
28
  | | |
29
29
  |---|---|
30
- | `posApiInterceptor` | Reads `portalApiToken` + `contextShopId` cookies and sets `Authorization` / `Shop-Context`. Provide via `provideHttpClient(withInterceptors([posApiInterceptor]))`. |
30
+ | `posApiInterceptor` | Reads `portalApiToken` + `contextShopId` cookies and sets `Authorization` / `Shop-Context`, plus `Clerk-Context` when `POS_API_CONFIG.getClerkContext` returns a clerk number. Provide via `provideHttpClient(withInterceptors([posApiInterceptor]))`. |
31
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
32
  | `PosCommandsClient` | `addItem` / `changeQuantity` / `clearBasket` / `tenderCash` / `newSale` — each returns a `PosStateEnvelope`. |
33
33
 
34
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
35
 
36
+ HeadlessAPI puts the operator on the envelope `session.clerkId` from `Clerk-Context`. There is no cookie for it; the host returns the signed-in clerk from `getClerkContext` after its password check passes (null before sign-in and after sign-out):
37
+
38
+ ```ts
39
+ {
40
+ provide: POS_API_CONFIG,
41
+ useFactory: () => {
42
+ const store = inject(EnvelopeStore); // wherever the host keeps the signed-in clerk
43
+ return { ...DEFAULT_POS_API_CONFIG, getClerkContext: () => store.clerk()?.clerkNumber ?? null };
44
+ },
45
+ }
46
+ ```
47
+
36
48
  ## Styles
37
49
 
38
50
  Add the package stylesheet once in the host app (e.g. `angular.json` `styles`):
@@ -41,7 +53,7 @@ Add the package stylesheet once in the host app (e.g. `angular.json` `styles`):
41
53
  node_modules/@retalia/pos-components/styles/styles.css
42
54
  ```
43
55
 
44
- Theme via CSS custom properties (`--pos-*`) defined in that file. Override tokens in the host to rebrand; values not exposed as tokens are not overridable without a library change.
56
+ Theme via CSS custom properties (`--pos-*`) defined in that file. Override tokens in the host to rebrand; values not exposed as tokens are not overridable without a library change. Brand wordmarks use `--pos-letter-spacing-brand`, tight titles use `--pos-letter-spacing-tight`, keypad keys use `--pos-shadow-key`, and menus use `--pos-shadow-overlay`. The masked PIN keeps `--pos-letter-spacing-display`.
45
57
 
46
58
  Use `<pos-tokens>` (Storybook: **Theme / Tokens**, or an internal theme lab page) to see every token in action. Click a sample or token to edit values as **live overrides** — this does not change the package defaults in `styles.css`. Export a complete `:root` CSS file for the consuming POS, and import that file back into the lab later.
47
59
 
@@ -75,7 +87,7 @@ src/styles/pos-theme.css
75
87
 
76
88
  ## Login
77
89
 
78
- Keypad login UI — presentational only. It does not call auth services. Layout around it (e.g. split with an image) belongs in the host app.
90
+ Keypad login UI — presentational only. It does not call auth services.
79
91
 
80
92
  The host envelope holds login/session state and passes it in. If the host uses a package `AuthService`, it calls that on `login.submit`, then updates `[session]` / `[errorMessage]`. The component never injects the service.
81
93
 
@@ -119,11 +131,16 @@ export class App {
119
131
  | Selector | `pos-login` |
120
132
  | Input `session` | Display-only login/session state: `signedOut` \| `busy` \| `signedIn`, optional `displayName` |
121
133
  | Input `errorMessage` | Optional error text |
134
+ | Input `showAside` | Shop context beside the keypad. Default `true`. Set `false` for the keypad alone |
135
+ | Input `bordered` | Frame around the login shell. Default `false` |
136
+ | Input `showBrandImage`, `brandImage`, `brandLabel` | Same brand rules as the header. `brandLabel` defaults to `RETALIA`. Empty hides the word. Empty `brandImage` uses the built-in mark |
137
+ | Input `locationName`, `terminalLine`, `shopName`, `posNumber` | Defaults `Address1`, `Address2`, `shop 1`, and `001`. Empty hides that line or tile |
138
+ | Date | Always the PC's local date. `dateLabel` is the caption only |
122
139
  | Output `action` | Typed intents: `login.key`, `login.next`, `login.submit`, `login.back`, … |
123
140
 
124
141
  ## Basket
125
142
 
126
- Presentational sale basket — renders `basket.lines[]` and `totals` exactly as the host envelope provides them. It does not price, tax, or discount. Quantity, void, and clear only emit intents; the host (or server) updates the envelope.
143
+ Presentational sale basket — renders `basket.lines[]` and `totals` exactly as the host envelope provides them. It does not price, tax, or discount. Quantity and void only emit intents; the host (or server) updates the envelope. Clearing the sale is a transaction action bar control, not a basket button. The host still dispatches `basket.clear`.
127
144
 
128
145
  ```ts
129
146
  import { Component, signal } from '@angular/core';
@@ -160,7 +177,7 @@ export class Sale {
160
177
  // Host envelope binding maps:
161
178
  // basket.changeQuantity → change-quantity
162
179
  // basket.voidLine → void-line
163
- // basket.clear → clear-basket
180
+ // Clear sale (`basket.clear`) is dispatched from the transaction action bar.
164
181
  }
165
182
  }
166
183
  ```
@@ -171,7 +188,54 @@ export class Sale {
171
188
  | Input `lines` | Envelope `basket.lines[]` as given (`lineId`, `sku`, `name`, `qty`, `unitPrice`, `lineTotal`, `vat`, `struckThrough`) |
172
189
  | Input `totals` | Envelope `totals` as given (`gross`, `net`, `vat`, `discount`, `due`, `currency`) |
173
190
  | Input `disabled` | Optional lock while the host is busy |
174
- | Output `action` | Typed intents: `basket.changeQuantity`, `basket.voidLine`, `basket.clear` |
191
+ | Input `bordered` | Panel frame. Default `false` |
192
+ | Output `action` | Typed intents: `basket.changeQuantity`, `basket.voidLine`. `basket.clear` remains a host intent for the action bar |
193
+
194
+ ## Header
195
+
196
+ Placeable sale chrome. The host passes the title, shop or other context, and clerk name. The menu beside the clerk initial currently offers sign out only. That click emits an intent; the host clears the session and navigates. The component does not know where it sits in the layout.
197
+
198
+ ```html
199
+ <pos-header
200
+ [clerkName]="clerkName()"
201
+ [clerkRole]="clerkRole()"
202
+ [disabled]="busy()"
203
+ (action)="onHeader($event)"
204
+ />
205
+ ```
206
+
207
+ | | |
208
+ |---|---|
209
+ | Selector | `pos-header` |
210
+ | Input `showBrandImage` | Shows an image before the wordmark. Defaults to true. False hides the image, including the built-in mark |
211
+ | Input `brandImage` | Image URL. Empty, while the image is shown, uses the built-in Retalia mark |
212
+ | Input `brandLabel` | Text after the image. Empty shows no text |
213
+ | Input `title` | Screen title. Omitted from the bar when empty |
214
+ | Input `context` | Secondary label, such as the shop name |
215
+ | Input `clerkName` | Signed-in clerk. The bar shows an initial and the name, and hides the block when empty |
216
+ | Input `clerkRole` | Role label under the name, such as Clerk or Manager. Shown in lowercase |
217
+ | Input `signOutLabel` | Menu item label. Defaults to `Portal Logout` |
218
+ | Input `menuLabel` | Accessible name for the menu button. Defaults to `Menu` |
219
+ | Input `canSignOut` | Hides the menu button when false |
220
+ | Input `disabled` | Disables sign out while the host is busy |
221
+ | Output `action` | `header.signOut` |
222
+
223
+ ## Transaction action bar
224
+
225
+ Layout container for sale commands. It does not render buttons or emit intents. The host projects the controls and decides what each one does, including mapping Cancel to `basket.clear`.
226
+
227
+ ```html
228
+ <pos-transaction-action-bar>
229
+ <button type="button" (click)="onCancel()">Cancel</button>
230
+ <button type="button" (click)="onCash()">Cash</button>
231
+ </pos-transaction-action-bar>
232
+ ```
233
+
234
+ | | |
235
+ |---|---|
236
+ | Selector | `pos-transaction-action-bar` |
237
+ | Content | Projected controls. The bar lays them out in a wrapping row |
238
+ | Output | None. Clicks stay on the projected controls |
175
239
 
176
240
  ## Item entry
177
241
 
@@ -207,13 +271,20 @@ export class Sale {
207
271
  |---|---|
208
272
  | Selector | `pos-item-entry` |
209
273
  | Input `disabled` | Optional lock while the host is busy (e.g. AddItem in flight) |
274
+ | Input `bordered` | Panel frame. Default `false` |
210
275
  | Input `errorMessage` | Optional host-provided error text (lookup failures belong to the host) |
276
+ | Input `label` | Accessible name for the field. Default `Item`. Not shown on the bar |
211
277
  | Input `autofocus` | Focus the field on render so a scanner can type immediately (default `true`) |
278
+ | Input `placeholder` | Field hint. Default `Scan barcode, search products` |
279
+ | Input `scannerStatus` | Status in the chip at the right of the field. Default `scanner ready` |
280
+ | Input `focusHotkey` | Key that focuses the field, shown beside the chip. Default `F2`. Empty hides the hint and the shortcut |
212
281
  | Output `action` | Typed intent: `itemEntry.submit` with `reference` |
213
282
 
283
+ Enter in the field submits the reference. `F2` moves focus back to the field from anywhere else on the page, matching the POS proof of concept. The label is the accessible name and is not shown on the bar.
284
+
214
285
  ## Tender
215
286
 
216
- Presentational cash tender — the cashier enters the amount tendered; the host envelope sends that intent and passes back `changeDue`. The component never calculates change or takes payment. Card / electronic tender is out of scope.
287
+ Presentational cash tender — coins, notes, and quick amounts build the amount given. Pay Now emits `tender.cash`. Cancel emits `tender.cancel`. The host envelope sends the cash intent and passes back `changeDue`. The component never calculates change or takes payment. Card / electronic tender is out of scope.
217
288
 
218
289
  ```ts
219
290
  import { Component, signal } from '@angular/core';
@@ -260,7 +331,7 @@ export class Tender {
260
331
  | Input `currency` | ISO currency code used only for display formatting |
261
332
  | Input `disabled` | Optional lock while the host is busy |
262
333
  | Input `errorMessage` | Optional error text |
263
- | Output `action` | Typed intent: `tender.cash` with the cashier-entered `amount` |
334
+ | Output `action` | `tender.cash` with the amount given, or `tender.cancel` |
264
335
 
265
336
  ## Receipt
266
337
 
@@ -323,4 +394,5 @@ export class Receipt {
323
394
  | Input `payments` | Envelope `payments[]` as given (`tender`, `amount`, `tendered`, `change`, `state`) |
324
395
  | Input `document` | Envelope `document` as given (`number`, `receiptReady`) |
325
396
  | Input `disabled` | Optional lock while the host is busy |
397
+ | Input `bordered` | Panel frame. Default `false` |
326
398
  | Output `action` | Typed intent: `receipt.newSale` |
@@ -39,6 +39,9 @@ function readAccessToken(config) {
39
39
  function readShopContext(config) {
40
40
  return config.getShopContext?.() ?? readCookie(config.shopContextCookieName);
41
41
  }
42
+ function readClerkContext(config) {
43
+ return config.getClerkContext?.()?.trim() || null;
44
+ }
42
45
 
43
46
  function joinUrl(baseUrl, path) {
44
47
  if (!baseUrl || /^https?:\/\//i.test(path)) {
@@ -48,11 +51,15 @@ function joinUrl(baseUrl, path) {
48
51
  const suffix = path.replace(/^\/+/, '');
49
52
  return `${origin}/${suffix}`;
50
53
  }
51
- /** Attaches Bearer + Shop-Context from cookies (or host overrides). */
54
+ /**
55
+ * Attaches Bearer + Shop-Context from cookies (or host overrides), and Clerk-Context once the
56
+ * host reports a signed-in clerk.
57
+ */
52
58
  const posApiInterceptor = (req, next) => {
53
59
  const config = inject(POS_API_CONFIG);
54
60
  const token = readAccessToken(config);
55
61
  const shopContext = readShopContext(config);
62
+ const clerkContext = readClerkContext(config);
56
63
  let headers = req.headers;
57
64
  if (token && !headers.has('Authorization')) {
58
65
  headers = headers.set('Authorization', `Bearer ${token}`);
@@ -60,6 +67,9 @@ const posApiInterceptor = (req, next) => {
60
67
  if (shopContext && !headers.has('Shop-Context')) {
61
68
  headers = headers.set('Shop-Context', shopContext);
62
69
  }
70
+ if (clerkContext && !headers.has('Clerk-Context')) {
71
+ headers = headers.set('Clerk-Context', clerkContext);
72
+ }
63
73
  return next(req.clone({
64
74
  url: joinUrl(config.apiBaseUrl, req.url),
65
75
  headers,
@@ -189,5 +199,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
189
199
  * Generated bundle index. Do not edit.
190
200
  */
191
201
 
192
- export { ClerkAuthError, ClerkAuthService, DEFAULT_POS_API_CONFIG, POS_API_CONFIG, PosCommandsClient, posApiInterceptor, readAccessToken, readShopContext };
202
+ export { ClerkAuthError, ClerkAuthService, DEFAULT_POS_API_CONFIG, POS_API_CONFIG, PosCommandsClient, posApiInterceptor, readAccessToken, readClerkContext, readShopContext };
193
203
  //# sourceMappingURL=retalia-pos-components-services.mjs.map
@@ -1 +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, from, Observable, switchMap, 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\nconst SHA256_HEX = /^[0-9a-f]{64}$/i;\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\n/** HeadlessPOS `SvcClerkService.validateClerk`: SHA-256 of UTF-16 `${identifier}.${pin}`. */\r\nasync function hashClerkPin(identifier: number | undefined, pin: string): Promise<string> {\r\n const message = `${identifier}.${pin}`;\r\n const buffer = new ArrayBuffer(message.length * 2);\r\n const utf16 = new Uint16Array(buffer);\r\n for (let i = 0; i < message.length; i++) {\r\n utf16[i] = message.charCodeAt(i);\r\n }\r\n\r\n const digest = await crypto.subtle.digest('SHA-256', buffer);\r\n return Array.from(new Uint8Array(digest))\r\n .map((byte) => byte.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n}\r\n\r\nasync function passwordsMatch(submitted: string, clerk: ClerkRecord): Promise<boolean> {\r\n const stored = clerk.password ?? '';\r\n if (SHA256_HEX.test(stored)) {\r\n const hash = await hashClerkPin(clerk.identifier, submitted);\r\n return hash === stored.toUpperCase();\r\n }\r\n\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 * Hashed passwords use the HeadlessPOS SHA-256 check. Not a dedicated\r\n * 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 switchMap((clerk) => from(this.toSession(clerk, number, password))),\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 private async toSession(\r\n clerk: ClerkRecord,\r\n clerkNumber: string,\r\n password: string,\r\n ): Promise<ClerkSession> {\r\n if (!(await passwordsMatch(password, clerk))) {\r\n throw new ClerkAuthError('Invalid clerk or password');\r\n }\r\n\r\n return {\r\n clerkNumber: clerk.clerkNumber || clerkNumber,\r\n displayName: displayName(clerk, clerkNumber),\r\n languageCode: clerk.languageCode || 'en',\r\n isManager: clerk.isManager === true,\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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"}
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 * Signed-in clerk number, sent as `Clerk-Context`. There is no cookie for it: the host app\r\n * returns it once its clerk sign-in (and password check) has passed, null otherwise.\r\n */\r\n readonly getClerkContext?: () => 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\r\nexport function readClerkContext(config: PosApiConfig): string | null {\r\n return config.getClerkContext?.()?.trim() || null;\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 readClerkContext,\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/**\r\n * Attaches Bearer + Shop-Context from cookies (or host overrides), and Clerk-Context once the\r\n * host reports a signed-in clerk.\r\n */\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 const clerkContext = readClerkContext(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 if (clerkContext && !headers.has('Clerk-Context')) {\r\n headers = headers.set('Clerk-Context', clerkContext);\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, from, Observable, switchMap, 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\nconst SHA256_HEX = /^[0-9a-f]{64}$/i;\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\n/** HeadlessPOS `SvcClerkService.validateClerk`: SHA-256 of UTF-16 `${identifier}.${pin}`. */\r\nasync function hashClerkPin(identifier: number | undefined, pin: string): Promise<string> {\r\n const message = `${identifier}.${pin}`;\r\n const buffer = new ArrayBuffer(message.length * 2);\r\n const utf16 = new Uint16Array(buffer);\r\n for (let i = 0; i < message.length; i++) {\r\n utf16[i] = message.charCodeAt(i);\r\n }\r\n\r\n const digest = await crypto.subtle.digest('SHA-256', buffer);\r\n return Array.from(new Uint8Array(digest))\r\n .map((byte) => byte.toString(16).padStart(2, '0'))\r\n .join('')\r\n .toUpperCase();\r\n}\r\n\r\nasync function passwordsMatch(submitted: string, clerk: ClerkRecord): Promise<boolean> {\r\n const stored = clerk.password ?? '';\r\n if (SHA256_HEX.test(stored)) {\r\n const hash = await hashClerkPin(clerk.identifier, submitted);\r\n return hash === stored.toUpperCase();\r\n }\r\n\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 * Hashed passwords use the HeadlessPOS SHA-256 check. Not a dedicated\r\n * 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 switchMap((clerk) => from(this.toSession(clerk, number, password))),\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 private async toSession(\r\n clerk: ClerkRecord,\r\n clerkNumber: string,\r\n password: string,\r\n ): Promise<ClerkSession> {\r\n if (!(await passwordsMatch(password, clerk))) {\r\n throw new ClerkAuthError('Invalid clerk or password');\r\n }\r\n\r\n return {\r\n clerkNumber: clerk.clerkNumber || clerkNumber,\r\n displayName: displayName(clerk, clerkNumber),\r\n languageCode: clerk.languageCode || 'en',\r\n isManager: clerk.isManager === true,\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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"}