@happyvertical/smrt-inventory 0.30.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/AGENTS.md +121 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +213 -0
- package/dist/index.d.ts +557 -0
- package/dist/index.js +922 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +1023 -0
- package/dist/smrt-knowledge.json +618 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { DatabaseConfig } from '@happyvertical/smrt-core';
|
|
2
|
+
import { DispatchBus } from '@happyvertical/smrt-core';
|
|
3
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
4
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
5
|
+
import { SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shape of a single reserved line in a `contract:created` payload. The
|
|
9
|
+
* producer (typically `@happyvertical/smrt-commerce`) packs one entry
|
|
10
|
+
* per line that needs stock motion; everything else (taxes, fees,
|
|
11
|
+
* non-physical items) should be filtered upstream.
|
|
12
|
+
*/
|
|
13
|
+
export declare interface ContractCreatedLine {
|
|
14
|
+
skuId: string;
|
|
15
|
+
locationId: string;
|
|
16
|
+
qty: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Shape of the `contract:created` payload this handler expects. Carries
|
|
21
|
+
* the contract id (used for source attribution on the audit row) and
|
|
22
|
+
* the list of line items to reserve.
|
|
23
|
+
*/
|
|
24
|
+
export declare interface ContractCreatedPayload {
|
|
25
|
+
contractId: string;
|
|
26
|
+
lines: ContractCreatedLine[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convenience factory. Returns a fully-initialized {@link StockService}
|
|
31
|
+
* sharing the given database with its internal collections.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createStockService(options: StockServiceOptions): Promise<StockService>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Shape of a single shipped line in a `fulfillment:shipped` payload.
|
|
37
|
+
*/
|
|
38
|
+
export declare interface FulfillmentShippedLine {
|
|
39
|
+
skuId: string;
|
|
40
|
+
locationId: string;
|
|
41
|
+
qty: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Shape of the `fulfillment:shipped` payload this handler expects.
|
|
46
|
+
* Producers emit one per shipped fulfilment; the handler fulfils each
|
|
47
|
+
* line against the level created by the matching `contract:created`
|
|
48
|
+
* reservation.
|
|
49
|
+
*/
|
|
50
|
+
export declare interface FulfillmentShippedPayload {
|
|
51
|
+
fulfillmentId: string;
|
|
52
|
+
lines: FulfillmentShippedLine[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Result handle returned by {@link installInventoryDispatchHandlers}.
|
|
57
|
+
* Call `dispose()` to detach the subscribers (mainly useful in tests
|
|
58
|
+
* and on graceful shutdown).
|
|
59
|
+
*/
|
|
60
|
+
export declare interface InstalledInventoryDispatchHandlers {
|
|
61
|
+
/** Resolved StockService — useful for follow-up writes in the same scope. */
|
|
62
|
+
stockService: StockService;
|
|
63
|
+
/** Detach every installed subscriber. Idempotent. */
|
|
64
|
+
dispose(): void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Subscribe a {@link StockService}-driven handler to the relevant signals
|
|
69
|
+
* on the given {@link DispatchBus}. Returns a disposer for tests and
|
|
70
|
+
* shutdown hooks.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { createDispatchBus } from '@happyvertical/smrt-core';
|
|
75
|
+
* import { installInventoryDispatchHandlers } from '@happyvertical/smrt-inventory';
|
|
76
|
+
*
|
|
77
|
+
* const bus = await createDispatchBus({ db });
|
|
78
|
+
* const handlers = await installInventoryDispatchHandlers({
|
|
79
|
+
* dispatchBus: bus,
|
|
80
|
+
* db,
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function installInventoryDispatchHandlers(options: InstallInventoryDispatchHandlersOptions): Promise<InstalledInventoryDispatchHandlers>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Options accepted by {@link installInventoryDispatchHandlers}.
|
|
88
|
+
*
|
|
89
|
+
* Provide either a pre-built {@link StockService} (when sharing one
|
|
90
|
+
* across multiple subsystems) or a `db` for the helper to construct one
|
|
91
|
+
* on first use.
|
|
92
|
+
*/
|
|
93
|
+
export declare type InstallInventoryDispatchHandlersOptions = {
|
|
94
|
+
/**
|
|
95
|
+
* Bus to subscribe on. Typically the app-wide `DispatchBus` created in
|
|
96
|
+
* the application's `smrt.ts`.
|
|
97
|
+
*/
|
|
98
|
+
dispatchBus: DispatchBus;
|
|
99
|
+
/**
|
|
100
|
+
* When `true` (default), install the `contract:created` handler.
|
|
101
|
+
* Producers should publish a {@link ContractCreatedPayload}.
|
|
102
|
+
*/
|
|
103
|
+
installContractReserved?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* When `true` (default), install the `fulfillment:shipped` handler.
|
|
106
|
+
* Producers should publish a {@link FulfillmentShippedPayload}.
|
|
107
|
+
*/
|
|
108
|
+
installFulfillmentShipped?: boolean;
|
|
109
|
+
} & ({
|
|
110
|
+
stockService: StockService;
|
|
111
|
+
db?: DatabaseConfig;
|
|
112
|
+
} | {
|
|
113
|
+
db: DatabaseConfig;
|
|
114
|
+
stockService?: undefined;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Thrown by {@link StockService.reserve} (and {@link StockService.fulfill},
|
|
119
|
+
* {@link StockService.transfer}) when the caller asks to move more stock
|
|
120
|
+
* than the source state currently holds. Carries enough context for a
|
|
121
|
+
* caller to surface a meaningful UI message and decide whether to retry,
|
|
122
|
+
* backorder, or cancel.
|
|
123
|
+
*/
|
|
124
|
+
export declare class InsufficientStockError extends Error {
|
|
125
|
+
readonly skuId: string;
|
|
126
|
+
readonly locationId: string;
|
|
127
|
+
readonly state: StockState;
|
|
128
|
+
readonly requested: number;
|
|
129
|
+
readonly available: number;
|
|
130
|
+
name: string;
|
|
131
|
+
constructor(skuId: string, locationId: string, state: StockState, requested: number, available: number);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export declare class InventoryLocation extends SmrtObject {
|
|
135
|
+
/** Tenant scope. `null` means the location record is global. */
|
|
136
|
+
tenantId: string | null;
|
|
137
|
+
/**
|
|
138
|
+
* Short stable identifier (`'WH-EAST'`, `'STORE-42'`, `'IN-TRANSIT'`).
|
|
139
|
+
* Together with `tenantId` this is the natural key.
|
|
140
|
+
*/
|
|
141
|
+
code: string;
|
|
142
|
+
/** Display name for UIs. */
|
|
143
|
+
name: string;
|
|
144
|
+
/**
|
|
145
|
+
* Open-ended classifier (`'warehouse'`, `'factory'`, `'retail'`,
|
|
146
|
+
* `'in_transit'`, `'virtual'`, or anything else your domain needs).
|
|
147
|
+
* The framework never branches on this value.
|
|
148
|
+
*/
|
|
149
|
+
kind: InventoryLocationKind;
|
|
150
|
+
/**
|
|
151
|
+
* Optional plain-string reference to a `Place.id` in
|
|
152
|
+
* `@happyvertical/smrt-places`. Cross-package id; intentionally not a
|
|
153
|
+
* `@foreignKey()` so this package can be used without `smrt-places`
|
|
154
|
+
* installed.
|
|
155
|
+
*/
|
|
156
|
+
placeId: string;
|
|
157
|
+
/** Soft-active flag — inactive locations stay queryable for history. */
|
|
158
|
+
active: boolean;
|
|
159
|
+
constructor(options?: InventoryLocationOptions);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export declare class InventoryLocationCollection extends SmrtCollection<InventoryLocation> {
|
|
163
|
+
static readonly _itemClass: typeof InventoryLocation;
|
|
164
|
+
/**
|
|
165
|
+
* Look up a location by its tenant-scoped `code`. Returns `null` when
|
|
166
|
+
* no row matches.
|
|
167
|
+
*/
|
|
168
|
+
findByCode(code: string): Promise<InventoryLocation | null>;
|
|
169
|
+
/**
|
|
170
|
+
* Find every location classified as the given kind (`'warehouse'`,
|
|
171
|
+
* `'factory'`, `'retail'`, `'in_transit'`, …).
|
|
172
|
+
*/
|
|
173
|
+
findByKind(kind: InventoryLocationKind): Promise<InventoryLocation[]>;
|
|
174
|
+
/**
|
|
175
|
+
* Find every location linked to a particular `Place.id` from
|
|
176
|
+
* `@happyvertical/smrt-places`. Returns an empty array when no row
|
|
177
|
+
* references the place.
|
|
178
|
+
*/
|
|
179
|
+
findByPlace(placeId: string): Promise<InventoryLocation[]>;
|
|
180
|
+
/** Find every active location, optionally narrowed by kind. */
|
|
181
|
+
findActive(kind?: InventoryLocationKind): Promise<InventoryLocation[]>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Open-ended classifier for an InventoryLocation. The framework does not
|
|
186
|
+
* special-case any value — pass whatever taxonomy your application needs.
|
|
187
|
+
*
|
|
188
|
+
* The strings listed here are conventions, not an exhaustive enum:
|
|
189
|
+
* - `warehouse` — fulfillment warehouse
|
|
190
|
+
* - `factory` — manufacturing site
|
|
191
|
+
* - `retail` — storefront / point-of-sale
|
|
192
|
+
* - `in_transit` — virtual location for stock that has left A but not yet
|
|
193
|
+
* arrived at B; balances `transfer()` semantics
|
|
194
|
+
* - `virtual` — any other non-physical bucket (returns staging, scrap,
|
|
195
|
+
* consignment pool)
|
|
196
|
+
*/
|
|
197
|
+
export declare type InventoryLocationKind = string;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Options accepted by the {@link InventoryLocation} constructor.
|
|
201
|
+
*/
|
|
202
|
+
export declare interface InventoryLocationOptions extends SmrtObjectOptions {
|
|
203
|
+
tenantId?: string | null;
|
|
204
|
+
code?: string;
|
|
205
|
+
name?: string;
|
|
206
|
+
kind?: InventoryLocationKind;
|
|
207
|
+
placeId?: string;
|
|
208
|
+
active?: boolean;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export declare class StockLevel extends SmrtObject {
|
|
212
|
+
/** Tenant scope. `null` means the level row is global. */
|
|
213
|
+
tenantId: string | null;
|
|
214
|
+
/** Plain string reference to the {@link Sku} this row tracks. */
|
|
215
|
+
skuId: string;
|
|
216
|
+
/** Plain string reference to the {@link InventoryLocation} this row tracks. */
|
|
217
|
+
locationId: string;
|
|
218
|
+
/** Logical state — `available`, `allocated`, `wip`, `qc_hold`, `damaged`. */
|
|
219
|
+
state: StockState;
|
|
220
|
+
/**
|
|
221
|
+
* Current quantity. Fractional values are allowed (`= 0.0`) for
|
|
222
|
+
* domains that count in units of measure other than whole pieces
|
|
223
|
+
* (kilograms, litres, metres).
|
|
224
|
+
*/
|
|
225
|
+
qty: number;
|
|
226
|
+
constructor(options?: StockLevelOptions);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export declare class StockLevelCollection extends SmrtCollection<StockLevel> {
|
|
230
|
+
static readonly _itemClass: typeof StockLevel;
|
|
231
|
+
/**
|
|
232
|
+
* Fetch the level row for a `(skuId, locationId, state)` tuple, or
|
|
233
|
+
* `null` when the row has never been written. State defaults to
|
|
234
|
+
* `'available'` because that is the common case (selling / picking
|
|
235
|
+
* decisions are driven by available stock).
|
|
236
|
+
*/
|
|
237
|
+
getLevel(skuId: string, locationId: string, state?: StockState): Promise<StockLevel | null>;
|
|
238
|
+
/**
|
|
239
|
+
* Return every level row for the given SKU across all locations and
|
|
240
|
+
* states. Useful for "where is this SKU?" admin screens.
|
|
241
|
+
*/
|
|
242
|
+
findBySku(skuId: string): Promise<StockLevel[]>;
|
|
243
|
+
/**
|
|
244
|
+
* Return every level row at the given location. Useful for "what is
|
|
245
|
+
* in this warehouse?" reports.
|
|
246
|
+
*/
|
|
247
|
+
findByLocation(locationId: string): Promise<StockLevel[]>;
|
|
248
|
+
/**
|
|
249
|
+
* Sum `qty` across all level rows for the given SKU. Pass `state` to
|
|
250
|
+
* narrow the sum to one logical state (e.g. only `available`);
|
|
251
|
+
* omit it for grand total across all states.
|
|
252
|
+
*/
|
|
253
|
+
totalForSku(skuId: string, state?: StockState): Promise<number>;
|
|
254
|
+
/**
|
|
255
|
+
* Sum `qty` across all level rows at the given location, optionally
|
|
256
|
+
* narrowed by state.
|
|
257
|
+
*/
|
|
258
|
+
totalForLocation(locationId: string, state?: StockState): Promise<number>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Options accepted by the {@link StockLevel} constructor.
|
|
263
|
+
*/
|
|
264
|
+
export declare interface StockLevelOptions extends SmrtObjectOptions {
|
|
265
|
+
tenantId?: string | null;
|
|
266
|
+
skuId?: string;
|
|
267
|
+
locationId?: string;
|
|
268
|
+
state?: StockState;
|
|
269
|
+
qty?: number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export declare class StockMovement extends SmrtObject {
|
|
273
|
+
/** Tenant scope. `null` means the movement is global. */
|
|
274
|
+
tenantId: string | null;
|
|
275
|
+
/** Plain string reference to the {@link Sku} being moved. */
|
|
276
|
+
skuId: string;
|
|
277
|
+
/** Plain string reference to the {@link InventoryLocation} being mutated. */
|
|
278
|
+
locationId: string;
|
|
279
|
+
/**
|
|
280
|
+
* Origin state for transitions (e.g. `available` → `allocated` for a
|
|
281
|
+
* reservation). `null` indicates "no origin" — used when stock enters
|
|
282
|
+
* the system fresh via {@link StockService.receive} or production.
|
|
283
|
+
*/
|
|
284
|
+
fromState: StockState | null;
|
|
285
|
+
/**
|
|
286
|
+
* Destination state. `null` indicates "no destination" — used for
|
|
287
|
+
* fulfilment, where stock leaves the building entirely.
|
|
288
|
+
*/
|
|
289
|
+
toState: StockState | null;
|
|
290
|
+
/** Quantity moved. Always positive; the direction is encoded by from/to. */
|
|
291
|
+
qty: number;
|
|
292
|
+
/**
|
|
293
|
+
* Why the movement happened (see {@link StockMovementReason}). Drawn
|
|
294
|
+
* from the canonical list when emitted by {@link StockService}; free-form
|
|
295
|
+
* strings are allowed for vertical-specific reasons.
|
|
296
|
+
*/
|
|
297
|
+
reasonCode: StockMovementReason;
|
|
298
|
+
/**
|
|
299
|
+
* Cross-package attribution tag — e.g. `'Contract'`, `'Fulfillment'`,
|
|
300
|
+
* `'ProductionOrder'`, `'CycleCount'`. The package writing the
|
|
301
|
+
* movement decides what tag makes sense; readers can group by
|
|
302
|
+
* `(sourceType, sourceId)` to reconstruct "what caused this".
|
|
303
|
+
*/
|
|
304
|
+
sourceType: string;
|
|
305
|
+
/**
|
|
306
|
+
* Cross-package id of the row that caused this movement. Plain string;
|
|
307
|
+
* the framework never dereferences it.
|
|
308
|
+
*/
|
|
309
|
+
sourceId: string;
|
|
310
|
+
/** Optional free-form note shown in audit UIs. */
|
|
311
|
+
note: string;
|
|
312
|
+
/**
|
|
313
|
+
* When the movement happened. Set to `now` at write time; explicit
|
|
314
|
+
* values are allowed when back-dating an import.
|
|
315
|
+
*/
|
|
316
|
+
occurredAt: Date;
|
|
317
|
+
constructor(options?: StockMovementOptions);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export declare class StockMovementCollection extends SmrtCollection<StockMovement> {
|
|
321
|
+
static readonly _itemClass: typeof StockMovement;
|
|
322
|
+
/**
|
|
323
|
+
* Return every movement for the given SKU, newest first. Useful for a
|
|
324
|
+
* per-SKU audit trail.
|
|
325
|
+
*/
|
|
326
|
+
findBySku(skuId: string): Promise<StockMovement[]>;
|
|
327
|
+
/**
|
|
328
|
+
* Return every movement at the given location, newest first. Useful
|
|
329
|
+
* for a per-warehouse audit trail.
|
|
330
|
+
*/
|
|
331
|
+
findByLocation(locationId: string): Promise<StockMovement[]>;
|
|
332
|
+
/**
|
|
333
|
+
* Return every movement attributed to the given upstream source — for
|
|
334
|
+
* example `findBySource('Contract', contract.id)` returns every
|
|
335
|
+
* movement caused by the reservation/fulfilment/release of that
|
|
336
|
+
* contract. Newest first.
|
|
337
|
+
*/
|
|
338
|
+
findBySource(sourceType: string, sourceId: string): Promise<StockMovement[]>;
|
|
339
|
+
/**
|
|
340
|
+
* Return every movement with the given reason code (`'receipt'`,
|
|
341
|
+
* `'reservation'`, `'adjustment'`, …). Newest first.
|
|
342
|
+
*/
|
|
343
|
+
findByReason(reasonCode: StockMovementReason): Promise<StockMovement[]>;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Options accepted by the {@link StockMovement} constructor.
|
|
348
|
+
*/
|
|
349
|
+
export declare interface StockMovementOptions extends SmrtObjectOptions {
|
|
350
|
+
tenantId?: string | null;
|
|
351
|
+
skuId?: string;
|
|
352
|
+
locationId?: string;
|
|
353
|
+
fromState?: StockState | null;
|
|
354
|
+
toState?: StockState | null;
|
|
355
|
+
qty?: number;
|
|
356
|
+
reasonCode?: StockMovementReason;
|
|
357
|
+
sourceType?: string;
|
|
358
|
+
sourceId?: string;
|
|
359
|
+
note?: string;
|
|
360
|
+
occurredAt?: Date | string;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Why a StockMovement was written.
|
|
365
|
+
*
|
|
366
|
+
* Each {@link StockService} method writes one movement with a `reasonCode`
|
|
367
|
+
* drawn from this list. Free-form `string` is also accepted so consumers
|
|
368
|
+
* can introduce vocabulary specific to their business (e.g. `'shrink'`,
|
|
369
|
+
* `'sample'`, `'consignment_out'`) without forking the package.
|
|
370
|
+
*/
|
|
371
|
+
export declare type StockMovementReason = 'receipt' | 'reservation' | 'release' | 'fulfillment' | 'transfer_out' | 'transfer_in' | 'adjustment' | 'production_consume' | 'production_produce' | (string & {});
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Options shared by every {@link StockService} method that wants to leave
|
|
375
|
+
* an audit attribution behind. Pairs neatly with the cross-package
|
|
376
|
+
* pattern in {@link StockMovement.sourceType} / {@link StockMovement.sourceId}.
|
|
377
|
+
*/
|
|
378
|
+
export declare interface StockMutationOptions {
|
|
379
|
+
/** Cross-package tag, e.g. `'Contract'`, `'Fulfillment'`, `'CycleCount'`. */
|
|
380
|
+
sourceType?: string;
|
|
381
|
+
/** Cross-package id of the row that caused this mutation. */
|
|
382
|
+
sourceId?: string;
|
|
383
|
+
/** Free-form note shown in audit UIs. */
|
|
384
|
+
note?: string;
|
|
385
|
+
/**
|
|
386
|
+
* Override the reason code stamped on the {@link StockMovement}. Each
|
|
387
|
+
* method picks a sensible default; explicit overrides are useful when a
|
|
388
|
+
* vertical wants to flag a more specific reason (e.g. `'return'`
|
|
389
|
+
* instead of `'receipt'`).
|
|
390
|
+
*/
|
|
391
|
+
reasonCode?: StockMovementReason;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Sanctioned stock-mutation surface.
|
|
396
|
+
*
|
|
397
|
+
* Construct via {@link createStockService} — the static factory wires up
|
|
398
|
+
* the underlying collections and shares one database connection across
|
|
399
|
+
* level reads and movement writes.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```typescript
|
|
403
|
+
* const service = await createStockService({ db });
|
|
404
|
+
* await service.receive(sku.id, warehouse.id, 100, {
|
|
405
|
+
* sourceType: 'PurchaseOrder',
|
|
406
|
+
* sourceId: po.id,
|
|
407
|
+
* });
|
|
408
|
+
* await service.reserve(sku.id, warehouse.id, 10, {
|
|
409
|
+
* sourceType: 'Contract',
|
|
410
|
+
* sourceId: order.id,
|
|
411
|
+
* });
|
|
412
|
+
* await service.fulfill(sku.id, warehouse.id, 10, {
|
|
413
|
+
* sourceType: 'Fulfillment',
|
|
414
|
+
* sourceId: fulfillment.id,
|
|
415
|
+
* });
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
export declare class StockService {
|
|
419
|
+
/**
|
|
420
|
+
* The database config this service was bound to (URL string, config
|
|
421
|
+
* object, or already-resolved `DatabaseInterface`). Exposed so
|
|
422
|
+
* downstream services that compose StockService (e.g. BomService,
|
|
423
|
+
* ProductionService in `@happyvertical/smrt-manufacturing`) can pass
|
|
424
|
+
* the same value to their own collection factories without reaching
|
|
425
|
+
* into private fields on the collections.
|
|
426
|
+
*/
|
|
427
|
+
readonly db: DatabaseConfig;
|
|
428
|
+
readonly levels: StockLevelCollection;
|
|
429
|
+
readonly movements: StockMovementCollection;
|
|
430
|
+
readonly locations: InventoryLocationCollection;
|
|
431
|
+
/**
|
|
432
|
+
* Marks a service instance handed to a {@link withTransaction}
|
|
433
|
+
* callback. Public mutation methods on a tx-bound instance skip
|
|
434
|
+
* opening a nested transaction and just execute against the already-
|
|
435
|
+
* bound collections; outer (non-tx) instances open a fresh
|
|
436
|
+
* transaction per mutation. Internal flag — consumers never set it.
|
|
437
|
+
*/
|
|
438
|
+
private readonly inTransaction;
|
|
439
|
+
private constructor();
|
|
440
|
+
/** Internal factory — prefer {@link createStockService}. */
|
|
441
|
+
static create(options: StockServiceOptions): Promise<StockService>;
|
|
442
|
+
/**
|
|
443
|
+
* Run `work` inside a single database transaction with a tx-bound
|
|
444
|
+
* {@link StockService} instance. All mutation calls on `tx` commit
|
|
445
|
+
* atomically when `work` resolves and roll back if it throws.
|
|
446
|
+
*
|
|
447
|
+
* Use this when you need atomicity ACROSS multiple stock-service
|
|
448
|
+
* calls — e.g. consuming materials for every line of a production
|
|
449
|
+
* order in `@happyvertical/smrt-manufacturing`'s `ProductionService`,
|
|
450
|
+
* or a custom workflow that reserves + fulfills + writes a custom
|
|
451
|
+
* audit comment in one indivisible step. Individual mutation methods
|
|
452
|
+
* (`receive`, `reserve`, etc.) are already atomic on their own — you
|
|
453
|
+
* only need `withTransaction` for cross-call composition.
|
|
454
|
+
*
|
|
455
|
+
* Nesting is safe: calling `tx.withTransaction(...)` inside an
|
|
456
|
+
* already-tx-bound callback simply runs the inner `work` on the same
|
|
457
|
+
* transaction without opening a savepoint.
|
|
458
|
+
*
|
|
459
|
+
* When the underlying adapter does not expose `transaction()`, falls
|
|
460
|
+
* through to a serial run on the regular collections with a one-time
|
|
461
|
+
* warning. All four built-in adapters in `@happyvertical/sql >= 0.74.0`
|
|
462
|
+
* support it; only test stubs would hit this branch.
|
|
463
|
+
*/
|
|
464
|
+
withTransaction<T>(work: (tx: StockService) => Promise<T>): Promise<T>;
|
|
465
|
+
/**
|
|
466
|
+
* Internal: run a single-method mutation in a transaction. If we're
|
|
467
|
+
* already inside one (the instance was handed to a `withTransaction`
|
|
468
|
+
* callback), reuse it; otherwise open a fresh one.
|
|
469
|
+
*/
|
|
470
|
+
private runAtomically;
|
|
471
|
+
/**
|
|
472
|
+
* Add `qty` to available stock at the given location. Used for
|
|
473
|
+
* purchase-order receipts, customer returns going back into available
|
|
474
|
+
* inventory, and the "produce" leg of a production order.
|
|
475
|
+
*/
|
|
476
|
+
receive(skuId: string, locationId: string, qty: number, options?: StockMutationOptions): Promise<void>;
|
|
477
|
+
/**
|
|
478
|
+
* Move `qty` from `available` to `allocated` at the given location.
|
|
479
|
+
* Throws {@link InsufficientStockError} if available stock would go
|
|
480
|
+
* negative.
|
|
481
|
+
*/
|
|
482
|
+
reserve(skuId: string, locationId: string, qty: number, options?: StockMutationOptions): Promise<void>;
|
|
483
|
+
/**
|
|
484
|
+
* Move `qty` from `allocated` back to `available`. Used when a
|
|
485
|
+
* reservation is cancelled and the previously-reserved stock should
|
|
486
|
+
* go back into the available pool.
|
|
487
|
+
*/
|
|
488
|
+
release(skuId: string, locationId: string, qty: number, options?: StockMutationOptions): Promise<void>;
|
|
489
|
+
/**
|
|
490
|
+
* Remove `qty` from `allocated` at the given location. Stock leaves
|
|
491
|
+
* the building entirely (shipped, picked up, consumed). Throws
|
|
492
|
+
* {@link InsufficientStockError} if allocated stock would go negative.
|
|
493
|
+
*/
|
|
494
|
+
fulfill(skuId: string, locationId: string, qty: number, options?: StockMutationOptions): Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Move `qty` of `available` stock from `fromLocationId` to
|
|
497
|
+
* `toLocationId`. Writes two movement rows — one for the `transfer_out`
|
|
498
|
+
* leg, one for the `transfer_in` leg — so the audit log preserves the
|
|
499
|
+
* lineage in both directions. Throws {@link InsufficientStockError} if
|
|
500
|
+
* source available stock would go negative.
|
|
501
|
+
*
|
|
502
|
+
* Both legs (level writes + movement rows) run inside one transaction
|
|
503
|
+
* — a failure mid-`transfer` rolls back the source debit so there's no
|
|
504
|
+
* "ghost stock disappearance" (source decremented, destination never
|
|
505
|
+
* credited).
|
|
506
|
+
*/
|
|
507
|
+
transfer(skuId: string, fromLocationId: string, toLocationId: string, qty: number, options?: StockMutationOptions): Promise<void>;
|
|
508
|
+
/**
|
|
509
|
+
* Apply a positive or negative `delta` to a level row. Used for cycle
|
|
510
|
+
* counts and one-off corrections; `delta=+5` adds five units,
|
|
511
|
+
* `delta=-2` removes two. By default the adjustment targets
|
|
512
|
+
* `available` stock; pass an explicit `state` to adjust a different
|
|
513
|
+
* bucket (e.g. `'damaged'` after a quality-control reclassification).
|
|
514
|
+
*
|
|
515
|
+
* Adjusting by `0` is rejected as a probable programming error — the
|
|
516
|
+
* caller almost always meant a non-zero delta and a no-op write would
|
|
517
|
+
* still cost an audit row.
|
|
518
|
+
*/
|
|
519
|
+
adjust(skuId: string, locationId: string, delta: number, options?: StockMutationOptions & {
|
|
520
|
+
state?: StockState;
|
|
521
|
+
}): Promise<void>;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Options accepted by the {@link StockService} factory.
|
|
526
|
+
*/
|
|
527
|
+
export declare interface StockServiceOptions {
|
|
528
|
+
/**
|
|
529
|
+
* Database to read/write through. Accepts the same shapes that
|
|
530
|
+
* `SmrtCollection.create({ db })` accepts — a `DatabaseInterface`, a
|
|
531
|
+
* connection-string URL, or a `{ type, url }` config object. Reused by
|
|
532
|
+
* the internal collections so the service, level reads, and movement
|
|
533
|
+
* writes always hit the same connection / pool.
|
|
534
|
+
*/
|
|
535
|
+
db: DatabaseConfig;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Logical state of a quantity of stock at a `(skuId, locationId)` pair.
|
|
540
|
+
*
|
|
541
|
+
* StockLevel rows are tuples of `(skuId, locationId, state)`, so a single
|
|
542
|
+
* SKU at a single location can simultaneously have non-zero quantities in
|
|
543
|
+
* several states (e.g. 50 available, 10 allocated, 3 damaged).
|
|
544
|
+
*
|
|
545
|
+
* - `available` — on hand and free to allocate.
|
|
546
|
+
* - `allocated` — reserved against a contract, order, or production plan;
|
|
547
|
+
* physically still on site but no longer free to sell.
|
|
548
|
+
* - `wip` — work-in-progress: consumed materials inside an active
|
|
549
|
+
* production order, not yet emitted as finished goods.
|
|
550
|
+
* - `qc_hold` — held pending quality control; not available to allocate
|
|
551
|
+
* or ship until released.
|
|
552
|
+
* - `damaged` — damaged or otherwise unsellable; kept on the books for
|
|
553
|
+
* shrinkage accounting until written off.
|
|
554
|
+
*/
|
|
555
|
+
export declare type StockState = 'available' | 'allocated' | 'wip' | 'qc_hold' | 'damaged';
|
|
556
|
+
|
|
557
|
+
export { }
|