@fayz-ai/inventory 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -2
- package/dist/chunk-2FXO63QV.js +1732 -0
- package/dist/chunk-2FXO63QV.js.map +1 -0
- package/dist/chunk-52AJ7H2W.js +116 -0
- package/dist/chunk-52AJ7H2W.js.map +1 -0
- package/dist/chunk-AFUR3HLA.js +84 -0
- package/dist/chunk-AFUR3HLA.js.map +1 -0
- package/dist/chunk-EF5LVTOC.js +65 -0
- package/dist/chunk-EF5LVTOC.js.map +1 -0
- package/dist/chunk-GBIFZCEN.js +33 -0
- package/dist/chunk-GBIFZCEN.js.map +1 -0
- package/dist/chunk-GDEIBHAB.js +14 -0
- package/dist/chunk-GDEIBHAB.js.map +1 -0
- package/dist/chunk-JN5AZ656.js +35 -0
- package/dist/chunk-JN5AZ656.js.map +1 -0
- package/dist/chunk-JX7NCXJQ.js +15 -0
- package/dist/chunk-JX7NCXJQ.js.map +1 -0
- package/dist/chunk-MCWG3PMP.js +549 -0
- package/dist/chunk-MCWG3PMP.js.map +1 -0
- package/dist/chunk-RUOX7OOY.js +30 -0
- package/dist/chunk-RUOX7OOY.js.map +1 -0
- package/dist/chunk-SZXUGORU.js +13 -0
- package/dist/chunk-SZXUGORU.js.map +1 -0
- package/dist/chunk-W36J5END.js +48 -0
- package/dist/chunk-W36J5END.js.map +1 -0
- package/dist/chunk-YZDGJBJD.js +53 -0
- package/dist/chunk-YZDGJBJD.js.map +1 -0
- package/dist/data/mock.d.ts +3 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/mock.js +10 -0
- package/dist/data/mock.js.map +1 -0
- package/dist/data/provider.d.ts +120 -0
- package/dist/data/provider.d.ts.map +1 -0
- package/dist/data/provider.js +3 -0
- package/dist/data/provider.js.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/supabase.js +13 -0
- package/dist/data/supabase.js.map +1 -0
- package/dist/data/tables.d.ts +25 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/data/tables.js +3 -0
- package/dist/data/tables.js.map +1 -0
- package/dist/domain/consumption.d.ts +33 -0
- package/dist/domain/consumption.d.ts.map +1 -0
- package/dist/domain/consumption.js +3 -0
- package/dist/domain/consumption.js.map +1 -0
- package/dist/domain/count.d.ts +85 -0
- package/dist/domain/count.d.ts.map +1 -0
- package/dist/domain/count.js +3 -0
- package/dist/domain/count.js.map +1 -0
- package/dist/domain/movement.d.ts +101 -0
- package/dist/domain/movement.d.ts.map +1 -0
- package/dist/domain/movement.js +3 -0
- package/dist/domain/movement.js.map +1 -0
- package/dist/domain/product.d.ts +245 -0
- package/dist/domain/product.d.ts.map +1 -0
- package/dist/domain/product.js +3 -0
- package/dist/domain/product.js.map +1 -0
- package/dist/domain/query.d.ts +12 -0
- package/dist/domain/query.d.ts.map +1 -0
- package/dist/domain/query.js +3 -0
- package/dist/domain/query.js.map +1 -0
- package/dist/domain/recipe-sheet.d.ts +133 -0
- package/dist/domain/recipe-sheet.d.ts.map +1 -0
- package/dist/domain/recipe-sheet.js +3 -0
- package/dist/domain/recipe-sheet.js.map +1 -0
- package/dist/domain/recipe.d.ts +138 -0
- package/dist/domain/recipe.d.ts.map +1 -0
- package/dist/domain/recipe.js +3 -0
- package/dist/domain/recipe.js.map +1 -0
- package/dist/domain/reference-data.d.ts +37 -0
- package/dist/domain/reference-data.d.ts.map +1 -0
- package/dist/domain/reference-data.js +3 -0
- package/dist/domain/reference-data.js.map +1 -0
- package/dist/domain/stock.d.ts +132 -0
- package/dist/domain/stock.d.ts.map +1 -0
- package/dist/domain/stock.js +5 -0
- package/dist/domain/stock.js.map +1 -0
- package/dist/domain/usage-audit.d.ts +95 -0
- package/dist/domain/usage-audit.d.ts.map +1 -0
- package/dist/domain/usage-audit.js +3 -0
- package/dist/domain/usage-audit.js.map +1 -0
- package/dist/lib/idempotency.d.ts +4 -0
- package/dist/lib/idempotency.d.ts.map +1 -0
- package/dist/lib/idempotency.js +3 -0
- package/dist/lib/idempotency.js.map +1 -0
- package/dist/store.d.ts +52 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +193 -0
- package/dist/store.js.map +1 -0
- package/package.json +50 -3
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import { activeRecipeFor, explodeRecipe } from './chunk-EF5LVTOC.js';
|
|
2
|
+
import { DEFAULT_STOCK_LOCATION } from './chunk-YZDGJBJD.js';
|
|
3
|
+
import { recentMovementCutoff, summarizeStock, expiryWindow, toPanelRow, isInExpiryWindow, hasHealth } from './chunk-52AJ7H2W.js';
|
|
4
|
+
import { paginate } from './chunk-GDEIBHAB.js';
|
|
5
|
+
import { assertReversible, consumptionKey, skippedConsumption, assertConsumable } from './chunk-GBIFZCEN.js';
|
|
6
|
+
import { isCounted, isDivergent, countProgress } from './chunk-W36J5END.js';
|
|
7
|
+
import { onHandDelta, reversedSourceDelta, sourcePositionChange } from './chunk-AFUR3HLA.js';
|
|
8
|
+
|
|
9
|
+
// src/data/mock.ts
|
|
10
|
+
var TENANT_ID = "mock-tenant";
|
|
11
|
+
var nextId = 1;
|
|
12
|
+
function uid() {
|
|
13
|
+
return String(nextId++);
|
|
14
|
+
}
|
|
15
|
+
function now() {
|
|
16
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
17
|
+
}
|
|
18
|
+
function today() {
|
|
19
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
20
|
+
}
|
|
21
|
+
function createMockInventoryProvider() {
|
|
22
|
+
const products = [];
|
|
23
|
+
const movements = [];
|
|
24
|
+
const positions = [];
|
|
25
|
+
const locations = [
|
|
26
|
+
{ id: uid(), ...DEFAULT_STOCK_LOCATION, isActive: true, tenantId: TENANT_ID, createdAt: now(), updatedAt: now() }
|
|
27
|
+
];
|
|
28
|
+
const suppliers = [{ id: uid(), name: "Main Supplier" }];
|
|
29
|
+
const recipes = [];
|
|
30
|
+
const recipeIngredients = [];
|
|
31
|
+
const recipeGroups = [];
|
|
32
|
+
const countSessions = [];
|
|
33
|
+
const countItems = [];
|
|
34
|
+
const consumptions = /* @__PURE__ */ new Map();
|
|
35
|
+
const countWatermarks = /* @__PURE__ */ new Map();
|
|
36
|
+
const productById = (id) => products.find((p) => p.id === id);
|
|
37
|
+
function withProduct(recipe) {
|
|
38
|
+
const product = productById(recipe.productId);
|
|
39
|
+
return { ...recipe, productName: product?.name ?? recipe.productName, productSalePrice: product?.salePrice };
|
|
40
|
+
}
|
|
41
|
+
function withCostBasis(ingredient) {
|
|
42
|
+
const product = productById(ingredient.productId);
|
|
43
|
+
if (!product) return ingredient;
|
|
44
|
+
return {
|
|
45
|
+
...ingredient,
|
|
46
|
+
productName: product.name ?? ingredient.productName,
|
|
47
|
+
costBasis: { cost: product.costPrice ?? 0, conversionFactor: product.conversionFactor, purchaseUnitId: product.purchaseUnitId }
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function applyPositionDelta(slot, locationId, amount, mode = "add") {
|
|
51
|
+
const position = positions.find((p) => p.productId === slot.productId && p.stockLocationId === locationId && (p.batchNumber ?? null) === (slot.batchNumber ?? null) && (p.expirationDate ?? null) === (slot.expirationDate ?? null));
|
|
52
|
+
if (position) {
|
|
53
|
+
const current = position.quantity;
|
|
54
|
+
position.quantity = mode === "set" ? amount : current + amount;
|
|
55
|
+
position.unitCost = slot.unitCost ?? position.unitCost;
|
|
56
|
+
position.updatedAt = now();
|
|
57
|
+
return position.quantity - current;
|
|
58
|
+
}
|
|
59
|
+
positions.push({
|
|
60
|
+
id: uid(),
|
|
61
|
+
productId: slot.productId,
|
|
62
|
+
quantity: amount,
|
|
63
|
+
unitCost: slot.unitCost ?? 0,
|
|
64
|
+
stockLocationId: locationId,
|
|
65
|
+
stockLocationName: locations.find((l) => l.id === locationId)?.name,
|
|
66
|
+
batchNumber: slot.batchNumber,
|
|
67
|
+
expirationDate: slot.expirationDate,
|
|
68
|
+
tenantId: TENANT_ID,
|
|
69
|
+
createdAt: now()
|
|
70
|
+
});
|
|
71
|
+
return amount;
|
|
72
|
+
}
|
|
73
|
+
const provider = {
|
|
74
|
+
async ensureReferenceData() {
|
|
75
|
+
},
|
|
76
|
+
async getProducts(query) {
|
|
77
|
+
let results = [...products];
|
|
78
|
+
if (query.productType) results = results.filter((p) => p.productType === query.productType);
|
|
79
|
+
if (query.categoryId) results = results.filter((p) => p.categoryId === query.categoryId);
|
|
80
|
+
if (query.isActive !== void 0) results = results.filter((p) => p.isActive === query.isActive);
|
|
81
|
+
if (query.lowStockOnly) results = results.filter((p) => p.currentQuantity <= p.minQuantity);
|
|
82
|
+
if (query.search) {
|
|
83
|
+
const s = query.search.toLowerCase();
|
|
84
|
+
results = results.filter((p) => p.name.toLowerCase().includes(s) || p.sku?.toLowerCase().includes(s) || p.barcode?.includes(s));
|
|
85
|
+
}
|
|
86
|
+
results.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
87
|
+
return paginate(results, query.page, query.pageSize);
|
|
88
|
+
},
|
|
89
|
+
async getProductById(id) {
|
|
90
|
+
return productById(id) ?? null;
|
|
91
|
+
},
|
|
92
|
+
async createProduct(input) {
|
|
93
|
+
const product = {
|
|
94
|
+
id: uid(),
|
|
95
|
+
name: input.name,
|
|
96
|
+
description: input.description,
|
|
97
|
+
sku: input.sku,
|
|
98
|
+
barcode: input.barcode,
|
|
99
|
+
brand: input.brand,
|
|
100
|
+
categoryId: input.categoryId,
|
|
101
|
+
productType: input.productType,
|
|
102
|
+
purpose: input.purpose,
|
|
103
|
+
currentQuantity: 0,
|
|
104
|
+
minQuantity: input.minQuantity ?? 0,
|
|
105
|
+
maxQuantity: input.maxQuantity,
|
|
106
|
+
costPrice: input.costPrice ?? 0,
|
|
107
|
+
salePrice: input.salePrice,
|
|
108
|
+
measurementUnitId: input.measurementUnitId,
|
|
109
|
+
purchaseUnitId: input.purchaseUnitId,
|
|
110
|
+
conversionFactor: input.conversionFactor ?? 1,
|
|
111
|
+
isActive: true,
|
|
112
|
+
supplierId: input.supplierId,
|
|
113
|
+
defaultLocationId: input.defaultLocationId,
|
|
114
|
+
imageUrl: input.imageUrl,
|
|
115
|
+
metadata: input.metadata,
|
|
116
|
+
tenantId: TENANT_ID,
|
|
117
|
+
createdAt: now(),
|
|
118
|
+
updatedAt: now()
|
|
119
|
+
};
|
|
120
|
+
products.push(product);
|
|
121
|
+
return product;
|
|
122
|
+
},
|
|
123
|
+
async updateProduct(id, data) {
|
|
124
|
+
const product = productById(id);
|
|
125
|
+
if (!product) throw new Error(`Product ${id} not found`);
|
|
126
|
+
Object.assign(product, data, { updatedAt: now() });
|
|
127
|
+
return product;
|
|
128
|
+
},
|
|
129
|
+
async uploadProductImage(productId, file) {
|
|
130
|
+
const product = productById(productId);
|
|
131
|
+
if (!product) throw new Error(`Product ${productId} not found`);
|
|
132
|
+
const url = typeof URL !== "undefined" && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : `mock://inventory-images/${TENANT_ID}/products/${productId}/${encodeURIComponent(file.name)}`;
|
|
133
|
+
product.imageUrl = url;
|
|
134
|
+
product.updatedAt = now();
|
|
135
|
+
return url;
|
|
136
|
+
},
|
|
137
|
+
async getMovements(query) {
|
|
138
|
+
let results = [...movements];
|
|
139
|
+
if (query.productId) results = results.filter((m) => m.productId === query.productId);
|
|
140
|
+
if (query.movementType) {
|
|
141
|
+
const types = Array.isArray(query.movementType) ? query.movementType : [query.movementType];
|
|
142
|
+
results = results.filter((m) => types.includes(m.movementType));
|
|
143
|
+
}
|
|
144
|
+
if (query.stockLocationId) results = results.filter((m) => m.stockLocationId === query.stockLocationId);
|
|
145
|
+
const range = query.dateRange;
|
|
146
|
+
if (range) results = results.filter((m) => m.movementDate >= range.from && m.movementDate <= range.to);
|
|
147
|
+
if (query.search) {
|
|
148
|
+
const s = query.search.toLowerCase();
|
|
149
|
+
results = results.filter((m) => m.productName?.toLowerCase().includes(s) || m.notes?.toLowerCase().includes(s));
|
|
150
|
+
}
|
|
151
|
+
results.sort((a, b) => b.movementDate.localeCompare(a.movementDate));
|
|
152
|
+
return paginate(results, query.page, query.pageSize);
|
|
153
|
+
},
|
|
154
|
+
async createMovement(input) {
|
|
155
|
+
const product = productById(input.productId);
|
|
156
|
+
const location = input.stockLocationId ? locations.find((l) => l.id === input.stockLocationId) : void 0;
|
|
157
|
+
const unitCost = input.unitCost ?? product?.costPrice ?? 0;
|
|
158
|
+
const movement = {
|
|
159
|
+
id: uid(),
|
|
160
|
+
productId: input.productId,
|
|
161
|
+
productName: product?.name,
|
|
162
|
+
quantity: input.quantity,
|
|
163
|
+
movementType: input.movementType,
|
|
164
|
+
unitCost,
|
|
165
|
+
totalCost: unitCost * input.quantity,
|
|
166
|
+
stockLocationId: input.stockLocationId,
|
|
167
|
+
stockLocationName: location?.name,
|
|
168
|
+
destinationLocationId: input.destinationLocationId,
|
|
169
|
+
supplierId: input.supplierId,
|
|
170
|
+
supplierName: suppliers.find((s) => s.id === input.supplierId)?.name,
|
|
171
|
+
documentNumber: input.documentNumber,
|
|
172
|
+
reason: input.reason,
|
|
173
|
+
notes: input.notes,
|
|
174
|
+
batchNumber: input.batchNumber,
|
|
175
|
+
expirationDate: input.expirationDate,
|
|
176
|
+
movementDate: input.movementDate ?? today(),
|
|
177
|
+
tenantId: TENANT_ID,
|
|
178
|
+
createdAt: now()
|
|
179
|
+
};
|
|
180
|
+
movements.push(movement);
|
|
181
|
+
let stockDelta = onHandDelta(input.movementType, input.quantity);
|
|
182
|
+
if (input.stockLocationId) {
|
|
183
|
+
const slot = { ...input, unitCost };
|
|
184
|
+
const change = sourcePositionChange(input.movementType, input.quantity);
|
|
185
|
+
const applied = applyPositionDelta(slot, input.stockLocationId, change.amount, change.mode);
|
|
186
|
+
if (input.movementType === "adjustment") stockDelta = applied;
|
|
187
|
+
if (input.movementType === "transfer" && input.destinationLocationId) {
|
|
188
|
+
applyPositionDelta(slot, input.destinationLocationId, input.quantity);
|
|
189
|
+
stockDelta = 0;
|
|
190
|
+
}
|
|
191
|
+
} else if (input.movementType === "adjustment") {
|
|
192
|
+
stockDelta = input.quantity - (product?.currentQuantity ?? 0);
|
|
193
|
+
}
|
|
194
|
+
if (product) {
|
|
195
|
+
product.currentQuantity += stockDelta;
|
|
196
|
+
product.updatedAt = now();
|
|
197
|
+
}
|
|
198
|
+
return movement;
|
|
199
|
+
},
|
|
200
|
+
async reverseMovement(movementId) {
|
|
201
|
+
const index = movements.findIndex((m) => m.id === movementId);
|
|
202
|
+
if (index === -1) throw new Error(`Movement ${movementId} not found`);
|
|
203
|
+
const movement = movements[index];
|
|
204
|
+
const product = productById(movement.productId);
|
|
205
|
+
if (product) {
|
|
206
|
+
product.currentQuantity -= onHandDelta(movement.movementType, movement.quantity);
|
|
207
|
+
product.updatedAt = now();
|
|
208
|
+
}
|
|
209
|
+
if (movement.stockLocationId) {
|
|
210
|
+
const slot = {
|
|
211
|
+
productId: movement.productId,
|
|
212
|
+
unitCost: movement.unitCost,
|
|
213
|
+
batchNumber: movement.batchNumber,
|
|
214
|
+
expirationDate: movement.expirationDate
|
|
215
|
+
};
|
|
216
|
+
applyPositionDelta(slot, movement.stockLocationId, reversedSourceDelta(movement.movementType, movement.quantity));
|
|
217
|
+
if (movement.movementType === "transfer" && movement.destinationLocationId) {
|
|
218
|
+
applyPositionDelta(slot, movement.destinationLocationId, -movement.quantity);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
movements.splice(index, 1);
|
|
222
|
+
},
|
|
223
|
+
// The mock has no sold lines, so the honest usage queue is empty.
|
|
224
|
+
async getUsageAudit(_query) {
|
|
225
|
+
return { data: [], total: 0 };
|
|
226
|
+
},
|
|
227
|
+
async correctUsage() {
|
|
228
|
+
throw new Error("Usage corrections need the ledger; the mock provider has none");
|
|
229
|
+
},
|
|
230
|
+
async correctUsageBatch() {
|
|
231
|
+
throw new Error("Usage corrections need the ledger; the mock provider has none");
|
|
232
|
+
},
|
|
233
|
+
async getPositions(productId) {
|
|
234
|
+
return positions.filter((p) => p.productId === productId);
|
|
235
|
+
},
|
|
236
|
+
async getStockPanel(query) {
|
|
237
|
+
const window = expiryWindow(query.expiry, /* @__PURE__ */ new Date());
|
|
238
|
+
const term = query.productSearch?.trim().toLowerCase();
|
|
239
|
+
const batch = query.batch?.toLowerCase();
|
|
240
|
+
return positions.map((pos) => {
|
|
241
|
+
const product = productById(pos.productId);
|
|
242
|
+
return toPanelRow(pos, product, pos.stockLocationName ?? locations.find((l) => l.id === pos.stockLocationId)?.name);
|
|
243
|
+
}).filter((row) => !query.stockLocationId || row.stockLocationId === query.stockLocationId).filter((row) => !query.categoryId || row.categoryId === query.categoryId).filter((row) => !term || row.productName.toLowerCase().includes(term)).filter((row) => !batch || (row.batchNumber ?? "").toLowerCase().includes(batch)).filter((row) => isInExpiryWindow(window, row.expirationDate)).filter((row) => hasHealth(row, query.status)).slice(0, query.limit ?? 500);
|
|
244
|
+
},
|
|
245
|
+
async getLocations() {
|
|
246
|
+
return locations.filter((l) => l.isActive);
|
|
247
|
+
},
|
|
248
|
+
async createLocation(data) {
|
|
249
|
+
const location = {
|
|
250
|
+
id: uid(),
|
|
251
|
+
name: data.name,
|
|
252
|
+
description: data.description,
|
|
253
|
+
isActive: true,
|
|
254
|
+
unitId: data.unitId,
|
|
255
|
+
tenantId: TENANT_ID,
|
|
256
|
+
createdAt: now(),
|
|
257
|
+
updatedAt: now()
|
|
258
|
+
};
|
|
259
|
+
locations.push(location);
|
|
260
|
+
return location;
|
|
261
|
+
},
|
|
262
|
+
async getSuppliers() {
|
|
263
|
+
return suppliers;
|
|
264
|
+
},
|
|
265
|
+
async getCategories() {
|
|
266
|
+
const seen = /* @__PURE__ */ new Map();
|
|
267
|
+
for (const product of products) {
|
|
268
|
+
if (!product.categoryId || seen.has(product.categoryId)) continue;
|
|
269
|
+
seen.set(product.categoryId, {
|
|
270
|
+
id: product.categoryId,
|
|
271
|
+
name: product.categoryName ?? product.categoryId,
|
|
272
|
+
isActive: true,
|
|
273
|
+
tenantId: TENANT_ID,
|
|
274
|
+
createdAt: now(),
|
|
275
|
+
updatedAt: now()
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
279
|
+
},
|
|
280
|
+
async getCountSessions() {
|
|
281
|
+
return [...countSessions].sort((a, b) => b.openedAt.localeCompare(a.openedAt)).map((session) => ({ ...session, ...countProgress(countItems.filter((i) => i.sessionId === session.id)) }));
|
|
282
|
+
},
|
|
283
|
+
async getCountSession(id) {
|
|
284
|
+
return countSessions.find((s) => s.id === id) ?? null;
|
|
285
|
+
},
|
|
286
|
+
async getCountItems(sessionId) {
|
|
287
|
+
return countItems.filter((i) => i.sessionId === sessionId).sort((a, b) => (a.productName ?? "").localeCompare(b.productName ?? ""));
|
|
288
|
+
},
|
|
289
|
+
async openCountSession(input) {
|
|
290
|
+
const location = locations.find((l) => l.id === input.stockLocationId);
|
|
291
|
+
if (!location) throw new Error(`Stock location ${input.stockLocationId} not found`);
|
|
292
|
+
const session = {
|
|
293
|
+
id: uid(),
|
|
294
|
+
reference: input.reference?.trim() || void 0,
|
|
295
|
+
status: "open",
|
|
296
|
+
stockLocationId: input.stockLocationId,
|
|
297
|
+
stockLocationName: location.name,
|
|
298
|
+
categoryId: input.categoryId,
|
|
299
|
+
blind: input.blind ?? true,
|
|
300
|
+
notes: input.notes,
|
|
301
|
+
openedAt: now(),
|
|
302
|
+
tenantId: TENANT_ID,
|
|
303
|
+
createdAt: now(),
|
|
304
|
+
updatedAt: now()
|
|
305
|
+
};
|
|
306
|
+
countSessions.push(session);
|
|
307
|
+
countWatermarks.set(session.id, new Set(movements.map((m) => m.id)));
|
|
308
|
+
for (const product of products) {
|
|
309
|
+
if (!product.isActive || product.productType === "asset") continue;
|
|
310
|
+
if (input.categoryId && product.categoryId !== input.categoryId) continue;
|
|
311
|
+
const slots = positions.filter((p) => p.productId === product.id && p.stockLocationId === input.stockLocationId);
|
|
312
|
+
countItems.push({
|
|
313
|
+
id: uid(),
|
|
314
|
+
sessionId: session.id,
|
|
315
|
+
productId: product.id,
|
|
316
|
+
productName: product.name,
|
|
317
|
+
productSku: product.sku,
|
|
318
|
+
systemQuantity: slots.reduce((sum, p) => sum + (p.quantity ?? 0), 0),
|
|
319
|
+
unitCost: slots[0]?.unitCost ?? product.costPrice ?? 0,
|
|
320
|
+
tenantId: TENANT_ID,
|
|
321
|
+
createdAt: now(),
|
|
322
|
+
updatedAt: now()
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return session;
|
|
326
|
+
},
|
|
327
|
+
async saveCountEntry(itemId, quantity, notes) {
|
|
328
|
+
const item = countItems.find((i) => i.id === itemId);
|
|
329
|
+
if (!item) throw new Error(`Count item ${itemId} not found`);
|
|
330
|
+
item.countedQuantity = quantity == null ? void 0 : quantity;
|
|
331
|
+
item.countedAt = quantity == null ? void 0 : now();
|
|
332
|
+
if (notes !== void 0) item.notes = notes || void 0;
|
|
333
|
+
item.updatedAt = now();
|
|
334
|
+
const session = countSessions.find((s) => s.id === item.sessionId);
|
|
335
|
+
if (session?.status === "open") session.status = "counting";
|
|
336
|
+
return item;
|
|
337
|
+
},
|
|
338
|
+
async closeCountSession(sessionId, reason) {
|
|
339
|
+
const session = countSessions.find((s) => s.id === sessionId);
|
|
340
|
+
if (!session) throw new Error(`Count session ${sessionId} not found`);
|
|
341
|
+
if (session.status === "closed") return { sessionId, adjustmentsCreated: 0, alreadyClosed: true };
|
|
342
|
+
if (session.status === "cancelled") throw new Error("This count was cancelled");
|
|
343
|
+
const items = countItems.filter((item) => item.sessionId === sessionId);
|
|
344
|
+
const uncounted = items.filter((item) => !isCounted(item));
|
|
345
|
+
if (uncounted.length > 0) throw new Error(`Count every line before closing (${uncounted.length} uncounted)`);
|
|
346
|
+
const watermark = countWatermarks.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
347
|
+
const changedAfterOpening = movements.some((movement) => !watermark.has(movement.id) && items.some((item) => item.productId === movement.productId) && (movement.stockLocationId === session.stockLocationId || movement.destinationLocationId === session.stockLocationId));
|
|
348
|
+
if (changedAfterOpening) throw new Error("Stock changed after this count opened; review and recount");
|
|
349
|
+
let emitted = 0;
|
|
350
|
+
for (const item of items) {
|
|
351
|
+
if (!isDivergent(item) || item.movementId) continue;
|
|
352
|
+
const counted = item.countedQuantity;
|
|
353
|
+
const delta = counted - item.systemQuantity;
|
|
354
|
+
const hasBatchStock = positions.some((position) => position.productId === item.productId && position.stockLocationId === session.stockLocationId && position.quantity !== 0 && Boolean(position.batchNumber || position.expirationDate));
|
|
355
|
+
if (hasBatchStock) throw new Error(`Product ${item.productId} has batch stock; use a batch-aware count`);
|
|
356
|
+
const movement = {
|
|
357
|
+
id: uid(),
|
|
358
|
+
productId: item.productId,
|
|
359
|
+
productName: item.productName,
|
|
360
|
+
quantity: delta,
|
|
361
|
+
movementType: "adjustment",
|
|
362
|
+
unitCost: item.unitCost,
|
|
363
|
+
totalCost: item.unitCost * Math.abs(delta),
|
|
364
|
+
stockLocationId: session.stockLocationId,
|
|
365
|
+
stockLocationName: session.stockLocationName,
|
|
366
|
+
reason: reason || "Stock count",
|
|
367
|
+
movementDate: today(),
|
|
368
|
+
metadata: {
|
|
369
|
+
countSessionId: sessionId,
|
|
370
|
+
countItemId: item.id,
|
|
371
|
+
systemQuantity: item.systemQuantity,
|
|
372
|
+
countedQuantity: counted,
|
|
373
|
+
variance: delta
|
|
374
|
+
},
|
|
375
|
+
tenantId: TENANT_ID,
|
|
376
|
+
createdAt: now()
|
|
377
|
+
};
|
|
378
|
+
movements.push(movement);
|
|
379
|
+
item.movementId = movement.id;
|
|
380
|
+
item.updatedAt = now();
|
|
381
|
+
applyPositionDelta({ productId: item.productId, unitCost: item.unitCost }, session.stockLocationId, delta);
|
|
382
|
+
const product = productById(item.productId);
|
|
383
|
+
if (product) {
|
|
384
|
+
product.currentQuantity += delta;
|
|
385
|
+
product.updatedAt = now();
|
|
386
|
+
}
|
|
387
|
+
emitted += 1;
|
|
388
|
+
}
|
|
389
|
+
session.status = "closed";
|
|
390
|
+
session.closedAt = now();
|
|
391
|
+
session.updatedAt = now();
|
|
392
|
+
countWatermarks.delete(sessionId);
|
|
393
|
+
return { sessionId, adjustmentsCreated: emitted, alreadyClosed: false };
|
|
394
|
+
},
|
|
395
|
+
async cancelCountSession(sessionId) {
|
|
396
|
+
const session = countSessions.find((s) => s.id === sessionId);
|
|
397
|
+
if (!session) throw new Error(`Count session ${sessionId} not found`);
|
|
398
|
+
if (session.status === "closed") throw new Error("A closed count cannot be cancelled");
|
|
399
|
+
session.status = "cancelled";
|
|
400
|
+
session.updatedAt = now();
|
|
401
|
+
countWatermarks.delete(sessionId);
|
|
402
|
+
return session;
|
|
403
|
+
},
|
|
404
|
+
async getRecipes() {
|
|
405
|
+
return recipes.filter((r) => r.isActive).map(withProduct);
|
|
406
|
+
},
|
|
407
|
+
async getRecipeById(id) {
|
|
408
|
+
const recipe = recipes.find((r) => r.id === id);
|
|
409
|
+
return recipe ? withProduct(recipe) : null;
|
|
410
|
+
},
|
|
411
|
+
async getRecipeIngredients(recipeId) {
|
|
412
|
+
return recipeIngredients.filter((ri) => ri.recipeId === recipeId).sort((a, b) => a.displayOrder - b.displayOrder).map(withCostBasis);
|
|
413
|
+
},
|
|
414
|
+
async getAllRecipeIngredients() {
|
|
415
|
+
return recipeIngredients.map(withCostBasis);
|
|
416
|
+
},
|
|
417
|
+
async getRecipeGroups(recipeId) {
|
|
418
|
+
return recipeGroups.filter((g) => g.recipeId === recipeId).sort((a, b) => a.displayOrder - b.displayOrder);
|
|
419
|
+
},
|
|
420
|
+
async createRecipe(input) {
|
|
421
|
+
const recipe = {
|
|
422
|
+
id: uid(),
|
|
423
|
+
name: input.name,
|
|
424
|
+
description: input.description,
|
|
425
|
+
productId: input.productId,
|
|
426
|
+
productName: productById(input.productId)?.name,
|
|
427
|
+
yieldQuantity: input.yieldQuantity,
|
|
428
|
+
yieldUnitId: input.yieldUnitId,
|
|
429
|
+
preparationTimeMinutes: input.preparationTimeMinutes,
|
|
430
|
+
instructions: input.instructions,
|
|
431
|
+
isActive: true,
|
|
432
|
+
ingredientCount: input.ingredients.length,
|
|
433
|
+
tenantId: TENANT_ID,
|
|
434
|
+
createdAt: now(),
|
|
435
|
+
updatedAt: now()
|
|
436
|
+
};
|
|
437
|
+
for (const existing of recipes) {
|
|
438
|
+
if (existing.productId === input.productId && existing.isActive) {
|
|
439
|
+
existing.isActive = false;
|
|
440
|
+
existing.updatedAt = now();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
recipes.push(recipe);
|
|
444
|
+
for (const ing of input.ingredients) {
|
|
445
|
+
recipeIngredients.push({
|
|
446
|
+
id: uid(),
|
|
447
|
+
recipeId: recipe.id,
|
|
448
|
+
productId: ing.productId,
|
|
449
|
+
productName: productById(ing.productId)?.name,
|
|
450
|
+
quantity: ing.quantity,
|
|
451
|
+
unitId: ing.unitId,
|
|
452
|
+
displayOrder: ing.displayOrder ?? 0,
|
|
453
|
+
notes: ing.notes,
|
|
454
|
+
createdAt: now()
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return recipe;
|
|
458
|
+
},
|
|
459
|
+
async updateRecipe(id, patch) {
|
|
460
|
+
const recipe = recipes.find((r) => r.id === id);
|
|
461
|
+
if (!recipe) throw new Error(`Recipe ${id} not found`);
|
|
462
|
+
Object.assign(recipe, patch, { updatedAt: now() });
|
|
463
|
+
return withProduct(recipe);
|
|
464
|
+
},
|
|
465
|
+
async addRecipeIngredient(recipeId, input) {
|
|
466
|
+
const ingredient = {
|
|
467
|
+
id: uid(),
|
|
468
|
+
recipeId,
|
|
469
|
+
productId: input.productId,
|
|
470
|
+
productName: productById(input.productId)?.name,
|
|
471
|
+
quantity: input.quantity,
|
|
472
|
+
unitId: input.unitId,
|
|
473
|
+
displayOrder: input.displayOrder ?? recipeIngredients.filter((i) => i.recipeId === recipeId).length,
|
|
474
|
+
notes: input.notes,
|
|
475
|
+
createdAt: now()
|
|
476
|
+
};
|
|
477
|
+
recipeIngredients.push(ingredient);
|
|
478
|
+
const recipe = recipes.find((r) => r.id === recipeId);
|
|
479
|
+
if (recipe) recipe.ingredientCount = recipeIngredients.filter((i) => i.recipeId === recipeId).length;
|
|
480
|
+
return withCostBasis(ingredient);
|
|
481
|
+
},
|
|
482
|
+
async consumeRecipeForSource(input) {
|
|
483
|
+
assertConsumable(input);
|
|
484
|
+
const source = { sourceType: input.sourceType.trim(), sourceId: input.sourceId.trim() };
|
|
485
|
+
const idempotencyKey = consumptionKey(source.sourceType, source.sourceId);
|
|
486
|
+
const replay = consumptions.get(idempotencyKey);
|
|
487
|
+
if (replay) return replay.result;
|
|
488
|
+
const recipe = activeRecipeFor(recipes, input.productId);
|
|
489
|
+
if (!recipe) return skippedConsumption(source, "no_active_recipe");
|
|
490
|
+
const ingredients = recipeIngredients.filter((item) => item.recipeId === recipe.id).map((ingredient) => {
|
|
491
|
+
const product = productById(ingredient.productId);
|
|
492
|
+
if (!product) throw new Error(`Ingredient ${ingredient.productId} was not found`);
|
|
493
|
+
return { productId: product.id, quantity: ingredient.quantity, locationId: product.defaultLocationId };
|
|
494
|
+
});
|
|
495
|
+
const lines = explodeRecipe(recipe, ingredients, input.quantity);
|
|
496
|
+
for (const line of lines) {
|
|
497
|
+
const position = positions.find((p) => p.productId === line.productId && p.stockLocationId === line.locationId);
|
|
498
|
+
if ((position?.quantity ?? 0) < line.quantity) {
|
|
499
|
+
throw new Error(`Insufficient stock for ingredient ${productById(line.productId)?.name}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const movementIds = [];
|
|
503
|
+
for (const line of lines) {
|
|
504
|
+
const movement = await provider.createMovement({
|
|
505
|
+
productId: line.productId,
|
|
506
|
+
quantity: line.quantity,
|
|
507
|
+
movementType: "exit",
|
|
508
|
+
stockLocationId: line.locationId,
|
|
509
|
+
reason: `Recipe ${recipe.id}`,
|
|
510
|
+
notes: idempotencyKey
|
|
511
|
+
});
|
|
512
|
+
movementIds.push(movement.id);
|
|
513
|
+
}
|
|
514
|
+
const result = {
|
|
515
|
+
...source,
|
|
516
|
+
idempotencyKey,
|
|
517
|
+
status: "confirmed",
|
|
518
|
+
recipeId: recipe.id,
|
|
519
|
+
lineCount: lines.length
|
|
520
|
+
};
|
|
521
|
+
consumptions.set(idempotencyKey, { result, movementIds });
|
|
522
|
+
return result;
|
|
523
|
+
},
|
|
524
|
+
async reverseRecipeConsumption(input) {
|
|
525
|
+
assertReversible(input);
|
|
526
|
+
const source = { sourceType: input.sourceType.trim(), sourceId: input.sourceId.trim() };
|
|
527
|
+
const idempotencyKey = consumptionKey(source.sourceType, source.sourceId);
|
|
528
|
+
const consumption = consumptions.get(idempotencyKey);
|
|
529
|
+
if (!consumption) return skippedConsumption(source, "nothing_to_reverse");
|
|
530
|
+
if (consumption.result.status === "reversed") return consumption.result;
|
|
531
|
+
for (const movementId of consumption.movementIds) await provider.reverseMovement(movementId);
|
|
532
|
+
const result = { ...consumption.result, status: "reversed" };
|
|
533
|
+
consumptions.set(idempotencyKey, { result, movementIds: [] });
|
|
534
|
+
return result;
|
|
535
|
+
},
|
|
536
|
+
async getSummary() {
|
|
537
|
+
const cutoff = recentMovementCutoff(/* @__PURE__ */ new Date());
|
|
538
|
+
return summarizeStock(
|
|
539
|
+
products.filter((p) => p.isActive).map((p) => ({ quantity: p.currentQuantity, minQuantity: p.minQuantity, unitValue: p.costPrice })),
|
|
540
|
+
movements.filter((m) => m.movementDate >= cutoff).map((m) => m.movementType)
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
return provider;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export { createMockInventoryProvider };
|
|
548
|
+
//# sourceMappingURL=chunk-MCWG3PMP.js.map
|
|
549
|
+
//# sourceMappingURL=chunk-MCWG3PMP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data/mock.ts"],"names":[],"mappings":"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","file":"chunk-MCWG3PMP.js","sourcesContent":["import type {\n CreateProductInput, Product, ProductCategory, ProductQuery, Supplier,\n} from '../domain/product'\nimport {\n onHandDelta, reversedSourceDelta, sourcePositionChange,\n type CreateStockMovementInput, type MovementQuery, type StockMovement,\n} from '../domain/movement'\nimport {\n expiryWindow, hasHealth, isInExpiryWindow, recentMovementCutoff, summarizeStock, toPanelRow,\n type InventorySummary, type StockLocation, type StockPanelQuery, type StockPanelRow, type StockPosition,\n} from '../domain/stock'\nimport {\n countProgress, isCounted, isDivergent,\n type CloseStockCountResult, type OpenStockCountInput, type StockCountItem, type StockCountSession,\n} from '../domain/count'\nimport {\n activeRecipeFor, explodeRecipe,\n type CreateRecipeIngredientInput, type CreateRecipeInput, type Recipe, type RecipeGroup, type RecipeIngredient,\n} from '../domain/recipe'\nimport {\n assertConsumable, assertReversible, consumptionKey, skippedConsumption,\n type ConsumeRecipeForSourceInput, type InventoryConsumptionResult, type ReverseRecipeConsumptionInput,\n} from '../domain/consumption'\nimport type { UsageAuditBatchResult, UsageAuditEntry, UsageAuditQuery } from '../domain/usage-audit'\nimport { paginate, type PaginatedResult } from '../domain/query'\nimport { DEFAULT_STOCK_LOCATION } from '../domain/reference-data'\nimport type { InventoryDataProvider } from './provider'\n\nconst TENANT_ID = 'mock-tenant'\n\nlet nextId = 1\nfunction uid(): string { return String(nextId++) }\nfunction now(): string { return new Date().toISOString() }\nfunction today(): string { return new Date().toISOString().slice(0, 10) }\n\ntype Slot = Pick<CreateStockMovementInput, 'productId' | 'unitCost' | 'batchNumber' | 'expirationDate'>\n\nexport function createMockInventoryProvider(): InventoryDataProvider {\n const products: Product[] = []\n const movements: StockMovement[] = []\n const positions: StockPosition[] = []\n const locations: StockLocation[] = [\n { id: uid(), ...DEFAULT_STOCK_LOCATION, isActive: true, tenantId: TENANT_ID, createdAt: now(), updatedAt: now() },\n ]\n const suppliers: Supplier[] = [{ id: uid(), name: 'Main Supplier' }]\n const recipes: Recipe[] = []\n const recipeIngredients: RecipeIngredient[] = []\n const recipeGroups: RecipeGroup[] = []\n const countSessions: StockCountSession[] = []\n const countItems: StockCountItem[] = []\n const consumptions = new Map<string, { result: InventoryConsumptionResult; movementIds: string[] }>()\n // The movements that existed when each count opened: anything newer on the same slots makes the count stale.\n const countWatermarks = new Map<string, Set<string>>()\n\n const productById = (id: string) => products.find((p) => p.id === id)\n\n function withProduct(recipe: Recipe): Recipe {\n const product = productById(recipe.productId)\n return { ...recipe, productName: product?.name ?? recipe.productName, productSalePrice: product?.salePrice }\n }\n\n function withCostBasis(ingredient: RecipeIngredient): RecipeIngredient {\n const product = productById(ingredient.productId)\n if (!product) return ingredient\n return {\n ...ingredient,\n productName: product.name ?? ingredient.productName,\n costBasis: { cost: product.costPrice ?? 0, conversionFactor: product.conversionFactor, purchaseUnitId: product.purchaseUnitId },\n }\n }\n\n /** Same slot key as the ledger: product + location + batch + expiry. Returns how much on-hand changed. */\n function applyPositionDelta(slot: Slot, locationId: string, amount: number, mode: 'add' | 'set' = 'add'): number {\n const position = positions.find((p) =>\n p.productId === slot.productId\n && p.stockLocationId === locationId\n && (p.batchNumber ?? null) === (slot.batchNumber ?? null)\n && (p.expirationDate ?? null) === (slot.expirationDate ?? null))\n if (position) {\n const current = position.quantity\n position.quantity = mode === 'set' ? amount : current + amount\n position.unitCost = slot.unitCost ?? position.unitCost\n position.updatedAt = now()\n return position.quantity - current\n }\n positions.push({\n id: uid(),\n productId: slot.productId,\n quantity: amount,\n unitCost: slot.unitCost ?? 0,\n stockLocationId: locationId,\n stockLocationName: locations.find((l) => l.id === locationId)?.name,\n batchNumber: slot.batchNumber,\n expirationDate: slot.expirationDate,\n tenantId: TENANT_ID,\n createdAt: now(),\n })\n return amount\n }\n\n const provider: InventoryDataProvider = {\n async ensureReferenceData(): Promise<void> {},\n\n async getProducts(query: ProductQuery): Promise<PaginatedResult<Product>> {\n let results = [...products]\n if (query.productType) results = results.filter((p) => p.productType === query.productType)\n if (query.categoryId) results = results.filter((p) => p.categoryId === query.categoryId)\n if (query.isActive !== undefined) results = results.filter((p) => p.isActive === query.isActive)\n if (query.lowStockOnly) results = results.filter((p) => p.currentQuantity <= p.minQuantity)\n if (query.search) {\n const s = query.search.toLowerCase()\n results = results.filter((p) => p.name.toLowerCase().includes(s) || p.sku?.toLowerCase().includes(s) || p.barcode?.includes(s))\n }\n results.sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n return paginate(results, query.page, query.pageSize)\n },\n\n async getProductById(id: string): Promise<Product | null> {\n return productById(id) ?? null\n },\n\n async createProduct(input: CreateProductInput): Promise<Product> {\n const product: Product = {\n id: uid(),\n name: input.name,\n description: input.description,\n sku: input.sku,\n barcode: input.barcode,\n brand: input.brand,\n categoryId: input.categoryId,\n productType: input.productType,\n purpose: input.purpose,\n currentQuantity: 0,\n minQuantity: input.minQuantity ?? 0,\n maxQuantity: input.maxQuantity,\n costPrice: input.costPrice ?? 0,\n salePrice: input.salePrice,\n measurementUnitId: input.measurementUnitId,\n purchaseUnitId: input.purchaseUnitId,\n conversionFactor: input.conversionFactor ?? 1,\n isActive: true,\n supplierId: input.supplierId,\n defaultLocationId: input.defaultLocationId,\n imageUrl: input.imageUrl,\n metadata: input.metadata,\n tenantId: TENANT_ID,\n createdAt: now(),\n updatedAt: now(),\n }\n products.push(product)\n return product\n },\n\n async updateProduct(id: string, data: Partial<Product>): Promise<Product> {\n const product = productById(id)\n if (!product) throw new Error(`Product ${id} not found`)\n Object.assign(product, data, { updatedAt: now() })\n return product\n },\n\n async uploadProductImage(productId: string, file: File): Promise<string> {\n const product = productById(productId)\n if (!product) throw new Error(`Product ${productId} not found`)\n const url = typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function'\n ? URL.createObjectURL(file)\n : `mock://inventory-images/${TENANT_ID}/products/${productId}/${encodeURIComponent(file.name)}`\n product.imageUrl = url\n product.updatedAt = now()\n return url\n },\n\n async getMovements(query: MovementQuery): Promise<PaginatedResult<StockMovement>> {\n let results = [...movements]\n if (query.productId) results = results.filter((m) => m.productId === query.productId)\n if (query.movementType) {\n const types = Array.isArray(query.movementType) ? query.movementType : [query.movementType]\n results = results.filter((m) => types.includes(m.movementType))\n }\n if (query.stockLocationId) results = results.filter((m) => m.stockLocationId === query.stockLocationId)\n const range = query.dateRange\n if (range) results = results.filter((m) => m.movementDate >= range.from && m.movementDate <= range.to)\n if (query.search) {\n const s = query.search.toLowerCase()\n results = results.filter((m) => m.productName?.toLowerCase().includes(s) || m.notes?.toLowerCase().includes(s))\n }\n results.sort((a, b) => b.movementDate.localeCompare(a.movementDate))\n return paginate(results, query.page, query.pageSize)\n },\n\n async createMovement(input: CreateStockMovementInput): Promise<StockMovement> {\n const product = productById(input.productId)\n const location = input.stockLocationId ? locations.find((l) => l.id === input.stockLocationId) : undefined\n const unitCost = input.unitCost ?? product?.costPrice ?? 0\n\n const movement: StockMovement = {\n id: uid(),\n productId: input.productId,\n productName: product?.name,\n quantity: input.quantity,\n movementType: input.movementType,\n unitCost,\n totalCost: unitCost * input.quantity,\n stockLocationId: input.stockLocationId,\n stockLocationName: location?.name,\n destinationLocationId: input.destinationLocationId,\n supplierId: input.supplierId,\n supplierName: suppliers.find((s) => s.id === input.supplierId)?.name,\n documentNumber: input.documentNumber,\n reason: input.reason,\n notes: input.notes,\n batchNumber: input.batchNumber,\n expirationDate: input.expirationDate,\n movementDate: input.movementDate ?? today(),\n tenantId: TENANT_ID,\n createdAt: now(),\n }\n movements.push(movement)\n\n let stockDelta = onHandDelta(input.movementType, input.quantity)\n if (input.stockLocationId) {\n const slot = { ...input, unitCost }\n const change = sourcePositionChange(input.movementType, input.quantity)\n const applied = applyPositionDelta(slot, input.stockLocationId, change.amount, change.mode)\n if (input.movementType === 'adjustment') stockDelta = applied\n if (input.movementType === 'transfer' && input.destinationLocationId) {\n applyPositionDelta(slot, input.destinationLocationId, input.quantity)\n stockDelta = 0\n }\n } else if (input.movementType === 'adjustment') {\n stockDelta = input.quantity - (product?.currentQuantity ?? 0)\n }\n\n if (product) {\n product.currentQuantity += stockDelta\n product.updatedAt = now()\n }\n return movement\n },\n\n async reverseMovement(movementId: string): Promise<void> {\n const index = movements.findIndex((m) => m.id === movementId)\n if (index === -1) throw new Error(`Movement ${movementId} not found`)\n const movement = movements[index]\n\n const product = productById(movement.productId)\n if (product) {\n product.currentQuantity -= onHandDelta(movement.movementType, movement.quantity)\n product.updatedAt = now()\n }\n\n if (movement.stockLocationId) {\n const slot = {\n productId: movement.productId,\n unitCost: movement.unitCost,\n batchNumber: movement.batchNumber,\n expirationDate: movement.expirationDate,\n }\n applyPositionDelta(slot, movement.stockLocationId, reversedSourceDelta(movement.movementType, movement.quantity))\n if (movement.movementType === 'transfer' && movement.destinationLocationId) {\n applyPositionDelta(slot, movement.destinationLocationId, -movement.quantity)\n }\n }\n movements.splice(index, 1)\n },\n\n // The mock has no sold lines, so the honest usage queue is empty.\n async getUsageAudit(_query: UsageAuditQuery): Promise<PaginatedResult<UsageAuditEntry>> {\n return { data: [], total: 0 }\n },\n\n async correctUsage(): Promise<void> {\n throw new Error('Usage corrections need the ledger; the mock provider has none')\n },\n\n async correctUsageBatch(): Promise<UsageAuditBatchResult> {\n throw new Error('Usage corrections need the ledger; the mock provider has none')\n },\n\n async getPositions(productId: string): Promise<StockPosition[]> {\n return positions.filter((p) => p.productId === productId)\n },\n\n async getStockPanel(query: StockPanelQuery): Promise<StockPanelRow[]> {\n const window = expiryWindow(query.expiry, new Date())\n const term = query.productSearch?.trim().toLowerCase()\n const batch = query.batch?.toLowerCase()\n return positions\n .map((pos) => {\n const product = productById(pos.productId)\n return toPanelRow(pos, product, pos.stockLocationName ?? locations.find((l) => l.id === pos.stockLocationId)?.name)\n })\n .filter((row) => !query.stockLocationId || row.stockLocationId === query.stockLocationId)\n .filter((row) => !query.categoryId || row.categoryId === query.categoryId)\n .filter((row) => !term || row.productName.toLowerCase().includes(term))\n .filter((row) => !batch || (row.batchNumber ?? '').toLowerCase().includes(batch))\n .filter((row) => isInExpiryWindow(window, row.expirationDate))\n .filter((row) => hasHealth(row, query.status))\n .slice(0, query.limit ?? 500)\n },\n\n async getLocations(): Promise<StockLocation[]> {\n return locations.filter((l) => l.isActive)\n },\n\n async createLocation(data): Promise<StockLocation> {\n const location: StockLocation = {\n id: uid(),\n name: data.name,\n description: data.description,\n isActive: true,\n unitId: data.unitId,\n tenantId: TENANT_ID,\n createdAt: now(),\n updatedAt: now(),\n }\n locations.push(location)\n return location\n },\n\n async getSuppliers(): Promise<Supplier[]> {\n return suppliers\n },\n\n async getCategories(): Promise<ProductCategory[]> {\n const seen = new Map<string, ProductCategory>()\n for (const product of products) {\n if (!product.categoryId || seen.has(product.categoryId)) continue\n seen.set(product.categoryId, {\n id: product.categoryId,\n name: product.categoryName ?? product.categoryId,\n isActive: true,\n tenantId: TENANT_ID,\n createdAt: now(),\n updatedAt: now(),\n })\n }\n return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name))\n },\n\n async getCountSessions(): Promise<StockCountSession[]> {\n return [...countSessions]\n .sort((a, b) => b.openedAt.localeCompare(a.openedAt))\n .map((session) => ({ ...session, ...countProgress(countItems.filter((i) => i.sessionId === session.id)) }))\n },\n\n async getCountSession(id: string): Promise<StockCountSession | null> {\n return countSessions.find((s) => s.id === id) ?? null\n },\n\n async getCountItems(sessionId: string): Promise<StockCountItem[]> {\n return countItems\n .filter((i) => i.sessionId === sessionId)\n .sort((a, b) => (a.productName ?? '').localeCompare(b.productName ?? ''))\n },\n\n async openCountSession(input: OpenStockCountInput): Promise<StockCountSession> {\n const location = locations.find((l) => l.id === input.stockLocationId)\n if (!location) throw new Error(`Stock location ${input.stockLocationId} not found`)\n\n const session: StockCountSession = {\n id: uid(),\n reference: input.reference?.trim() || undefined,\n status: 'open',\n stockLocationId: input.stockLocationId,\n stockLocationName: location.name,\n categoryId: input.categoryId,\n blind: input.blind ?? true,\n notes: input.notes,\n openedAt: now(),\n tenantId: TENANT_ID,\n createdAt: now(),\n updatedAt: now(),\n }\n countSessions.push(session)\n countWatermarks.set(session.id, new Set(movements.map((m) => m.id)))\n\n for (const product of products) {\n if (!product.isActive || product.productType === 'asset') continue\n if (input.categoryId && product.categoryId !== input.categoryId) continue\n const slots = positions.filter((p) => p.productId === product.id && p.stockLocationId === input.stockLocationId)\n countItems.push({\n id: uid(),\n sessionId: session.id,\n productId: product.id,\n productName: product.name,\n productSku: product.sku,\n systemQuantity: slots.reduce((sum, p) => sum + (p.quantity ?? 0), 0),\n unitCost: slots[0]?.unitCost ?? product.costPrice ?? 0,\n tenantId: TENANT_ID,\n createdAt: now(),\n updatedAt: now(),\n })\n }\n return session\n },\n\n async saveCountEntry(itemId: string, quantity: number | null, notes?: string): Promise<StockCountItem> {\n const item = countItems.find((i) => i.id === itemId)\n if (!item) throw new Error(`Count item ${itemId} not found`)\n item.countedQuantity = quantity == null ? undefined : quantity\n item.countedAt = quantity == null ? undefined : now()\n if (notes !== undefined) item.notes = notes || undefined\n item.updatedAt = now()\n\n const session = countSessions.find((s) => s.id === item.sessionId)\n if (session?.status === 'open') session.status = 'counting'\n return item\n },\n\n async closeCountSession(sessionId: string, reason?: string): Promise<CloseStockCountResult> {\n const session = countSessions.find((s) => s.id === sessionId)\n if (!session) throw new Error(`Count session ${sessionId} not found`)\n if (session.status === 'closed') return { sessionId, adjustmentsCreated: 0, alreadyClosed: true }\n if (session.status === 'cancelled') throw new Error('This count was cancelled')\n\n const items = countItems.filter((item) => item.sessionId === sessionId)\n const uncounted = items.filter((item) => !isCounted(item))\n if (uncounted.length > 0) throw new Error(`Count every line before closing (${uncounted.length} uncounted)`)\n\n const watermark = countWatermarks.get(sessionId) ?? new Set<string>()\n const changedAfterOpening = movements.some((movement) =>\n !watermark.has(movement.id)\n && items.some((item) => item.productId === movement.productId)\n && (movement.stockLocationId === session.stockLocationId || movement.destinationLocationId === session.stockLocationId))\n if (changedAfterOpening) throw new Error('Stock changed after this count opened; review and recount')\n\n let emitted = 0\n for (const item of items) {\n if (!isDivergent(item) || item.movementId) continue\n const counted = item.countedQuantity as number\n const delta = counted - item.systemQuantity\n const hasBatchStock = positions.some((position) =>\n position.productId === item.productId\n && position.stockLocationId === session.stockLocationId\n && position.quantity !== 0\n && Boolean(position.batchNumber || position.expirationDate))\n if (hasBatchStock) throw new Error(`Product ${item.productId} has batch stock; use a batch-aware count`)\n\n const movement: StockMovement = {\n id: uid(),\n productId: item.productId,\n productName: item.productName,\n quantity: delta,\n movementType: 'adjustment',\n unitCost: item.unitCost,\n totalCost: item.unitCost * Math.abs(delta),\n stockLocationId: session.stockLocationId,\n stockLocationName: session.stockLocationName,\n reason: reason || 'Stock count',\n movementDate: today(),\n metadata: {\n countSessionId: sessionId,\n countItemId: item.id,\n systemQuantity: item.systemQuantity,\n countedQuantity: counted,\n variance: delta,\n },\n tenantId: TENANT_ID,\n createdAt: now(),\n }\n movements.push(movement)\n item.movementId = movement.id\n item.updatedAt = now()\n\n applyPositionDelta({ productId: item.productId, unitCost: item.unitCost }, session.stockLocationId, delta)\n const product = productById(item.productId)\n if (product) {\n product.currentQuantity += delta\n product.updatedAt = now()\n }\n emitted += 1\n }\n\n session.status = 'closed'\n session.closedAt = now()\n session.updatedAt = now()\n countWatermarks.delete(sessionId)\n return { sessionId, adjustmentsCreated: emitted, alreadyClosed: false }\n },\n\n async cancelCountSession(sessionId: string): Promise<StockCountSession> {\n const session = countSessions.find((s) => s.id === sessionId)\n if (!session) throw new Error(`Count session ${sessionId} not found`)\n if (session.status === 'closed') throw new Error('A closed count cannot be cancelled')\n session.status = 'cancelled'\n session.updatedAt = now()\n countWatermarks.delete(sessionId)\n return session\n },\n\n async getRecipes(): Promise<Recipe[]> {\n return recipes.filter((r) => r.isActive).map(withProduct)\n },\n\n async getRecipeById(id: string): Promise<Recipe | null> {\n const recipe = recipes.find((r) => r.id === id)\n return recipe ? withProduct(recipe) : null\n },\n\n async getRecipeIngredients(recipeId: string): Promise<RecipeIngredient[]> {\n return recipeIngredients\n .filter((ri) => ri.recipeId === recipeId)\n .sort((a, b) => a.displayOrder - b.displayOrder)\n .map(withCostBasis)\n },\n\n async getAllRecipeIngredients(): Promise<RecipeIngredient[]> {\n return recipeIngredients.map(withCostBasis)\n },\n\n async getRecipeGroups(recipeId: string): Promise<RecipeGroup[]> {\n return recipeGroups.filter((g) => g.recipeId === recipeId).sort((a, b) => a.displayOrder - b.displayOrder)\n },\n\n async createRecipe(input: CreateRecipeInput): Promise<Recipe> {\n const recipe: Recipe = {\n id: uid(),\n name: input.name,\n description: input.description,\n productId: input.productId,\n productName: productById(input.productId)?.name,\n yieldQuantity: input.yieldQuantity,\n yieldUnitId: input.yieldUnitId,\n preparationTimeMinutes: input.preparationTimeMinutes,\n instructions: input.instructions,\n isActive: true,\n ingredientCount: input.ingredients.length,\n tenantId: TENANT_ID,\n createdAt: now(),\n updatedAt: now(),\n }\n // One active ficha per product.\n for (const existing of recipes) {\n if (existing.productId === input.productId && existing.isActive) {\n existing.isActive = false\n existing.updatedAt = now()\n }\n }\n recipes.push(recipe)\n\n for (const ing of input.ingredients) {\n recipeIngredients.push({\n id: uid(),\n recipeId: recipe.id,\n productId: ing.productId,\n productName: productById(ing.productId)?.name,\n quantity: ing.quantity,\n unitId: ing.unitId,\n displayOrder: ing.displayOrder ?? 0,\n notes: ing.notes,\n createdAt: now(),\n })\n }\n return recipe\n },\n\n async updateRecipe(id: string, patch: Partial<Recipe>): Promise<Recipe> {\n const recipe = recipes.find((r) => r.id === id)\n if (!recipe) throw new Error(`Recipe ${id} not found`)\n Object.assign(recipe, patch, { updatedAt: now() })\n return withProduct(recipe)\n },\n\n async addRecipeIngredient(recipeId: string, input: CreateRecipeIngredientInput): Promise<RecipeIngredient> {\n const ingredient: RecipeIngredient = {\n id: uid(),\n recipeId,\n productId: input.productId,\n productName: productById(input.productId)?.name,\n quantity: input.quantity,\n unitId: input.unitId,\n displayOrder: input.displayOrder ?? recipeIngredients.filter((i) => i.recipeId === recipeId).length,\n notes: input.notes,\n createdAt: now(),\n }\n recipeIngredients.push(ingredient)\n const recipe = recipes.find((r) => r.id === recipeId)\n if (recipe) recipe.ingredientCount = recipeIngredients.filter((i) => i.recipeId === recipeId).length\n return withCostBasis(ingredient)\n },\n\n async consumeRecipeForSource(input: ConsumeRecipeForSourceInput): Promise<InventoryConsumptionResult> {\n assertConsumable(input)\n const source = { sourceType: input.sourceType.trim(), sourceId: input.sourceId.trim() }\n const idempotencyKey = consumptionKey(source.sourceType, source.sourceId)\n const replay = consumptions.get(idempotencyKey)\n if (replay) return replay.result\n\n const recipe = activeRecipeFor(recipes, input.productId)\n if (!recipe) return skippedConsumption(source, 'no_active_recipe')\n\n const ingredients = recipeIngredients.filter((item) => item.recipeId === recipe.id).map((ingredient) => {\n const product = productById(ingredient.productId)\n if (!product) throw new Error(`Ingredient ${ingredient.productId} was not found`)\n return { productId: product.id, quantity: ingredient.quantity, locationId: product.defaultLocationId }\n })\n const lines = explodeRecipe(recipe, ingredients, input.quantity)\n\n for (const line of lines) {\n const position = positions.find((p) => p.productId === line.productId && p.stockLocationId === line.locationId)\n if ((position?.quantity ?? 0) < line.quantity) {\n throw new Error(`Insufficient stock for ingredient ${productById(line.productId)?.name}`)\n }\n }\n\n const movementIds: string[] = []\n for (const line of lines) {\n const movement = await provider.createMovement({\n productId: line.productId,\n quantity: line.quantity,\n movementType: 'exit',\n stockLocationId: line.locationId,\n reason: `Recipe ${recipe.id}`,\n notes: idempotencyKey,\n })\n movementIds.push(movement.id)\n }\n const result: InventoryConsumptionResult = {\n ...source,\n idempotencyKey,\n status: 'confirmed',\n recipeId: recipe.id,\n lineCount: lines.length,\n }\n consumptions.set(idempotencyKey, { result, movementIds })\n return result\n },\n\n async reverseRecipeConsumption(input: ReverseRecipeConsumptionInput): Promise<InventoryConsumptionResult> {\n assertReversible(input)\n const source = { sourceType: input.sourceType.trim(), sourceId: input.sourceId.trim() }\n const idempotencyKey = consumptionKey(source.sourceType, source.sourceId)\n const consumption = consumptions.get(idempotencyKey)\n if (!consumption) return skippedConsumption(source, 'nothing_to_reverse')\n if (consumption.result.status === 'reversed') return consumption.result\n\n for (const movementId of consumption.movementIds) await provider.reverseMovement(movementId)\n const result: InventoryConsumptionResult = { ...consumption.result, status: 'reversed' }\n consumptions.set(idempotencyKey, { result, movementIds: [] })\n return result\n },\n\n async getSummary(): Promise<InventorySummary> {\n const cutoff = recentMovementCutoff(new Date())\n return summarizeStock(\n products.filter((p) => p.isActive).map((p) => ({ quantity: p.currentQuantity, minQuantity: p.minQuantity, unitValue: p.costPrice })),\n movements.filter((m) => m.movementDate >= cutoff).map((m) => m.movementType),\n )\n },\n }\n\n return provider\n}\n"]}
|