@classytic/flow 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/README.md +258 -0
  4. package/dist/allocation-policy-my_HfzdV.d.mts +23 -0
  5. package/dist/base-MWBqRFM2.mjs +16 -0
  6. package/dist/catalog-bridge-K8bdkncJ.d.mts +29 -0
  7. package/dist/cost-layer.port-iH9pvZqB.d.mts +30 -0
  8. package/dist/cost-layer.service-BQ1bs-XN.mjs +86 -0
  9. package/dist/cost-layer.service-DWmo9dQz.d.mts +53 -0
  10. package/dist/count.port-BRqwGbi3.d.mts +57 -0
  11. package/dist/counting/index.d.mts +2 -0
  12. package/dist/counting/index.mjs +2 -0
  13. package/dist/counting.service-BiQXqorv.mjs +232 -0
  14. package/dist/counting.service-CpAxU2G0.d.mts +74 -0
  15. package/dist/domain/contracts/index.d.mts +3 -0
  16. package/dist/domain/contracts/index.mjs +1 -0
  17. package/dist/domain/enums/index.d.mts +2 -0
  18. package/dist/domain/enums/index.mjs +4 -0
  19. package/dist/domain/index.d.mts +24 -0
  20. package/dist/domain/index.mjs +10 -0
  21. package/dist/domain/policies/index.d.mts +4 -0
  22. package/dist/domain/policies/index.mjs +1 -0
  23. package/dist/domain-D5cpMpR0.mjs +96 -0
  24. package/dist/domain-errors-D7S9ydNF.mjs +133 -0
  25. package/dist/enums-C3_z6aHC.mjs +82 -0
  26. package/dist/event-bus-BNmyoJb4.mjs +37 -0
  27. package/dist/event-bus-Um_xrcMY.d.mts +21 -0
  28. package/dist/event-emitter.port-BFh2pasY.d.mts +183 -0
  29. package/dist/event-types-BSqQOvXv.mjs +29 -0
  30. package/dist/events/index.d.mts +3 -0
  31. package/dist/events/index.mjs +3 -0
  32. package/dist/idempotency.port-CTC70JON.d.mts +55 -0
  33. package/dist/index-Bia4m8d2.d.mts +67 -0
  34. package/dist/index-BmNm3oNU2.d.mts +107 -0
  35. package/dist/index-C5PciI9P.d.mts +203 -0
  36. package/dist/index-CMTUKEK_.d.mts +308 -0
  37. package/dist/index-C_aEnozN.d.mts +220 -0
  38. package/dist/index-CulWO137.d.mts +107 -0
  39. package/dist/index-DFF0GJ4J.d.mts +36 -0
  40. package/dist/index-DsE7lZdO.d.mts +11 -0
  41. package/dist/index-DwO9IdNa.d.mts +1 -0
  42. package/dist/index-dtWUZr2a2.d.mts +350 -0
  43. package/dist/index.d.mts +128 -0
  44. package/dist/index.mjs +102 -0
  45. package/dist/insufficient-stock.error-Dyr4BYaV.mjs +15 -0
  46. package/dist/location.port-CValXIpb.d.mts +52 -0
  47. package/dist/lot.port-ChsmvZqs.d.mts +32 -0
  48. package/dist/models/index.d.mts +2 -0
  49. package/dist/models/index.mjs +2 -0
  50. package/dist/models-CHTMbp-G.mjs +1020 -0
  51. package/dist/move-group.port-DHGoQA3d.d.mts +56 -0
  52. package/dist/move-status-DkaFp2GD.mjs +38 -0
  53. package/dist/move.port-Qg1CYp7h.d.mts +89 -0
  54. package/dist/package.service-4tcAwBbr.mjs +95 -0
  55. package/dist/package.service-C605NaBQ.d.mts +42 -0
  56. package/dist/packaging/index.d.mts +2 -0
  57. package/dist/packaging/index.mjs +2 -0
  58. package/dist/procurement/index.d.mts +2 -0
  59. package/dist/procurement/index.mjs +2 -0
  60. package/dist/quant.port-BBa66PBT.d.mts +42 -0
  61. package/dist/removal-policy-BItBB8FD.d.mts +29 -0
  62. package/dist/replenishment-rule.port-DnEYtbyD.d.mts +78 -0
  63. package/dist/replenishment.service-BT9P-HKM.mjs +284 -0
  64. package/dist/replenishment.service-HO0sDhB_.d.mts +89 -0
  65. package/dist/reporting/index.d.mts +2 -0
  66. package/dist/reporting/index.mjs +2 -0
  67. package/dist/reporting-CL5ffrKM.mjs +243 -0
  68. package/dist/repositories/index.d.mts +2 -0
  69. package/dist/repositories/index.mjs +2 -0
  70. package/dist/repositories-nZXJKvLW.mjs +842 -0
  71. package/dist/reservation-status-ZfuTaWG0.mjs +22 -0
  72. package/dist/reservation.port-l9NFQ0si.d.mts +85 -0
  73. package/dist/reservations/index.d.mts +2 -0
  74. package/dist/reservations/index.mjs +2 -0
  75. package/dist/reservations-Cg4wN0QB.mjs +112 -0
  76. package/dist/routing/index.d.mts +362 -0
  77. package/dist/routing/index.mjs +582 -0
  78. package/dist/runtime-config-C0ggPkiK.mjs +40 -0
  79. package/dist/runtime-config-CQLtPPqY.d.mts +38 -0
  80. package/dist/scan-token-CNM9QVLY.d.mts +26 -0
  81. package/dist/scanning/index.d.mts +45 -0
  82. package/dist/scanning/index.mjs +228 -0
  83. package/dist/services/index.d.mts +8 -0
  84. package/dist/services/index.mjs +8 -0
  85. package/dist/services-_lLO4Xbl.mjs +1009 -0
  86. package/dist/stock-move-group-C0DqUfPY.mjs +88 -0
  87. package/dist/stock-package-BIarxbDS.d.mts +19 -0
  88. package/dist/stock-quant-CZhgvTu7.d.mts +41 -0
  89. package/dist/tenant-guard-6Ne-BILP.mjs +12 -0
  90. package/dist/tenant-isolation.error-D3OcKUdx.mjs +11 -0
  91. package/dist/trace.service-B9vAh-l-.d.mts +55 -0
  92. package/dist/trace.service-DE6Eh8_8.mjs +71 -0
  93. package/dist/traceability/index.d.mts +2 -0
  94. package/dist/traceability/index.mjs +2 -0
  95. package/dist/types/index.d.mts +2 -0
  96. package/dist/types/index.mjs +1 -0
  97. package/dist/unit-of-work.port-CWEkrDKu.d.mts +17 -0
  98. package/dist/valuation/index.d.mts +78 -0
  99. package/dist/valuation/index.mjs +103 -0
  100. package/dist/valuation-policy-Dco8c9Vw.d.mts +14 -0
  101. package/dist/virtual-locations-B9zXqPdi.d.mts +38 -0
  102. package/package.json +155 -0
@@ -0,0 +1,284 @@
1
+ import { h as ValidationError, u as ProcurementNotApprovedError } from "./domain-errors-D7S9ydNF.mjs";
2
+ import { t as assertTenantContext } from "./tenant-guard-6Ne-BILP.mjs";
3
+ import { t as FlowEvents } from "./event-types-BSqQOvXv.mjs";
4
+ import { a as formatDocumentNumber, i as requireStandardMode } from "./runtime-config-C0ggPkiK.mjs";
5
+ import { n as MoveStatus } from "./move-status-DkaFp2GD.mjs";
6
+ //#region src/services/procurement.service.ts
7
+ var ProcurementService = class {
8
+ constructor(procurementPort, moveGroupPort, movePort, quantPort, unitOfWork, eventEmitter, idempotency, getNextSequence, virtualLocations) {
9
+ this.procurementPort = procurementPort;
10
+ this.moveGroupPort = moveGroupPort;
11
+ this.movePort = movePort;
12
+ this.quantPort = quantPort;
13
+ this.unitOfWork = unitOfWork;
14
+ this.eventEmitter = eventEmitter;
15
+ this.idempotency = idempotency;
16
+ this.getNextSequence = getNextSequence;
17
+ this.virtualLocations = virtualLocations;
18
+ }
19
+ async create(input, ctx) {
20
+ assertTenantContext(ctx);
21
+ if (!input.items || input.items.length === 0) throw new ValidationError("Procurement order must have at least one item");
22
+ const orderNumber = formatDocumentNumber("PO", await this.getNextSequence("PO", ctx.organizationId));
23
+ return this.procurementPort.create({
24
+ organizationId: ctx.organizationId,
25
+ orderNumber,
26
+ vendorRef: input.vendorRef,
27
+ destinationNodeId: input.destinationNodeId,
28
+ destinationLocationId: input.destinationLocationId,
29
+ status: "draft",
30
+ items: input.items.map((item) => ({
31
+ skuRef: item.skuRef,
32
+ quantity: item.quantity,
33
+ quantityReceived: 0,
34
+ unitCost: item.unitCost,
35
+ expectedAt: item.expectedAt
36
+ })),
37
+ expectedAt: input.items[0]?.expectedAt,
38
+ sourceDemandRefs: input.sourceDemandRefs,
39
+ createdBy: ctx.actorId,
40
+ metadata: input.metadata
41
+ });
42
+ }
43
+ async getById(orderId, ctx) {
44
+ assertTenantContext(ctx);
45
+ return this.procurementPort.findById(orderId, ctx);
46
+ }
47
+ async list(query, ctx) {
48
+ assertTenantContext(ctx);
49
+ return this.procurementPort.list(query, ctx);
50
+ }
51
+ async approve(orderId, ctx) {
52
+ assertTenantContext(ctx);
53
+ const order = await this.requireOrder(orderId, ctx);
54
+ if (order.status !== "draft") throw new ValidationError(`Cannot approve order in status: ${order.status}`);
55
+ return this.procurementPort.updateStatus(orderId, "approved", {});
56
+ }
57
+ async receive(orderId, payload, ctx) {
58
+ assertTenantContext(ctx);
59
+ const scopedKey = ctx.idempotencyKey ? `${ctx.organizationId}:${ctx.idempotencyKey}` : null;
60
+ if (scopedKey && this.idempotency) {
61
+ if (this.idempotency.claim) {
62
+ const claimResult = await this.idempotency.claim(scopedKey);
63
+ if (claimResult.status === "hit") return claimResult.result;
64
+ if (claimResult.status === "busy") throw new Error(`Concurrent operation in progress for idempotency key: ${ctx.idempotencyKey}`);
65
+ try {
66
+ const result = await this.executeReceive(orderId, payload, ctx);
67
+ await this.idempotency?.complete?.(scopedKey, result);
68
+ return result;
69
+ } catch (err) {
70
+ await this.idempotency?.release?.(scopedKey);
71
+ throw err;
72
+ }
73
+ }
74
+ const cached = await this.idempotency.check(scopedKey);
75
+ if (cached.hit) return cached.result;
76
+ const result = await this.executeReceive(orderId, payload, ctx);
77
+ await this.idempotency.save(scopedKey, result);
78
+ return result;
79
+ }
80
+ return this.executeReceive(orderId, payload, ctx);
81
+ }
82
+ async executeReceive(orderId, payload, ctx) {
83
+ const order = await this.requireOrder(orderId, ctx);
84
+ if (![
85
+ "approved",
86
+ "ordered",
87
+ "partially_received"
88
+ ].includes(order.status)) throw new ProcurementNotApprovedError(orderId);
89
+ for (const line of payload.lines) {
90
+ if (line.quantityReceived <= 0) throw new ValidationError(`quantityReceived must be positive for ${line.skuRef}`);
91
+ const orderItem = order.items.find((i) => i.skuRef === line.skuRef);
92
+ if (orderItem) {
93
+ if (orderItem.quantityReceived + line.quantityReceived > orderItem.quantity * 1.1) throw new ValidationError(`Over-receipt: ${line.skuRef} would exceed ordered quantity by more than 10%`);
94
+ }
95
+ }
96
+ return await this.unitOfWork.withTransaction(async (session) => {
97
+ const receivedAt = payload.receivedAt ?? /* @__PURE__ */ new Date();
98
+ const docNumber = formatDocumentNumber("RCV", await this.getNextSequence("RCV", ctx.organizationId));
99
+ const moveGroup = await this.moveGroupPort.create({
100
+ organizationId: ctx.organizationId,
101
+ groupType: "receipt",
102
+ documentNumber: docNumber,
103
+ destinationNodeId: order.destinationNodeId,
104
+ counterparty: {
105
+ type: "vendor",
106
+ id: order.vendorRef
107
+ },
108
+ createdBy: ctx.actorId
109
+ }, session);
110
+ for (const line of payload.lines) {
111
+ const orderItem = order.items.find((i) => i.skuRef === line.skuRef);
112
+ if (!orderItem) continue;
113
+ const move = await this.movePort.create({
114
+ moveGroupId: moveGroup._id,
115
+ operationType: "receipt",
116
+ skuRef: line.skuRef,
117
+ sourceLocationId: this.virtualLocations.vendor,
118
+ destinationLocationId: order.destinationLocationId ?? order.destinationNodeId,
119
+ quantityPlanned: line.quantityReceived,
120
+ organizationId: ctx.organizationId,
121
+ createdBy: ctx.actorId,
122
+ metadata: {
123
+ procurementOrderId: orderId,
124
+ unitCost: line.unitCost ?? orderItem.unitCost
125
+ }
126
+ }, session);
127
+ await this.movePort.updateStatus(move._id, MoveStatus.done, {
128
+ quantityDone: line.quantityReceived,
129
+ executedAt: receivedAt
130
+ }, session);
131
+ await this.quantPort.upsert({
132
+ organizationId: ctx.organizationId,
133
+ skuRef: line.skuRef,
134
+ locationId: order.destinationLocationId ?? order.destinationNodeId,
135
+ quantityDelta: line.quantityReceived,
136
+ inDate: receivedAt,
137
+ unitCost: line.unitCost ?? orderItem.unitCost
138
+ }, session);
139
+ }
140
+ const updatedItems = order.items.map((item) => {
141
+ const receiveLine = payload.lines.find((l) => l.skuRef === item.skuRef);
142
+ return {
143
+ ...item,
144
+ quantityReceived: item.quantityReceived + (receiveLine?.quantityReceived ?? 0)
145
+ };
146
+ });
147
+ const allReceived = updatedItems.every((item) => item.quantityReceived >= item.quantity);
148
+ const newStatus = allReceived ? "received" : "partially_received";
149
+ const updated = await this.procurementPort.updateStatus(orderId, newStatus, {
150
+ items: updatedItems,
151
+ receivedAt: allReceived ? receivedAt : void 0
152
+ }, session);
153
+ await this.eventEmitter.emit(FlowEvents.PROCUREMENT_RECEIVED, {
154
+ organizationId: ctx.organizationId,
155
+ orderId: updated._id,
156
+ orderNumber: updated.orderNumber,
157
+ vendorRef: updated.vendorRef,
158
+ destinationNodeId: updated.destinationNodeId,
159
+ itemCount: payload.lines.length
160
+ }, session);
161
+ return updated;
162
+ });
163
+ }
164
+ async cancel(orderId, ctx) {
165
+ assertTenantContext(ctx);
166
+ if ((await this.requireOrder(orderId, ctx)).status === "received") throw new ValidationError("Cannot cancel a fully received procurement order");
167
+ return this.procurementPort.updateStatus(orderId, "cancelled", {});
168
+ }
169
+ async requireOrder(orderId, ctx) {
170
+ const order = await this.procurementPort.findById(orderId, ctx);
171
+ if (!order) throw new ValidationError(`Procurement order ${orderId} not found`);
172
+ return order;
173
+ }
174
+ };
175
+ //#endregion
176
+ //#region src/services/replenishment.service.ts
177
+ var ReplenishmentService = class {
178
+ constructor(rulePort, quantPort, movePort, procurementPort, eventEmitter, getNextSequence, runtimeConfig) {
179
+ this.rulePort = rulePort;
180
+ this.quantPort = quantPort;
181
+ this.movePort = movePort;
182
+ this.procurementPort = procurementPort;
183
+ this.eventEmitter = eventEmitter;
184
+ this.getNextSequence = getNextSequence;
185
+ this.runtimeConfig = runtimeConfig;
186
+ }
187
+ async evaluateRules(scope, ctx) {
188
+ requireStandardMode(this.runtimeConfig, "Replenishment");
189
+ assertTenantContext(ctx);
190
+ let rules;
191
+ if (scope.skuRef) rules = await this.rulePort.findBySkuRef(scope.skuRef, ctx);
192
+ else if (scope.nodeId) rules = await this.rulePort.findByNode(scope.nodeId, ctx);
193
+ else rules = await this.rulePort.list(ctx);
194
+ const triggers = [];
195
+ for (const rule of rules) {
196
+ if (!rule.enabled) continue;
197
+ const availability = await this.quantPort.getAvailability({
198
+ skuRef: rule.skuRef,
199
+ locationId: rule.locationId
200
+ }, ctx);
201
+ const incoming = (await this.movePort.findMany({
202
+ organizationId: ctx.organizationId,
203
+ skuRef: rule.skuRef,
204
+ destinationLocationId: rule.locationId,
205
+ status: { $in: [
206
+ "draft",
207
+ "planned",
208
+ "waiting",
209
+ "ready"
210
+ ] }
211
+ }, ctx)).reduce((sum, m) => sum + m.quantityPlanned, 0);
212
+ const outgoing = (await this.movePort.findMany({
213
+ organizationId: ctx.organizationId,
214
+ skuRef: rule.skuRef,
215
+ sourceLocationId: rule.locationId,
216
+ status: { $in: [
217
+ "draft",
218
+ "planned",
219
+ "waiting",
220
+ "ready"
221
+ ] }
222
+ }, ctx)).reduce((sum, m) => sum + m.quantityPlanned, 0);
223
+ const projected = availability.quantityOnHand + incoming - outgoing;
224
+ if (projected <= rule.reorderPoint) {
225
+ let needed = rule.targetLevel - projected;
226
+ if (rule.minOrderQty && needed < rule.minOrderQty) needed = rule.minOrderQty;
227
+ if (rule.multipleOf && rule.multipleOf > 1) needed = Math.ceil(needed / rule.multipleOf) * rule.multipleOf;
228
+ if (rule.maxOrderQty && needed > rule.maxOrderQty) needed = rule.maxOrderQty;
229
+ triggers.push({
230
+ ruleId: rule._id,
231
+ skuRef: rule.skuRef,
232
+ locationId: rule.locationId ?? rule.scopeRef,
233
+ currentLevel: projected,
234
+ reorderPoint: rule.reorderPoint,
235
+ targetLevel: rule.targetLevel,
236
+ suggestedQty: needed,
237
+ preferredSourceType: rule.preferredSourceType,
238
+ preferredSourceRef: rule.preferredSourceRef
239
+ });
240
+ }
241
+ }
242
+ return { triggers };
243
+ }
244
+ async generateDemand(evaluation, ctx) {
245
+ assertTenantContext(ctx);
246
+ const orders = [];
247
+ const vendorGroups = /* @__PURE__ */ new Map();
248
+ for (const trigger of evaluation.triggers) {
249
+ const key = trigger.preferredSourceRef ?? "default_vendor";
250
+ if (!vendorGroups.has(key)) vendorGroups.set(key, []);
251
+ vendorGroups.get(key)?.push(trigger);
252
+ }
253
+ for (const [vendorRef, triggers] of vendorGroups) {
254
+ const orderNumber = formatDocumentNumber("PO", await this.getNextSequence("PO", ctx.organizationId));
255
+ const order = await this.procurementPort.create({
256
+ organizationId: ctx.organizationId,
257
+ orderNumber,
258
+ vendorRef,
259
+ destinationNodeId: triggers[0].locationId,
260
+ status: "draft",
261
+ items: triggers.map((t) => ({
262
+ skuRef: t.skuRef,
263
+ quantity: t.suggestedQty,
264
+ quantityReceived: 0,
265
+ unitCost: 0
266
+ })),
267
+ sourceDemandRefs: triggers.map((t) => t.ruleId),
268
+ createdBy: ctx.actorId
269
+ });
270
+ orders.push(order);
271
+ for (const trigger of triggers) await this.eventEmitter.emit(FlowEvents.REPLENISHMENT_TRIGGERED, {
272
+ organizationId: ctx.organizationId,
273
+ ruleId: trigger.ruleId,
274
+ skuRef: trigger.skuRef,
275
+ currentLevel: trigger.currentLevel,
276
+ reorderPoint: trigger.reorderPoint,
277
+ suggestedQty: trigger.suggestedQty
278
+ });
279
+ }
280
+ return orders;
281
+ }
282
+ };
283
+ //#endregion
284
+ export { ProcurementService as n, ReplenishmentService as t };
@@ -0,0 +1,89 @@
1
+ import { n as VirtualLocationMap } from "./virtual-locations-B9zXqPdi.mjs";
2
+ import { n as MovePort } from "./move.port-Qg1CYp7h.mjs";
3
+ import { i as PaginatedResult, r as ListQuery, t as FlowContext } from "./index-DFF0GJ4J.mjs";
4
+ import { a as ProcurementOrder, n as ProcurementPort, t as ReplenishmentRulePort } from "./replenishment-rule.port-DnEYtbyD.mjs";
5
+ import { n as MoveGroupPort } from "./move-group.port-DHGoQA3d.mjs";
6
+ import { n as UnitOfWork } from "./unit-of-work.port-CWEkrDKu.mjs";
7
+ import { t as EventEmitterPort } from "./event-emitter.port-BFh2pasY.mjs";
8
+ import { r as QuantPort } from "./quant.port-BBa66PBT.mjs";
9
+ import { t as RuntimeConfig } from "./runtime-config-CQLtPPqY.mjs";
10
+ import { t as IdempotencyPort } from "./idempotency.port-CTC70JON.mjs";
11
+
12
+ //#region src/services/procurement.service.d.ts
13
+ interface CreateProcurementInput {
14
+ vendorRef: string;
15
+ destinationNodeId: string;
16
+ destinationLocationId?: string;
17
+ items: Array<{
18
+ skuRef: string;
19
+ quantity: number;
20
+ unitCost: number;
21
+ expectedAt?: Date;
22
+ }>;
23
+ sourceDemandRefs?: string[];
24
+ metadata?: Record<string, unknown>;
25
+ }
26
+ interface ReceivePayload {
27
+ lines: Array<{
28
+ skuRef: string;
29
+ quantityReceived: number;
30
+ lotCode?: string;
31
+ unitCost?: number;
32
+ }>;
33
+ receivedAt?: Date;
34
+ }
35
+ declare class ProcurementService {
36
+ private procurementPort;
37
+ private moveGroupPort;
38
+ private movePort;
39
+ private quantPort;
40
+ private unitOfWork;
41
+ private eventEmitter;
42
+ private idempotency;
43
+ private getNextSequence;
44
+ private virtualLocations;
45
+ constructor(procurementPort: ProcurementPort, moveGroupPort: MoveGroupPort, movePort: MovePort, quantPort: QuantPort, unitOfWork: UnitOfWork, eventEmitter: EventEmitterPort, idempotency: IdempotencyPort | null, getNextSequence: (prefix: string, organizationId: string) => Promise<number>, virtualLocations: VirtualLocationMap);
46
+ create(input: CreateProcurementInput, ctx: FlowContext): Promise<ProcurementOrder>;
47
+ getById(orderId: string, ctx: FlowContext): Promise<ProcurementOrder | null>;
48
+ list(query: ListQuery, ctx: FlowContext): Promise<PaginatedResult<ProcurementOrder>>;
49
+ approve(orderId: string, ctx: FlowContext): Promise<ProcurementOrder>;
50
+ receive(orderId: string, payload: ReceivePayload, ctx: FlowContext): Promise<ProcurementOrder>;
51
+ private executeReceive;
52
+ cancel(orderId: string, ctx: FlowContext): Promise<ProcurementOrder>;
53
+ private requireOrder;
54
+ }
55
+ //#endregion
56
+ //#region src/services/replenishment.service.d.ts
57
+ interface ReplenishmentScope {
58
+ nodeId?: string;
59
+ skuRef?: string;
60
+ mode?: 'scheduled' | 'emergency';
61
+ }
62
+ interface ReplenishmentTrigger {
63
+ ruleId: string;
64
+ skuRef: string;
65
+ locationId: string;
66
+ currentLevel: number;
67
+ reorderPoint: number;
68
+ targetLevel: number;
69
+ suggestedQty: number;
70
+ preferredSourceType?: string;
71
+ preferredSourceRef?: string;
72
+ }
73
+ interface ReplenishmentEvaluation {
74
+ triggers: ReplenishmentTrigger[];
75
+ }
76
+ declare class ReplenishmentService {
77
+ private rulePort;
78
+ private quantPort;
79
+ private movePort;
80
+ private procurementPort;
81
+ private eventEmitter;
82
+ private getNextSequence;
83
+ private runtimeConfig;
84
+ constructor(rulePort: ReplenishmentRulePort, quantPort: QuantPort, movePort: MovePort, procurementPort: ProcurementPort, eventEmitter: EventEmitterPort, getNextSequence: (prefix: string, organizationId: string) => Promise<number>, runtimeConfig: RuntimeConfig);
85
+ evaluateRules(scope: ReplenishmentScope, ctx: FlowContext): Promise<ReplenishmentEvaluation>;
86
+ generateDemand(evaluation: ReplenishmentEvaluation, ctx: FlowContext): Promise<ProcurementOrder[]>;
87
+ }
88
+ //#endregion
89
+ export { CreateProcurementInput as a, ReplenishmentTrigger as i, ReplenishmentScope as n, ProcurementService as o, ReplenishmentService as r, ReceivePayload as s, ReplenishmentEvaluation as t };
@@ -0,0 +1,2 @@
1
+ import { a as StockAgingResult, c as AvailabilityMatrixQuery, i as StockAgingReport, l as AvailabilityMatrixResult, n as TurnoverResult, o as HealthMetricsReport, r as AgingBucket, s as StockHealthMetrics, t as TurnoverReport, u as AvailabilityReport } from "../index-BmNm3oNU2.mjs";
2
+ export { AgingBucket, AvailabilityMatrixQuery, AvailabilityMatrixResult, AvailabilityReport, HealthMetricsReport, StockAgingReport, StockAgingResult, StockHealthMetrics, TurnoverReport, TurnoverResult };
@@ -0,0 +1,2 @@
1
+ import { i as AvailabilityReport, n as StockAgingReport, r as HealthMetricsReport, t as TurnoverReport } from "../reporting-CL5ffrKM.mjs";
2
+ export { AvailabilityReport, HealthMetricsReport, StockAgingReport, TurnoverReport };
@@ -0,0 +1,243 @@
1
+ import { t as assertTenantContext } from "./tenant-guard-6Ne-BILP.mjs";
2
+ //#region src/reporting/availability.ts
3
+ var AvailabilityReport = class {
4
+ constructor(quantPort, locationPort) {
5
+ this.quantPort = quantPort;
6
+ this.locationPort = locationPort;
7
+ }
8
+ async getMatrix(query, ctx) {
9
+ assertTenantContext(ctx);
10
+ const matrix = [];
11
+ if (query.nodeIds && query.nodeIds.length > 0) for (const skuRef of query.skuRefs) {
12
+ const nodes = [];
13
+ let totalOnHand = 0;
14
+ let totalAvailable = 0;
15
+ for (const nodeId of query.nodeIds) {
16
+ const availability = await this.quantPort.getAvailability({
17
+ skuRef,
18
+ nodeId
19
+ }, ctx);
20
+ nodes.push({
21
+ nodeId,
22
+ onHand: availability.quantityOnHand,
23
+ reserved: availability.quantityReserved,
24
+ available: availability.quantityAvailable,
25
+ incoming: availability.quantityIncoming,
26
+ outgoing: availability.quantityOutgoing
27
+ });
28
+ totalOnHand += availability.quantityOnHand;
29
+ totalAvailable += availability.quantityAvailable;
30
+ }
31
+ matrix.push({
32
+ skuRef,
33
+ nodes,
34
+ totalOnHand,
35
+ totalAvailable
36
+ });
37
+ }
38
+ else for (const skuRef of query.skuRefs) {
39
+ const availability = await this.quantPort.getAvailability({ skuRef }, ctx);
40
+ const nodeGroups = /* @__PURE__ */ new Map();
41
+ for (const breakdown of availability.breakdowns) {
42
+ let nodeId = "unknown";
43
+ if (this.locationPort) {
44
+ const loc = await this.locationPort.findById(breakdown.locationId, ctx);
45
+ if (loc) nodeId = loc.nodeId;
46
+ }
47
+ const existing = nodeGroups.get(nodeId) ?? {
48
+ onHand: 0,
49
+ reserved: 0,
50
+ available: 0,
51
+ incoming: 0,
52
+ outgoing: 0
53
+ };
54
+ existing.onHand += breakdown.quantityOnHand;
55
+ existing.reserved += breakdown.quantityReserved;
56
+ existing.available += breakdown.quantityAvailable;
57
+ existing.incoming += breakdown.quantityIncoming ?? 0;
58
+ existing.outgoing += breakdown.quantityOutgoing ?? 0;
59
+ nodeGroups.set(nodeId, existing);
60
+ }
61
+ if (nodeGroups.size === 0) nodeGroups.set("all", {
62
+ onHand: availability.quantityOnHand,
63
+ reserved: availability.quantityReserved,
64
+ available: availability.quantityAvailable,
65
+ incoming: availability.quantityIncoming,
66
+ outgoing: availability.quantityOutgoing
67
+ });
68
+ const nodes = [...nodeGroups.entries()].map(([nodeId, data]) => ({
69
+ nodeId,
70
+ ...data
71
+ }));
72
+ matrix.push({
73
+ skuRef,
74
+ nodes,
75
+ totalOnHand: availability.quantityOnHand,
76
+ totalAvailable: availability.quantityAvailable
77
+ });
78
+ }
79
+ return { matrix };
80
+ }
81
+ };
82
+ //#endregion
83
+ //#region src/reporting/health-metrics.ts
84
+ var HealthMetricsReport = class {
85
+ constructor(quantPort, _movePort) {
86
+ this.quantPort = quantPort;
87
+ }
88
+ async generate(ctx) {
89
+ assertTenantContext(ctx);
90
+ const withStock = (await this.quantPort.findMany({ organizationId: ctx.organizationId }, ctx)).filter((q) => q.quantityOnHand > 0);
91
+ const totalOnHand = withStock.reduce((s, q) => s + q.quantityOnHand, 0);
92
+ const totalValue = withStock.reduce((s, q) => s + q.quantityOnHand * (q.unitCost ?? 0), 0);
93
+ const uniqueSkus = new Set(withStock.map((q) => q.skuRef));
94
+ const now = /* @__PURE__ */ new Date();
95
+ const yearAgo = /* @__PURE__ */ new Date(now.getTime() - 365 * 864e5);
96
+ const deadStockValue = withStock.filter((q) => {
97
+ const lastMove = q.lastMovementAt ?? q.inDate;
98
+ return new Date(lastMove).getTime() < yearAgo.getTime();
99
+ }).reduce((s, q) => s + q.quantityOnHand * (q.unitCost ?? 0), 0);
100
+ const thirtyDaysOut = new Date(now.getTime() + 30 * 864e5);
101
+ const expiryRiskValue = withStock.filter((q) => q.lotExpiresAt && new Date(q.lotExpiresAt).getTime() < thirtyDaysOut.getTime()).reduce((s, q) => s + q.quantityOnHand * (q.unitCost ?? 0), 0);
102
+ return {
103
+ turnoverRate: 0,
104
+ daysOfInventory: 0,
105
+ stockoutRate: 0,
106
+ fillRate: 0,
107
+ deadStockPercentage: totalValue > 0 ? Math.round(deadStockValue / totalValue * 1e4) / 100 : 0,
108
+ expiryRiskValue: Math.round(expiryRiskValue * 100) / 100,
109
+ totalSkus: uniqueSkus.size,
110
+ totalOnHand,
111
+ totalValue: Math.round(totalValue * 100) / 100
112
+ };
113
+ }
114
+ };
115
+ //#endregion
116
+ //#region src/reporting/stock-aging.ts
117
+ const BUCKET_RANGES = [
118
+ {
119
+ label: "0-30 days",
120
+ minDays: 0,
121
+ maxDays: 30
122
+ },
123
+ {
124
+ label: "31-60 days",
125
+ minDays: 31,
126
+ maxDays: 60
127
+ },
128
+ {
129
+ label: "61-90 days",
130
+ minDays: 61,
131
+ maxDays: 90
132
+ },
133
+ {
134
+ label: "91-180 days",
135
+ minDays: 91,
136
+ maxDays: 180
137
+ },
138
+ {
139
+ label: "181-365 days",
140
+ minDays: 181,
141
+ maxDays: 365
142
+ },
143
+ {
144
+ label: "365+ days",
145
+ minDays: 366,
146
+ maxDays: Infinity
147
+ }
148
+ ];
149
+ var StockAgingReport = class {
150
+ constructor(quantPort) {
151
+ this.quantPort = quantPort;
152
+ }
153
+ async generate(ctx, nodeId) {
154
+ assertTenantContext(ctx);
155
+ const now = /* @__PURE__ */ new Date();
156
+ const quants = await this.quantPort.findMany({
157
+ organizationId: ctx.organizationId,
158
+ ...nodeId ? { nodeId } : {},
159
+ quantityOnHand: { $gt: 0 }
160
+ }, ctx);
161
+ const buckets = BUCKET_RANGES.map((r) => ({
162
+ ...r,
163
+ quantity: 0,
164
+ value: 0,
165
+ skuCount: 0
166
+ }));
167
+ const slowMoving = [];
168
+ const deadStock = [];
169
+ for (const q of quants) {
170
+ const lastMove = q.lastMovementAt ?? q.inDate;
171
+ const ageDays = Math.floor((now.getTime() - new Date(lastMove).getTime()) / 864e5);
172
+ const value = q.quantityOnHand * (q.unitCost ?? 0);
173
+ const bucket = buckets.find((b) => ageDays >= b.minDays && ageDays <= b.maxDays);
174
+ if (bucket) {
175
+ bucket.quantity += q.quantityOnHand;
176
+ bucket.value += value;
177
+ bucket.skuCount++;
178
+ }
179
+ if (ageDays > 90) slowMoving.push({
180
+ skuRef: q.skuRef,
181
+ locationId: q.locationId,
182
+ quantity: q.quantityOnHand,
183
+ ageDays
184
+ });
185
+ if (ageDays > 365) deadStock.push({
186
+ skuRef: q.skuRef,
187
+ locationId: q.locationId,
188
+ quantity: q.quantityOnHand,
189
+ ageDays
190
+ });
191
+ }
192
+ return {
193
+ asOfDate: now,
194
+ buckets,
195
+ slowMoving,
196
+ deadStock
197
+ };
198
+ }
199
+ };
200
+ //#endregion
201
+ //#region src/reporting/turnover.ts
202
+ var TurnoverReport = class {
203
+ constructor(quantPort, movePort) {
204
+ this.quantPort = quantPort;
205
+ this.movePort = movePort;
206
+ }
207
+ async generate(periodDays, ctx) {
208
+ assertTenantContext(ctx);
209
+ const end = /* @__PURE__ */ new Date();
210
+ const start = /* @__PURE__ */ new Date(end.getTime() - periodDays * 864e5);
211
+ const totalCOGS = (await this.movePort.findMany({
212
+ organizationId: ctx.organizationId,
213
+ operationType: "shipment",
214
+ status: "done",
215
+ executedAt: {
216
+ $gte: start,
217
+ $lte: end
218
+ }
219
+ }, ctx)).reduce((sum, m) => {
220
+ const cost = m.metadata?.unitCost ?? 0;
221
+ return sum + (m.quantityDone ?? 0) * cost;
222
+ }, 0);
223
+ const averageInventoryValue = (await this.quantPort.findMany({
224
+ organizationId: ctx.organizationId,
225
+ quantityOnHand: { $gt: 0 }
226
+ }, ctx)).reduce((sum, q) => sum + q.quantityOnHand * (q.unitCost ?? 0), 0);
227
+ const turnoverRate = averageInventoryValue > 0 ? totalCOGS / averageInventoryValue : 0;
228
+ const daysOfInventory = turnoverRate > 0 ? 365 / turnoverRate : Infinity;
229
+ return {
230
+ period: {
231
+ start,
232
+ end
233
+ },
234
+ totalCOGS,
235
+ averageInventoryValue,
236
+ turnoverRate: Math.round(turnoverRate * 100) / 100,
237
+ daysOfInventory: Math.round(daysOfInventory),
238
+ skuBreakdown: []
239
+ };
240
+ }
241
+ };
242
+ //#endregion
243
+ export { AvailabilityReport as i, StockAgingReport as n, HealthMetricsReport as r, TurnoverReport as t };
@@ -0,0 +1,2 @@
1
+ import { _ as CountRepository, a as extractDocs, c as ReservationRepository, d as ProcurementRepository, f as NodeRepository, g as LocationRepository, h as LotRepository, i as asOffsetResult, l as ReplenishmentRuleRepository, m as MoveRepository, n as RepositoryPlugins, o as toOrgId, p as MoveGroupRepository, r as createRepositories, s as toSession, t as FlowRepositories, u as QuantRepository, v as CostLayerRepository } from "../index-C5PciI9P.mjs";
2
+ export { CostLayerRepository, CountRepository, FlowRepositories, LocationRepository, LotRepository, MoveGroupRepository, MoveRepository, NodeRepository, ProcurementRepository, QuantRepository, ReplenishmentRuleRepository, RepositoryPlugins, ReservationRepository, asOffsetResult, createRepositories, extractDocs, toOrgId, toSession };
@@ -0,0 +1,2 @@
1
+ import { a as ProcurementRepository, c as MoveRepository, d as CountRepository, f as CostLayerRepository, g as toSession, h as toOrgId, i as QuantRepository, l as LotRepository, m as extractDocs, n as ReservationRepository, o as NodeRepository, p as asOffsetResult, r as ReplenishmentRuleRepository, s as MoveGroupRepository, t as createRepositories, u as LocationRepository } from "../repositories-nZXJKvLW.mjs";
2
+ export { CostLayerRepository, CountRepository, LocationRepository, LotRepository, MoveGroupRepository, MoveRepository, NodeRepository, ProcurementRepository, QuantRepository, ReplenishmentRuleRepository, ReservationRepository, asOffsetResult, createRepositories, extractDocs, toOrgId, toSession };