@naisys/erp 3.0.0-beta.23 → 3.0.0-beta.25
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/client-dist/assets/{index-DycEn-R_.js → index-ey6Ixpe7.js} +245 -213
- package/client-dist/index.html +1 -1
- package/dist/auth-middleware.js +1 -0
- package/dist/error-handler.js +3 -0
- package/dist/routes/inventory.js +31 -8
- package/dist/routes/item-fields.js +18 -16
- package/dist/routes/item-instances.js +36 -18
- package/dist/routes/items.js +31 -29
- package/dist/routes/labor-tickets.js +1 -2
- package/dist/routes/operation-run-transitions.js +9 -2
- package/dist/routes/operation-runs.js +5 -1
- package/dist/routes/order-run-transitions.js +8 -2
- package/dist/routes/orders.js +11 -7
- package/dist/routes/users.js +1 -0
- package/dist/routes/work-centers.js +25 -25
- package/dist/services/field-value-service.js +26 -2
- package/dist/services/order-run-service.js +64 -17
- package/dist/services/order-service.js +9 -0
- package/npm-shrinkwrap.json +28 -28
- package/package.json +6 -6
|
@@ -113,6 +113,30 @@ export function checkFieldValueShape(label, type, isArray, value) {
|
|
|
113
113
|
}
|
|
114
114
|
return null;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Validate a list of field definitions across one or more set indexes, given a
|
|
118
|
+
* way to look up the current value for each (fieldId, setIndex) pair. Returns
|
|
119
|
+
* one entry per invalid cell; callers format as appropriate. Iteration order
|
|
120
|
+
* is ascending setIndex, then the order of `fieldDefs` as given.
|
|
121
|
+
*/
|
|
122
|
+
export function validateFieldSet(fieldDefs, setIndexes, getValue) {
|
|
123
|
+
const failures = [];
|
|
124
|
+
for (const si of [...setIndexes].sort((a, b) => a - b)) {
|
|
125
|
+
for (const def of fieldDefs) {
|
|
126
|
+
const value = getValue(def.id, si);
|
|
127
|
+
const result = validateFieldValue(def.type, def.isArray, def.required, value);
|
|
128
|
+
if (!result.valid) {
|
|
129
|
+
failures.push({
|
|
130
|
+
fieldId: def.id,
|
|
131
|
+
label: def.label,
|
|
132
|
+
setIndex: si,
|
|
133
|
+
error: result.error,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return failures;
|
|
139
|
+
}
|
|
116
140
|
export function validateFieldValue(type, isArray, required, value) {
|
|
117
141
|
const shapeErr = checkFieldValueShape("field", type, isArray, value);
|
|
118
142
|
if (shapeErr)
|
|
@@ -223,9 +247,9 @@ export async function clearAttachmentFieldValue(fieldRecordId, fieldId, setIndex
|
|
|
223
247
|
await upsertFieldValue(fieldRecordId, fieldId, setIndex, empty, userId);
|
|
224
248
|
}
|
|
225
249
|
// --- Mutations ---
|
|
226
|
-
export async function upsertFieldValue(fieldRecordId, fieldId, setIndex, value, userId) {
|
|
250
|
+
export async function upsertFieldValue(fieldRecordId, fieldId, setIndex, value, userId, tx = erpDb) {
|
|
227
251
|
const dbValue = serializeFieldValue(value);
|
|
228
|
-
await
|
|
252
|
+
await tx.fieldValue.upsert({
|
|
229
253
|
where: {
|
|
230
254
|
fieldRecordId_fieldId_setIndex: { fieldRecordId, fieldId, setIndex },
|
|
231
255
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OperationRunStatus as OperationRunStatusValues, OrderRunStatus as OrderRunStatusValues, } from "@naisys/erp-shared";
|
|
2
2
|
import { writeAuditEntry } from "../audit.js";
|
|
3
3
|
import erpDb from "../erpDb.js";
|
|
4
|
+
import { deserializeFieldValue, upsertFieldValue, validateFieldSet, } from "./field-value-service.js";
|
|
4
5
|
// --- Prisma include & result type ---
|
|
5
6
|
export const includeRev = {
|
|
6
7
|
orderRev: { select: { revNo: true } },
|
|
@@ -259,7 +260,7 @@ async function autoGenerateInstanceKey(erpTx, itemId) {
|
|
|
259
260
|
}
|
|
260
261
|
export async function completeOrderRun(orderRunId, orderId, data, userId) {
|
|
261
262
|
return erpDb.$transaction(async (erpTx) => {
|
|
262
|
-
// Load the order with its item
|
|
263
|
+
// Load the order with its item and full item field definitions.
|
|
263
264
|
const order = await erpTx.order.findUniqueOrThrow({
|
|
264
265
|
where: { id: orderId },
|
|
265
266
|
select: {
|
|
@@ -270,7 +271,15 @@ export async function completeOrderRun(orderRunId, orderId, data, userId) {
|
|
|
270
271
|
fieldSet: {
|
|
271
272
|
select: {
|
|
272
273
|
fields: {
|
|
273
|
-
select: {
|
|
274
|
+
select: {
|
|
275
|
+
id: true,
|
|
276
|
+
seqNo: true,
|
|
277
|
+
label: true,
|
|
278
|
+
type: true,
|
|
279
|
+
isArray: true,
|
|
280
|
+
required: true,
|
|
281
|
+
},
|
|
282
|
+
orderBy: { seqNo: "asc" },
|
|
274
283
|
},
|
|
275
284
|
},
|
|
276
285
|
},
|
|
@@ -279,14 +288,60 @@ export async function completeOrderRun(orderRunId, orderId, data, userId) {
|
|
|
279
288
|
},
|
|
280
289
|
});
|
|
281
290
|
if (!order.item) {
|
|
282
|
-
return {
|
|
291
|
+
return {
|
|
292
|
+
error: "Order has no item assigned — cannot complete",
|
|
293
|
+
status: 422,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const itemFields = order.item.fieldSet?.fields ?? [];
|
|
297
|
+
const fieldsBySeqNo = new Map(itemFields.map((f) => [f.seqNo, f]));
|
|
298
|
+
const fieldsById = new Map(itemFields.map((f) => [f.id, f]));
|
|
299
|
+
// Validate caller-supplied fieldSeqNos exist on the item.
|
|
300
|
+
const callerValues = data.fieldValues ?? [];
|
|
301
|
+
for (const fv of callerValues) {
|
|
302
|
+
if (!fieldsBySeqNo.has(fv.fieldSeqNo)) {
|
|
303
|
+
return {
|
|
304
|
+
error: `Unknown item field seqNo ${fv.fieldSeqNo} — item has no field with that sequence number`,
|
|
305
|
+
status: 400,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const resolved = new Map();
|
|
310
|
+
const keyOf = (fieldId, setIndex) => `${fieldId}:${setIndex}`;
|
|
311
|
+
for (const fv of callerValues) {
|
|
312
|
+
const def = fieldsBySeqNo.get(fv.fieldSeqNo);
|
|
313
|
+
const setIndex = fv.setIndex ?? 0;
|
|
314
|
+
resolved.set(keyOf(def.id, setIndex), {
|
|
315
|
+
fieldId: def.id,
|
|
316
|
+
value: fv.value,
|
|
317
|
+
setIndex,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// Validate all item fields against caller-supplied values at setIndex 0.
|
|
321
|
+
// `fieldValues[]` on CompleteOrderRun is flat (no multi-set support), so
|
|
322
|
+
// we only validate set 0. Flags both missing-required and type-invalid.
|
|
323
|
+
const failures = validateFieldSet(itemFields, [0], (fieldId, setIndex) => {
|
|
324
|
+
const def = fieldsById.get(fieldId);
|
|
325
|
+
const r = resolved.get(keyOf(fieldId, setIndex));
|
|
326
|
+
if (r)
|
|
327
|
+
return deserializeFieldValue(r.value, def.isArray);
|
|
328
|
+
return def.isArray ? [] : "";
|
|
329
|
+
});
|
|
330
|
+
if (failures.length > 0) {
|
|
331
|
+
return {
|
|
332
|
+
error: `Cannot complete order run: ${failures
|
|
333
|
+
.map((f) => `${f.label} — ${f.error}`)
|
|
334
|
+
.join("; ")}. Provide values via fieldValues[] using fieldSeqNo.`,
|
|
335
|
+
status: 400,
|
|
336
|
+
missingFields: failures.map((f) => f.label),
|
|
337
|
+
};
|
|
283
338
|
}
|
|
284
339
|
// Determine instance key
|
|
285
340
|
let instanceKey = data.instanceKey;
|
|
286
341
|
if (!instanceKey) {
|
|
287
342
|
const result = await autoGenerateInstanceKey(erpTx, order.item.id);
|
|
288
343
|
if ("error" in result)
|
|
289
|
-
return result;
|
|
344
|
+
return { error: result.error, status: 422 };
|
|
290
345
|
instanceKey = result.key;
|
|
291
346
|
}
|
|
292
347
|
// Check for duplicate key
|
|
@@ -296,6 +351,7 @@ export async function completeOrderRun(orderRunId, orderId, data, userId) {
|
|
|
296
351
|
if (existing) {
|
|
297
352
|
return {
|
|
298
353
|
error: `Instance key "${instanceKey}" already exists for this item`,
|
|
354
|
+
status: 422,
|
|
299
355
|
};
|
|
300
356
|
}
|
|
301
357
|
// Create the item instance
|
|
@@ -309,8 +365,8 @@ export async function completeOrderRun(orderRunId, orderId, data, userId) {
|
|
|
309
365
|
updatedById: userId,
|
|
310
366
|
},
|
|
311
367
|
});
|
|
312
|
-
// Create field record and field values if
|
|
313
|
-
if (order.item.fieldSetId &&
|
|
368
|
+
// Create field record and field values if we have any to write.
|
|
369
|
+
if (order.item.fieldSetId && resolved.size > 0) {
|
|
314
370
|
const fieldRecord = await erpTx.fieldRecord.create({
|
|
315
371
|
data: {
|
|
316
372
|
fieldSetId: order.item.fieldSetId,
|
|
@@ -321,17 +377,8 @@ export async function completeOrderRun(orderRunId, orderId, data, userId) {
|
|
|
321
377
|
where: { id: instance.id },
|
|
322
378
|
data: { fieldRecordId: fieldRecord.id },
|
|
323
379
|
});
|
|
324
|
-
for (const fv of
|
|
325
|
-
await
|
|
326
|
-
data: {
|
|
327
|
-
fieldRecordId: fieldRecord.id,
|
|
328
|
-
fieldId: fv.fieldId,
|
|
329
|
-
setIndex: fv.setIndex ?? 0,
|
|
330
|
-
value: fv.value,
|
|
331
|
-
createdById: userId,
|
|
332
|
-
updatedById: userId,
|
|
333
|
-
},
|
|
334
|
-
});
|
|
380
|
+
for (const fv of resolved.values()) {
|
|
381
|
+
await upsertFieldValue(fieldRecord.id, fv.fieldId, fv.setIndex, fv.value, userId, erpTx);
|
|
335
382
|
}
|
|
336
383
|
}
|
|
337
384
|
// Sum operation run costs and transition run to closed
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RevisionStatus } from "@naisys/erp-shared";
|
|
1
2
|
import erpDb from "../erpDb.js";
|
|
2
3
|
import { includeUsers } from "../route-helpers.js";
|
|
3
4
|
// --- Prisma include & result type ---
|
|
@@ -31,6 +32,14 @@ export async function checkHasRevisions(orderId) {
|
|
|
31
32
|
});
|
|
32
33
|
return revisionCount > 0;
|
|
33
34
|
}
|
|
35
|
+
// --- Derived fields ---
|
|
36
|
+
export async function getLatestApprovedRevNo(orderId) {
|
|
37
|
+
const result = await erpDb.orderRevision.aggregate({
|
|
38
|
+
where: { orderId, status: RevisionStatus.approved },
|
|
39
|
+
_max: { revNo: true },
|
|
40
|
+
});
|
|
41
|
+
return result._max.revNo ?? null;
|
|
42
|
+
}
|
|
34
43
|
// --- Mutations ---
|
|
35
44
|
export async function resolveItemKey(itemKey) {
|
|
36
45
|
if (!itemKey)
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naisys/erp",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.25",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@naisys/erp",
|
|
9
|
-
"version": "3.0.0-beta.
|
|
9
|
+
"version": "3.0.0-beta.25",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@fastify/cookie": "^11.0.2",
|
|
12
12
|
"@fastify/cors": "^11.2.0",
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
"@fastify/rate-limit": "^10.3.0",
|
|
15
15
|
"@fastify/static": "^9.0.0",
|
|
16
16
|
"@fastify/swagger": "^9.7.0",
|
|
17
|
-
"@naisys/common": "3.0.0-beta.
|
|
18
|
-
"@naisys/common-node": "3.0.0-beta.
|
|
19
|
-
"@naisys/erp-shared": "3.0.0-beta.
|
|
20
|
-
"@naisys/hub-database": "3.0.0-beta.
|
|
21
|
-
"@naisys/supervisor-database": "3.0.0-beta.
|
|
17
|
+
"@naisys/common": "3.0.0-beta.25",
|
|
18
|
+
"@naisys/common-node": "3.0.0-beta.25",
|
|
19
|
+
"@naisys/erp-shared": "3.0.0-beta.25",
|
|
20
|
+
"@naisys/hub-database": "3.0.0-beta.25",
|
|
21
|
+
"@naisys/supervisor-database": "3.0.0-beta.25",
|
|
22
22
|
"@prisma/adapter-better-sqlite3": "^7.5.0",
|
|
23
23
|
"@prisma/client": "^7.5.0",
|
|
24
24
|
"@scalar/fastify-api-reference": "^1.48.7",
|
|
@@ -444,41 +444,41 @@
|
|
|
444
444
|
}
|
|
445
445
|
},
|
|
446
446
|
"node_modules/@naisys/common": {
|
|
447
|
-
"version": "3.0.0-beta.
|
|
448
|
-
"resolved": "https://registry.npmjs.org/@naisys/common/-/common-3.0.0-beta.
|
|
449
|
-
"integrity": "sha512-
|
|
447
|
+
"version": "3.0.0-beta.25",
|
|
448
|
+
"resolved": "https://registry.npmjs.org/@naisys/common/-/common-3.0.0-beta.25.tgz",
|
|
449
|
+
"integrity": "sha512-v21DvH/KlxKMxoTwwlInMyLgqVcSAYNYsMF2z7FXYiRL9fcqX15+S7oFZ2PsB+SR6MXlhN7AachF7BsYi7ikCw==",
|
|
450
450
|
"dependencies": {
|
|
451
451
|
"semver": "^7.7.4",
|
|
452
452
|
"zod": "^4.3.6"
|
|
453
453
|
}
|
|
454
454
|
},
|
|
455
455
|
"node_modules/@naisys/common-node": {
|
|
456
|
-
"version": "3.0.0-beta.
|
|
457
|
-
"resolved": "https://registry.npmjs.org/@naisys/common-node/-/common-node-3.0.0-beta.
|
|
458
|
-
"integrity": "sha512-
|
|
456
|
+
"version": "3.0.0-beta.25",
|
|
457
|
+
"resolved": "https://registry.npmjs.org/@naisys/common-node/-/common-node-3.0.0-beta.25.tgz",
|
|
458
|
+
"integrity": "sha512-WMc8gRNBEB3oh14zRjDNezqldLyPz2aXe7KHVZVEkIpShiYY8pMBva1up/1q40NmlWw/m/rbHRa6yrDUuH3qwg==",
|
|
459
459
|
"dependencies": {
|
|
460
|
-
"@naisys/common": "3.0.0-beta.
|
|
460
|
+
"@naisys/common": "3.0.0-beta.25",
|
|
461
461
|
"better-sqlite3": "^12.6.2",
|
|
462
462
|
"js-yaml": "^4.1.1",
|
|
463
463
|
"pino": "^10.3.1"
|
|
464
464
|
}
|
|
465
465
|
},
|
|
466
466
|
"node_modules/@naisys/erp-shared": {
|
|
467
|
-
"version": "3.0.0-beta.
|
|
468
|
-
"resolved": "https://registry.npmjs.org/@naisys/erp-shared/-/erp-shared-3.0.0-beta.
|
|
469
|
-
"integrity": "sha512-
|
|
467
|
+
"version": "3.0.0-beta.25",
|
|
468
|
+
"resolved": "https://registry.npmjs.org/@naisys/erp-shared/-/erp-shared-3.0.0-beta.25.tgz",
|
|
469
|
+
"integrity": "sha512-x4RgdksPu4wRKoYKyPeMnSO5nBhk+2G4MY5uy7iYU+OzH6Ozg5MofXTFX62YzKRrYcpPie2B8c37Ihkq59dtfA==",
|
|
470
470
|
"dependencies": {
|
|
471
|
-
"@naisys/common": "3.0.0-beta.
|
|
471
|
+
"@naisys/common": "3.0.0-beta.25",
|
|
472
472
|
"zod": "^4.3.6"
|
|
473
473
|
}
|
|
474
474
|
},
|
|
475
475
|
"node_modules/@naisys/hub-database": {
|
|
476
|
-
"version": "3.0.0-beta.
|
|
477
|
-
"resolved": "https://registry.npmjs.org/@naisys/hub-database/-/hub-database-3.0.0-beta.
|
|
478
|
-
"integrity": "sha512-
|
|
476
|
+
"version": "3.0.0-beta.25",
|
|
477
|
+
"resolved": "https://registry.npmjs.org/@naisys/hub-database/-/hub-database-3.0.0-beta.25.tgz",
|
|
478
|
+
"integrity": "sha512-BtlvEaCwULaZlsgAZN6+1kKOxc0yHOyLV7CQmobrdBTuW8TTL4r71WoIL1wIVZFfu4Dz1SgCO5aSPh4LqRJkxg==",
|
|
479
479
|
"dependencies": {
|
|
480
|
-
"@naisys/common": "3.0.0-beta.
|
|
481
|
-
"@naisys/common-node": "3.0.0-beta.
|
|
480
|
+
"@naisys/common": "3.0.0-beta.25",
|
|
481
|
+
"@naisys/common-node": "3.0.0-beta.25",
|
|
482
482
|
"@prisma/adapter-better-sqlite3": "^7.5.0",
|
|
483
483
|
"@prisma/client": "^7.5.0",
|
|
484
484
|
"better-sqlite3": "^12.6.2",
|
|
@@ -486,12 +486,12 @@
|
|
|
486
486
|
}
|
|
487
487
|
},
|
|
488
488
|
"node_modules/@naisys/supervisor-database": {
|
|
489
|
-
"version": "3.0.0-beta.
|
|
490
|
-
"resolved": "https://registry.npmjs.org/@naisys/supervisor-database/-/supervisor-database-3.0.0-beta.
|
|
491
|
-
"integrity": "sha512-
|
|
489
|
+
"version": "3.0.0-beta.25",
|
|
490
|
+
"resolved": "https://registry.npmjs.org/@naisys/supervisor-database/-/supervisor-database-3.0.0-beta.25.tgz",
|
|
491
|
+
"integrity": "sha512-v1rB1fcXgpbaffp4KzAlD1gNAZX9MDIbv4N96J/Q0Zv/Oia1Jr55/yfXscCRtqKhp0NhBxED00irYiKKC7psoQ==",
|
|
492
492
|
"dependencies": {
|
|
493
|
-
"@naisys/common": "3.0.0-beta.
|
|
494
|
-
"@naisys/common-node": "3.0.0-beta.
|
|
493
|
+
"@naisys/common": "3.0.0-beta.25",
|
|
494
|
+
"@naisys/common-node": "3.0.0-beta.25",
|
|
495
495
|
"@prisma/adapter-better-sqlite3": "^7.5.0",
|
|
496
496
|
"@prisma/client": "^7.5.0",
|
|
497
497
|
"bcryptjs": "^3.0.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naisys/erp",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.25",
|
|
4
4
|
"description": "NAISYS ERP - Web UI for AI-driven order and work management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/erpServer.js",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"@fastify/rate-limit": "^10.3.0",
|
|
47
47
|
"@fastify/static": "^9.0.0",
|
|
48
48
|
"@fastify/swagger": "^9.7.0",
|
|
49
|
-
"@naisys/erp-shared": "3.0.0-beta.
|
|
50
|
-
"@naisys/common": "3.0.0-beta.
|
|
51
|
-
"@naisys/common-node": "3.0.0-beta.
|
|
52
|
-
"@naisys/hub-database": "3.0.0-beta.
|
|
53
|
-
"@naisys/supervisor-database": "3.0.0-beta.
|
|
49
|
+
"@naisys/erp-shared": "3.0.0-beta.25",
|
|
50
|
+
"@naisys/common": "3.0.0-beta.25",
|
|
51
|
+
"@naisys/common-node": "3.0.0-beta.25",
|
|
52
|
+
"@naisys/hub-database": "3.0.0-beta.25",
|
|
53
|
+
"@naisys/supervisor-database": "3.0.0-beta.25",
|
|
54
54
|
"@prisma/adapter-better-sqlite3": "^7.5.0",
|
|
55
55
|
"@prisma/client": "^7.5.0",
|
|
56
56
|
"@scalar/fastify-api-reference": "^1.48.7",
|