@naisys/erp 3.0.0-beta.3
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.
Potentially problematic release.
This version of @naisys/erp might be problematic. Click here for more details.
- package/bin/naisys-erp +2 -0
- package/client-dist/android-chrome-192x192.png +0 -0
- package/client-dist/android-chrome-512x512.png +0 -0
- package/client-dist/apple-touch-icon.png +0 -0
- package/client-dist/assets/index-45dVo30p.css +1 -0
- package/client-dist/assets/index-Dffms7F_.js +168 -0
- package/client-dist/assets/naisys-logo-CzoPnn5I.webp +0 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +42 -0
- package/client-dist/site.webmanifest +22 -0
- package/dist/api-reference.d.ts +10 -0
- package/dist/api-reference.js +101 -0
- package/dist/audit.d.ts +5 -0
- package/dist/audit.js +14 -0
- package/dist/auth-middleware.d.ts +18 -0
- package/dist/auth-middleware.js +203 -0
- package/dist/dbConfig.d.ts +5 -0
- package/dist/dbConfig.js +10 -0
- package/dist/erpDb.d.ts +10 -0
- package/dist/erpDb.js +34 -0
- package/dist/erpServer.d.ts +10 -0
- package/dist/erpServer.js +321 -0
- package/dist/error-handler.d.ts +7 -0
- package/dist/error-handler.js +17 -0
- package/dist/generated/prisma/client.d.ts +154 -0
- package/dist/generated/prisma/client.js +35 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +637 -0
- package/dist/generated/prisma/commonInputTypes.js +11 -0
- package/dist/generated/prisma/enums.d.ts +59 -0
- package/dist/generated/prisma/enums.js +60 -0
- package/dist/generated/prisma/internal/class.d.ts +406 -0
- package/dist/generated/prisma/internal/class.js +50 -0
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +2722 -0
- package/dist/generated/prisma/internal/prismaNamespace.js +366 -0
- package/dist/generated/prisma/models/Attachment.d.ts +1455 -0
- package/dist/generated/prisma/models/Attachment.js +2 -0
- package/dist/generated/prisma/models/AuditLog.d.ts +1359 -0
- package/dist/generated/prisma/models/AuditLog.js +2 -0
- package/dist/generated/prisma/models/Field.d.ts +1880 -0
- package/dist/generated/prisma/models/Field.js +2 -0
- package/dist/generated/prisma/models/FieldAttachment.d.ts +1245 -0
- package/dist/generated/prisma/models/FieldAttachment.js +2 -0
- package/dist/generated/prisma/models/FieldRecord.d.ts +1625 -0
- package/dist/generated/prisma/models/FieldRecord.js +2 -0
- package/dist/generated/prisma/models/FieldSet.d.ts +1577 -0
- package/dist/generated/prisma/models/FieldSet.js +2 -0
- package/dist/generated/prisma/models/FieldValue.d.ts +1908 -0
- package/dist/generated/prisma/models/FieldValue.js +2 -0
- package/dist/generated/prisma/models/Item.d.ts +1858 -0
- package/dist/generated/prisma/models/Item.js +2 -0
- package/dist/generated/prisma/models/ItemInstance.d.ts +1987 -0
- package/dist/generated/prisma/models/ItemInstance.js +2 -0
- package/dist/generated/prisma/models/LaborTicket.d.ts +1867 -0
- package/dist/generated/prisma/models/LaborTicket.js +2 -0
- package/dist/generated/prisma/models/Operation.d.ts +2578 -0
- package/dist/generated/prisma/models/Operation.js +2 -0
- package/dist/generated/prisma/models/OperationDependency.d.ts +1434 -0
- package/dist/generated/prisma/models/OperationDependency.js +2 -0
- package/dist/generated/prisma/models/OperationFieldRef.d.ts +1539 -0
- package/dist/generated/prisma/models/OperationFieldRef.js +2 -0
- package/dist/generated/prisma/models/OperationRun.d.ts +2563 -0
- package/dist/generated/prisma/models/OperationRun.js +2 -0
- package/dist/generated/prisma/models/OperationRunComment.d.ts +1366 -0
- package/dist/generated/prisma/models/OperationRunComment.js +2 -0
- package/dist/generated/prisma/models/Order.d.ts +1931 -0
- package/dist/generated/prisma/models/Order.js +2 -0
- package/dist/generated/prisma/models/OrderRevision.d.ts +1962 -0
- package/dist/generated/prisma/models/OrderRevision.js +2 -0
- package/dist/generated/prisma/models/OrderRun.d.ts +2310 -0
- package/dist/generated/prisma/models/OrderRun.js +2 -0
- package/dist/generated/prisma/models/SchemaVersion.d.ts +985 -0
- package/dist/generated/prisma/models/SchemaVersion.js +2 -0
- package/dist/generated/prisma/models/Session.d.ts +1213 -0
- package/dist/generated/prisma/models/Session.js +2 -0
- package/dist/generated/prisma/models/Step.d.ts +2180 -0
- package/dist/generated/prisma/models/Step.js +2 -0
- package/dist/generated/prisma/models/StepRun.d.ts +1963 -0
- package/dist/generated/prisma/models/StepRun.js +2 -0
- package/dist/generated/prisma/models/User.d.ts +11819 -0
- package/dist/generated/prisma/models/User.js +2 -0
- package/dist/generated/prisma/models/UserPermission.d.ts +1348 -0
- package/dist/generated/prisma/models/UserPermission.js +2 -0
- package/dist/generated/prisma/models/WorkCenter.d.ts +1657 -0
- package/dist/generated/prisma/models/WorkCenter.js +2 -0
- package/dist/generated/prisma/models/WorkCenterUser.d.ts +1390 -0
- package/dist/generated/prisma/models/WorkCenterUser.js +2 -0
- package/dist/generated/prisma/models.d.ts +28 -0
- package/dist/generated/prisma/models.js +2 -0
- package/dist/hateoas.d.ts +7 -0
- package/dist/hateoas.js +61 -0
- package/dist/route-helpers.d.ts +318 -0
- package/dist/route-helpers.js +220 -0
- package/dist/routes/admin.d.ts +3 -0
- package/dist/routes/admin.js +147 -0
- package/dist/routes/audit.d.ts +3 -0
- package/dist/routes/audit.js +36 -0
- package/dist/routes/auth.d.ts +3 -0
- package/dist/routes/auth.js +112 -0
- package/dist/routes/dispatch.d.ts +3 -0
- package/dist/routes/dispatch.js +174 -0
- package/dist/routes/inventory.d.ts +3 -0
- package/dist/routes/inventory.js +70 -0
- package/dist/routes/item-fields.d.ts +3 -0
- package/dist/routes/item-fields.js +220 -0
- package/dist/routes/item-instances.d.ts +3 -0
- package/dist/routes/item-instances.js +426 -0
- package/dist/routes/items.d.ts +3 -0
- package/dist/routes/items.js +252 -0
- package/dist/routes/labor-tickets.d.ts +3 -0
- package/dist/routes/labor-tickets.js +268 -0
- package/dist/routes/operation-dependencies.d.ts +3 -0
- package/dist/routes/operation-dependencies.js +170 -0
- package/dist/routes/operation-field-refs.d.ts +3 -0
- package/dist/routes/operation-field-refs.js +263 -0
- package/dist/routes/operation-run-comments.d.ts +3 -0
- package/dist/routes/operation-run-comments.js +108 -0
- package/dist/routes/operation-run-transitions.d.ts +3 -0
- package/dist/routes/operation-run-transitions.js +249 -0
- package/dist/routes/operation-runs.d.ts +112 -0
- package/dist/routes/operation-runs.js +299 -0
- package/dist/routes/operations.d.ts +3 -0
- package/dist/routes/operations.js +283 -0
- package/dist/routes/order-revision-transitions.d.ts +3 -0
- package/dist/routes/order-revision-transitions.js +86 -0
- package/dist/routes/order-revisions.d.ts +51 -0
- package/dist/routes/order-revisions.js +327 -0
- package/dist/routes/order-run-transitions.d.ts +3 -0
- package/dist/routes/order-run-transitions.js +215 -0
- package/dist/routes/order-runs.d.ts +58 -0
- package/dist/routes/order-runs.js +335 -0
- package/dist/routes/orders.d.ts +3 -0
- package/dist/routes/orders.js +262 -0
- package/dist/routes/root.d.ts +3 -0
- package/dist/routes/root.js +123 -0
- package/dist/routes/schemas.d.ts +3 -0
- package/dist/routes/schemas.js +31 -0
- package/dist/routes/step-field-attachments.d.ts +3 -0
- package/dist/routes/step-field-attachments.js +231 -0
- package/dist/routes/step-fields.d.ts +100 -0
- package/dist/routes/step-fields.js +315 -0
- package/dist/routes/step-run-fields.d.ts +3 -0
- package/dist/routes/step-run-fields.js +438 -0
- package/dist/routes/step-run-transitions.d.ts +3 -0
- package/dist/routes/step-run-transitions.js +113 -0
- package/dist/routes/step-runs.d.ts +332 -0
- package/dist/routes/step-runs.js +324 -0
- package/dist/routes/steps.d.ts +3 -0
- package/dist/routes/steps.js +283 -0
- package/dist/routes/user-permissions.d.ts +3 -0
- package/dist/routes/user-permissions.js +100 -0
- package/dist/routes/users.d.ts +57 -0
- package/dist/routes/users.js +381 -0
- package/dist/routes/work-centers.d.ts +3 -0
- package/dist/routes/work-centers.js +280 -0
- package/dist/schema-registry.d.ts +3 -0
- package/dist/schema-registry.js +45 -0
- package/dist/services/attachment-service.d.ts +33 -0
- package/dist/services/attachment-service.js +118 -0
- package/dist/services/field-ref-service.d.ts +96 -0
- package/dist/services/field-ref-service.js +74 -0
- package/dist/services/field-service.d.ts +49 -0
- package/dist/services/field-service.js +114 -0
- package/dist/services/field-value-service.d.ts +61 -0
- package/dist/services/field-value-service.js +256 -0
- package/dist/services/item-instance-service.d.ts +152 -0
- package/dist/services/item-instance-service.js +155 -0
- package/dist/services/item-service.d.ts +47 -0
- package/dist/services/item-service.js +56 -0
- package/dist/services/labor-ticket-service.d.ts +40 -0
- package/dist/services/labor-ticket-service.js +148 -0
- package/dist/services/log-file-service.d.ts +4 -0
- package/dist/services/log-file-service.js +11 -0
- package/dist/services/operation-dependency-service.d.ts +33 -0
- package/dist/services/operation-dependency-service.js +30 -0
- package/dist/services/operation-run-comment-service.d.ts +17 -0
- package/dist/services/operation-run-comment-service.js +26 -0
- package/dist/services/operation-run-service.d.ts +126 -0
- package/dist/services/operation-run-service.js +347 -0
- package/dist/services/operation-service.d.ts +47 -0
- package/dist/services/operation-service.js +132 -0
- package/dist/services/order-revision-service.d.ts +53 -0
- package/dist/services/order-revision-service.js +264 -0
- package/dist/services/order-run-service.d.ts +138 -0
- package/dist/services/order-run-service.js +356 -0
- package/dist/services/order-service.d.ts +15 -0
- package/dist/services/order-service.js +68 -0
- package/dist/services/revision-diff-service.d.ts +3 -0
- package/dist/services/revision-diff-service.js +194 -0
- package/dist/services/step-run-service.d.ts +172 -0
- package/dist/services/step-run-service.js +106 -0
- package/dist/services/step-service.d.ts +104 -0
- package/dist/services/step-service.js +89 -0
- package/dist/services/user-service.d.ts +185 -0
- package/dist/services/user-service.js +132 -0
- package/dist/services/work-center-service.d.ts +29 -0
- package/dist/services/work-center-service.js +106 -0
- package/dist/supervisorAuth.d.ts +3 -0
- package/dist/supervisorAuth.js +16 -0
- package/dist/userService.d.ts +20 -0
- package/dist/userService.js +118 -0
- package/package.json +69 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { OperationRunStatus as OperationRunStatusValues, OrderRunStatus as OrderRunStatusValues, } from "@naisys/erp-shared";
|
|
2
|
+
import { writeAuditEntry } from "../audit.js";
|
|
3
|
+
import erpDb from "../erpDb.js";
|
|
4
|
+
// --- Prisma include & result type ---
|
|
5
|
+
export const includeRev = {
|
|
6
|
+
orderRev: { select: { revNo: true } },
|
|
7
|
+
order: { select: { item: { select: { key: true } } } },
|
|
8
|
+
itemInstances: { select: { id: true, key: true }, take: 1 },
|
|
9
|
+
createdBy: { select: { username: true } },
|
|
10
|
+
updatedBy: { select: { username: true } },
|
|
11
|
+
};
|
|
12
|
+
// --- Lookups ---
|
|
13
|
+
export async function listOrderRuns(where, page, pageSize) {
|
|
14
|
+
const [items, total] = await Promise.all([
|
|
15
|
+
erpDb.orderRun.findMany({
|
|
16
|
+
where,
|
|
17
|
+
include: includeRev,
|
|
18
|
+
skip: (page - 1) * pageSize,
|
|
19
|
+
take: pageSize,
|
|
20
|
+
orderBy: { createdAt: "desc" },
|
|
21
|
+
}),
|
|
22
|
+
erpDb.orderRun.count({ where }),
|
|
23
|
+
]);
|
|
24
|
+
return { items, total };
|
|
25
|
+
}
|
|
26
|
+
export async function getOrderRun(id) {
|
|
27
|
+
return erpDb.orderRun.findUnique({
|
|
28
|
+
where: { id },
|
|
29
|
+
include: includeRev,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export async function findExisting(id, orderId) {
|
|
33
|
+
const existing = await erpDb.orderRun.findUnique({ where: { id } });
|
|
34
|
+
if (!existing || existing.orderId !== orderId)
|
|
35
|
+
return null;
|
|
36
|
+
return existing;
|
|
37
|
+
}
|
|
38
|
+
export async function getOrderRunOpSummary(orderRunId) {
|
|
39
|
+
return erpDb.operationRun.findMany({
|
|
40
|
+
where: { orderRunId },
|
|
41
|
+
select: {
|
|
42
|
+
operation: { select: { seqNo: true, title: true } },
|
|
43
|
+
status: true,
|
|
44
|
+
},
|
|
45
|
+
orderBy: { operation: { seqNo: "asc" } },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// --- Validation ---
|
|
49
|
+
export function validateStatusFor(action, currentStatus, allowedStatuses) {
|
|
50
|
+
if (!allowedStatuses.includes(currentStatus)) {
|
|
51
|
+
return `Cannot ${action} order run in ${currentStatus} status`;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
export async function checkOpsComplete(orderRunId) {
|
|
56
|
+
const incompleteOps = await erpDb.operationRun.findMany({
|
|
57
|
+
where: {
|
|
58
|
+
orderRunId,
|
|
59
|
+
status: {
|
|
60
|
+
notIn: [
|
|
61
|
+
OperationRunStatusValues.completed,
|
|
62
|
+
OperationRunStatusValues.skipped,
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
include: { operation: { select: { seqNo: true, title: true } } },
|
|
67
|
+
});
|
|
68
|
+
if (incompleteOps.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
const labels = incompleteOps.map((op) => `Op ${op.operation.seqNo} "${op.operation.title}" (${op.status})`);
|
|
71
|
+
return `Cannot close order run: incomplete operations — ${labels.join(", ")}`;
|
|
72
|
+
}
|
|
73
|
+
export async function sumOpRunCosts(orderRunId) {
|
|
74
|
+
const result = await erpDb.operationRun.aggregate({
|
|
75
|
+
where: { orderRunId },
|
|
76
|
+
_sum: { cost: true },
|
|
77
|
+
});
|
|
78
|
+
return Math.round((result._sum.cost ?? 0) * 100) / 100;
|
|
79
|
+
}
|
|
80
|
+
// --- Mutations ---
|
|
81
|
+
export async function createOrderRun(orderId, orderRevId, data, userId) {
|
|
82
|
+
return erpDb.$transaction(async (erpTx) => {
|
|
83
|
+
const maxOrder = await erpTx.orderRun.findFirst({
|
|
84
|
+
where: { orderId },
|
|
85
|
+
orderBy: { runNo: "desc" },
|
|
86
|
+
select: { runNo: true },
|
|
87
|
+
});
|
|
88
|
+
const nextRunNo = (maxOrder?.runNo ?? 0) + 1;
|
|
89
|
+
const orderRun = await erpTx.orderRun.create({
|
|
90
|
+
data: {
|
|
91
|
+
runNo: nextRunNo,
|
|
92
|
+
orderId,
|
|
93
|
+
orderRevId,
|
|
94
|
+
priority: data.priority,
|
|
95
|
+
dueAt: data.dueAt ?? null,
|
|
96
|
+
releaseNote: data.releaseNote ?? null,
|
|
97
|
+
createdById: userId,
|
|
98
|
+
updatedById: userId,
|
|
99
|
+
},
|
|
100
|
+
include: includeRev,
|
|
101
|
+
});
|
|
102
|
+
// Fetch operations -> steps -> fields for this revision
|
|
103
|
+
const operations = await erpTx.operation.findMany({
|
|
104
|
+
where: { orderRevId },
|
|
105
|
+
include: {
|
|
106
|
+
steps: {
|
|
107
|
+
include: { fieldSet: { include: { fields: true } } },
|
|
108
|
+
orderBy: { seqNo: "asc" },
|
|
109
|
+
},
|
|
110
|
+
predecessors: { select: { predecessorId: true } },
|
|
111
|
+
},
|
|
112
|
+
orderBy: { seqNo: "asc" },
|
|
113
|
+
});
|
|
114
|
+
// Create OperationRun -> StepRun -> FieldRecord -> FieldValue rows
|
|
115
|
+
for (const op of operations) {
|
|
116
|
+
// Ops with predecessors start blocked; ops without start pending
|
|
117
|
+
const initialStatus = op.predecessors.length > 0
|
|
118
|
+
? OperationRunStatusValues.blocked
|
|
119
|
+
: OperationRunStatusValues.pending;
|
|
120
|
+
const opRun = await erpTx.operationRun.create({
|
|
121
|
+
data: {
|
|
122
|
+
orderRunId: orderRun.id,
|
|
123
|
+
operationId: op.id,
|
|
124
|
+
status: initialStatus,
|
|
125
|
+
createdById: userId,
|
|
126
|
+
updatedById: userId,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
for (const step of op.steps) {
|
|
130
|
+
const stepRun = await erpTx.stepRun.create({
|
|
131
|
+
data: {
|
|
132
|
+
operationRunId: opRun.id,
|
|
133
|
+
stepId: step.id,
|
|
134
|
+
createdById: userId,
|
|
135
|
+
updatedById: userId,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
const fields = step.fieldSet?.fields ?? [];
|
|
139
|
+
if (fields.length > 0 && step.fieldSetId) {
|
|
140
|
+
const fieldRecord = await erpTx.fieldRecord.create({
|
|
141
|
+
data: { fieldSetId: step.fieldSetId, createdById: userId },
|
|
142
|
+
});
|
|
143
|
+
await erpTx.stepRun.update({
|
|
144
|
+
where: { id: stepRun.id },
|
|
145
|
+
data: { fieldRecordId: fieldRecord.id },
|
|
146
|
+
});
|
|
147
|
+
for (const field of fields) {
|
|
148
|
+
await erpTx.fieldValue.create({
|
|
149
|
+
data: {
|
|
150
|
+
fieldRecordId: fieldRecord.id,
|
|
151
|
+
fieldId: field.id,
|
|
152
|
+
value: "",
|
|
153
|
+
createdById: userId,
|
|
154
|
+
updatedById: userId,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return orderRun;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
export async function updateOrderRun(id, data, userId) {
|
|
165
|
+
const updateData = { updatedById: userId };
|
|
166
|
+
if (data.priority !== undefined)
|
|
167
|
+
updateData.priority = data.priority;
|
|
168
|
+
if (data.releaseNote !== undefined)
|
|
169
|
+
updateData.releaseNote = data.releaseNote;
|
|
170
|
+
if (data.dueAt !== undefined)
|
|
171
|
+
updateData.dueAt = data.dueAt;
|
|
172
|
+
return erpDb.orderRun.update({
|
|
173
|
+
where: { id },
|
|
174
|
+
data: updateData,
|
|
175
|
+
include: includeRev,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
export async function deleteOrderRun(id) {
|
|
179
|
+
await erpDb.$transaction(async (tx) => {
|
|
180
|
+
const opRuns = await tx.operationRun.findMany({
|
|
181
|
+
where: { orderRunId: id },
|
|
182
|
+
select: { id: true },
|
|
183
|
+
});
|
|
184
|
+
const opRunIds = opRuns.map((r) => r.id);
|
|
185
|
+
if (opRunIds.length > 0) {
|
|
186
|
+
const stepRuns = await tx.stepRun.findMany({
|
|
187
|
+
where: { operationRunId: { in: opRunIds } },
|
|
188
|
+
select: { fieldRecordId: true },
|
|
189
|
+
});
|
|
190
|
+
const fieldRecordIds = stepRuns
|
|
191
|
+
.map((s) => s.fieldRecordId)
|
|
192
|
+
.filter((id) => id !== null);
|
|
193
|
+
if (fieldRecordIds.length > 0) {
|
|
194
|
+
await tx.fieldValue.deleteMany({
|
|
195
|
+
where: { fieldRecordId: { in: fieldRecordIds } },
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
await tx.stepRun.deleteMany({
|
|
199
|
+
where: { operationRunId: { in: opRunIds } },
|
|
200
|
+
});
|
|
201
|
+
if (fieldRecordIds.length > 0) {
|
|
202
|
+
await tx.fieldRecord.deleteMany({
|
|
203
|
+
where: { id: { in: fieldRecordIds } },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
await tx.operationRun.deleteMany({ where: { orderRunId: id } });
|
|
207
|
+
}
|
|
208
|
+
await tx.orderRun.delete({ where: { id } });
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
export async function transitionStatus(id, action, fromStatus, toStatus, userId, extraData) {
|
|
212
|
+
return erpDb.$transaction(async (erpTx) => {
|
|
213
|
+
const updated = await erpTx.orderRun.update({
|
|
214
|
+
where: { id },
|
|
215
|
+
data: { status: toStatus, updatedById: userId, ...extraData },
|
|
216
|
+
include: includeRev,
|
|
217
|
+
});
|
|
218
|
+
await writeAuditEntry(erpTx, "OrderRun", id, action, "status", fromStatus, toStatus, userId);
|
|
219
|
+
return updated;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
export async function findOrderRevision(orderId, revNo) {
|
|
223
|
+
return erpDb.orderRevision.findUnique({
|
|
224
|
+
where: { orderId_revNo: { orderId, revNo } },
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
export async function findLatestApprovedRevision(orderId) {
|
|
228
|
+
return erpDb.orderRevision.findFirst({
|
|
229
|
+
where: { orderId, status: "approved" },
|
|
230
|
+
orderBy: { revNo: "desc" },
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
export function getReopenTarget(currentStatus) {
|
|
234
|
+
return currentStatus === OrderRunStatusValues.closed
|
|
235
|
+
? OrderRunStatusValues.started
|
|
236
|
+
: OrderRunStatusValues.released;
|
|
237
|
+
}
|
|
238
|
+
// --- Completion ---
|
|
239
|
+
/**
|
|
240
|
+
* Auto-generate an instance key by finding the last instance for the item,
|
|
241
|
+
* parsing its key as a number, and incrementing. Returns the generated key
|
|
242
|
+
* or an error string if the last key is not numeric.
|
|
243
|
+
*/
|
|
244
|
+
async function autoGenerateInstanceKey(erpTx, itemId) {
|
|
245
|
+
const last = await erpTx.itemInstance.findFirst({
|
|
246
|
+
where: { itemId },
|
|
247
|
+
orderBy: { createdAt: "desc" },
|
|
248
|
+
select: { key: true },
|
|
249
|
+
});
|
|
250
|
+
if (!last)
|
|
251
|
+
return { key: "1" };
|
|
252
|
+
const num = Number(last.key);
|
|
253
|
+
if (isNaN(num)) {
|
|
254
|
+
return {
|
|
255
|
+
error: `Cannot auto-generate instance key: last key "${last.key}" is not numeric. Please provide a key manually.`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return { key: String(Math.floor(num) + 1) };
|
|
259
|
+
}
|
|
260
|
+
export async function completeOrderRun(orderRunId, orderId, data, userId) {
|
|
261
|
+
return erpDb.$transaction(async (erpTx) => {
|
|
262
|
+
// Load the order with its item
|
|
263
|
+
const order = await erpTx.order.findUniqueOrThrow({
|
|
264
|
+
where: { id: orderId },
|
|
265
|
+
select: {
|
|
266
|
+
item: {
|
|
267
|
+
select: {
|
|
268
|
+
id: true,
|
|
269
|
+
fieldSetId: true,
|
|
270
|
+
fieldSet: {
|
|
271
|
+
select: {
|
|
272
|
+
fields: {
|
|
273
|
+
select: { id: true },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
if (!order.item) {
|
|
282
|
+
return { error: "Order has no item assigned — cannot complete" };
|
|
283
|
+
}
|
|
284
|
+
// Determine instance key
|
|
285
|
+
let instanceKey = data.instanceKey;
|
|
286
|
+
if (!instanceKey) {
|
|
287
|
+
const result = await autoGenerateInstanceKey(erpTx, order.item.id);
|
|
288
|
+
if ("error" in result)
|
|
289
|
+
return result;
|
|
290
|
+
instanceKey = result.key;
|
|
291
|
+
}
|
|
292
|
+
// Check for duplicate key
|
|
293
|
+
const existing = await erpTx.itemInstance.findUnique({
|
|
294
|
+
where: { itemId_key: { itemId: order.item.id, key: instanceKey } },
|
|
295
|
+
});
|
|
296
|
+
if (existing) {
|
|
297
|
+
return {
|
|
298
|
+
error: `Instance key "${instanceKey}" already exists for this item`,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// Create the item instance
|
|
302
|
+
const instance = await erpTx.itemInstance.create({
|
|
303
|
+
data: {
|
|
304
|
+
itemId: order.item.id,
|
|
305
|
+
orderRunId: orderRunId,
|
|
306
|
+
key: instanceKey,
|
|
307
|
+
quantity: data.quantity ?? null,
|
|
308
|
+
createdById: userId,
|
|
309
|
+
updatedById: userId,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
// Create field record and field values if item has a field set
|
|
313
|
+
if (order.item.fieldSetId && (data.fieldValues?.length ?? 0) > 0) {
|
|
314
|
+
const fieldRecord = await erpTx.fieldRecord.create({
|
|
315
|
+
data: {
|
|
316
|
+
fieldSetId: order.item.fieldSetId,
|
|
317
|
+
createdById: userId,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
await erpTx.itemInstance.update({
|
|
321
|
+
where: { id: instance.id },
|
|
322
|
+
data: { fieldRecordId: fieldRecord.id },
|
|
323
|
+
});
|
|
324
|
+
for (const fv of data.fieldValues ?? []) {
|
|
325
|
+
await erpTx.fieldValue.create({
|
|
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
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Sum operation run costs and transition run to closed
|
|
338
|
+
const costResult = await erpTx.operationRun.aggregate({
|
|
339
|
+
where: { orderRunId },
|
|
340
|
+
_sum: { cost: true },
|
|
341
|
+
});
|
|
342
|
+
const cost = Math.round((costResult._sum.cost ?? 0) * 100) / 100;
|
|
343
|
+
const updated = await erpTx.orderRun.update({
|
|
344
|
+
where: { id: orderRunId },
|
|
345
|
+
data: {
|
|
346
|
+
status: OrderRunStatusValues.closed,
|
|
347
|
+
cost,
|
|
348
|
+
updatedById: userId,
|
|
349
|
+
},
|
|
350
|
+
include: includeRev,
|
|
351
|
+
});
|
|
352
|
+
await writeAuditEntry(erpTx, "OrderRun", orderRunId, "complete", "status", OrderRunStatusValues.started, OrderRunStatusValues.closed, userId);
|
|
353
|
+
return { run: updated };
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=order-run-service.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OrderModel } from "../generated/prisma/models/Order.js";
|
|
2
|
+
import { type WithAuditUsers } from "../route-helpers.js";
|
|
3
|
+
export type OrderWithRelations = OrderModel & WithAuditUsers & {
|
|
4
|
+
item: {
|
|
5
|
+
key: string;
|
|
6
|
+
} | null;
|
|
7
|
+
};
|
|
8
|
+
export declare function listOrders(where: Record<string, unknown>, page: number, pageSize: number): Promise<[OrderWithRelations[], number]>;
|
|
9
|
+
export declare function findExisting(key: string): Promise<OrderWithRelations | null>;
|
|
10
|
+
export declare function checkHasRevisions(orderId: number): Promise<boolean>;
|
|
11
|
+
export declare function resolveItemKey(itemKey: string | undefined | null): Promise<number | null>;
|
|
12
|
+
export declare function createOrder(key: string, description: string | undefined, itemId: number | null, userId: number): Promise<OrderWithRelations>;
|
|
13
|
+
export declare function updateOrder(key: string, data: Record<string, unknown>, userId: number): Promise<OrderWithRelations>;
|
|
14
|
+
export declare function deleteOrder(key: string): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=order-service.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import erpDb from "../erpDb.js";
|
|
2
|
+
import { includeUsers } from "../route-helpers.js";
|
|
3
|
+
// --- Prisma include & result type ---
|
|
4
|
+
const includeOrderRelations = {
|
|
5
|
+
...includeUsers,
|
|
6
|
+
item: { select: { key: true } },
|
|
7
|
+
};
|
|
8
|
+
// --- Lookups ---
|
|
9
|
+
export async function listOrders(where, page, pageSize) {
|
|
10
|
+
return Promise.all([
|
|
11
|
+
erpDb.order.findMany({
|
|
12
|
+
where,
|
|
13
|
+
include: includeOrderRelations,
|
|
14
|
+
skip: (page - 1) * pageSize,
|
|
15
|
+
take: pageSize,
|
|
16
|
+
orderBy: { createdAt: "desc" },
|
|
17
|
+
}),
|
|
18
|
+
erpDb.order.count({ where }),
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
export async function findExisting(key) {
|
|
22
|
+
return erpDb.order.findUnique({
|
|
23
|
+
where: { key },
|
|
24
|
+
include: includeOrderRelations,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// --- Validation ---
|
|
28
|
+
export async function checkHasRevisions(orderId) {
|
|
29
|
+
const revisionCount = await erpDb.orderRevision.count({
|
|
30
|
+
where: { orderId },
|
|
31
|
+
});
|
|
32
|
+
return revisionCount > 0;
|
|
33
|
+
}
|
|
34
|
+
// --- Mutations ---
|
|
35
|
+
export async function resolveItemKey(itemKey) {
|
|
36
|
+
if (!itemKey)
|
|
37
|
+
return null;
|
|
38
|
+
const item = await erpDb.item.findUnique({
|
|
39
|
+
where: { key: itemKey },
|
|
40
|
+
select: { id: true },
|
|
41
|
+
});
|
|
42
|
+
if (!item)
|
|
43
|
+
throw new Error(`Item '${itemKey}' not found`);
|
|
44
|
+
return item.id;
|
|
45
|
+
}
|
|
46
|
+
export async function createOrder(key, description, itemId, userId) {
|
|
47
|
+
return erpDb.order.create({
|
|
48
|
+
data: {
|
|
49
|
+
key,
|
|
50
|
+
description,
|
|
51
|
+
itemId,
|
|
52
|
+
createdById: userId,
|
|
53
|
+
updatedById: userId,
|
|
54
|
+
},
|
|
55
|
+
include: includeOrderRelations,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export async function updateOrder(key, data, userId) {
|
|
59
|
+
return erpDb.order.update({
|
|
60
|
+
where: { key },
|
|
61
|
+
data: { ...data, updatedById: userId },
|
|
62
|
+
include: includeOrderRelations,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export async function deleteOrder(key) {
|
|
66
|
+
await erpDb.order.delete({ where: { key } });
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=order-service.js.map
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import erpDb from "../erpDb.js";
|
|
2
|
+
// --- Prisma deep include for full revision tree ---
|
|
3
|
+
const includeFullTree = {
|
|
4
|
+
operations: {
|
|
5
|
+
orderBy: { seqNo: "asc" },
|
|
6
|
+
include: {
|
|
7
|
+
steps: {
|
|
8
|
+
orderBy: { seqNo: "asc" },
|
|
9
|
+
include: {
|
|
10
|
+
fieldSet: {
|
|
11
|
+
include: { fields: { orderBy: { seqNo: "asc" } } },
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
predecessors: {
|
|
16
|
+
include: {
|
|
17
|
+
predecessor: { select: { seqNo: true, title: true } },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
async function getRevisionTree(orderId, revNo) {
|
|
24
|
+
return erpDb.orderRevision.findFirst({
|
|
25
|
+
where: { orderId, revNo },
|
|
26
|
+
include: includeFullTree,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// --- Comparison helpers ---
|
|
30
|
+
function compareProps(pairs) {
|
|
31
|
+
const changes = [];
|
|
32
|
+
for (const [field, from, to] of pairs) {
|
|
33
|
+
if (from !== to) {
|
|
34
|
+
changes.push({
|
|
35
|
+
field,
|
|
36
|
+
from: from,
|
|
37
|
+
to: to,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return changes;
|
|
42
|
+
}
|
|
43
|
+
function diffFields(fromFields, toFields) {
|
|
44
|
+
const fromMap = new Map(fromFields.map((f) => [f.seqNo, f]));
|
|
45
|
+
const toMap = new Map(toFields.map((f) => [f.seqNo, f]));
|
|
46
|
+
const allSeqNos = new Set([...fromMap.keys(), ...toMap.keys()]);
|
|
47
|
+
const result = [];
|
|
48
|
+
for (const seqNo of [...allSeqNos].sort((a, b) => a - b)) {
|
|
49
|
+
const from = fromMap.get(seqNo);
|
|
50
|
+
const to = toMap.get(seqNo);
|
|
51
|
+
if (!from && to) {
|
|
52
|
+
result.push({ seqNo, label: to.label, status: "added" });
|
|
53
|
+
}
|
|
54
|
+
else if (from && !to) {
|
|
55
|
+
result.push({ seqNo, label: from.label, status: "removed" });
|
|
56
|
+
}
|
|
57
|
+
else if (from && to) {
|
|
58
|
+
const changes = compareProps([
|
|
59
|
+
["label", from.label, to.label],
|
|
60
|
+
["type", from.type, to.type],
|
|
61
|
+
["isArray", from.isArray, to.isArray],
|
|
62
|
+
["required", from.required, to.required],
|
|
63
|
+
]);
|
|
64
|
+
result.push({
|
|
65
|
+
seqNo,
|
|
66
|
+
label: to.label,
|
|
67
|
+
status: changes.length > 0 ? "modified" : "unchanged",
|
|
68
|
+
...(changes.length > 0 ? { changes } : {}),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
function diffSteps(fromSteps, toSteps) {
|
|
75
|
+
const fromMap = new Map(fromSteps.map((s) => [s.seqNo, s]));
|
|
76
|
+
const toMap = new Map(toSteps.map((s) => [s.seqNo, s]));
|
|
77
|
+
const allSeqNos = new Set([...fromMap.keys(), ...toMap.keys()]);
|
|
78
|
+
const result = [];
|
|
79
|
+
for (const seqNo of [...allSeqNos].sort((a, b) => a - b)) {
|
|
80
|
+
const from = fromMap.get(seqNo);
|
|
81
|
+
const to = toMap.get(seqNo);
|
|
82
|
+
if (!from && to) {
|
|
83
|
+
result.push({ seqNo, title: to.title, status: "added" });
|
|
84
|
+
}
|
|
85
|
+
else if (from && !to) {
|
|
86
|
+
result.push({ seqNo, title: from.title, status: "removed" });
|
|
87
|
+
}
|
|
88
|
+
else if (from && to) {
|
|
89
|
+
const changes = compareProps([
|
|
90
|
+
["title", from.title, to.title],
|
|
91
|
+
["instructions", from.instructions, to.instructions],
|
|
92
|
+
["multiSet", from.multiSet, to.multiSet],
|
|
93
|
+
]);
|
|
94
|
+
const fields = diffFields(from.fieldSet?.fields ?? [], to.fieldSet?.fields ?? []);
|
|
95
|
+
const hasFieldChanges = fields.some((f) => f.status !== "unchanged");
|
|
96
|
+
const isModified = changes.length > 0 || hasFieldChanges;
|
|
97
|
+
result.push({
|
|
98
|
+
seqNo,
|
|
99
|
+
title: to.title,
|
|
100
|
+
status: isModified ? "modified" : "unchanged",
|
|
101
|
+
...(changes.length > 0 ? { changes } : {}),
|
|
102
|
+
...(hasFieldChanges ? { fields } : {}),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
function diffDeps(fromDeps, toDeps) {
|
|
109
|
+
const fromSet = new Map(fromDeps.map((d) => [d.predecessor.seqNo, d.predecessor.title]));
|
|
110
|
+
const toSet = new Map(toDeps.map((d) => [d.predecessor.seqNo, d.predecessor.title]));
|
|
111
|
+
const allSeqNos = new Set([...fromSet.keys(), ...toSet.keys()]);
|
|
112
|
+
const result = [];
|
|
113
|
+
for (const seqNo of [...allSeqNos].sort((a, b) => a - b)) {
|
|
114
|
+
const fromTitle = fromSet.get(seqNo);
|
|
115
|
+
const toTitle = toSet.get(seqNo);
|
|
116
|
+
if (fromTitle === undefined && toTitle !== undefined) {
|
|
117
|
+
result.push({
|
|
118
|
+
predecessorSeqNo: seqNo,
|
|
119
|
+
predecessorTitle: toTitle,
|
|
120
|
+
status: "added",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else if (fromTitle !== undefined && toTitle === undefined) {
|
|
124
|
+
result.push({
|
|
125
|
+
predecessorSeqNo: seqNo,
|
|
126
|
+
predecessorTitle: fromTitle,
|
|
127
|
+
status: "removed",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else if (fromTitle !== undefined) {
|
|
131
|
+
result.push({
|
|
132
|
+
predecessorSeqNo: seqNo,
|
|
133
|
+
predecessorTitle: toTitle ?? fromTitle,
|
|
134
|
+
status: "unchanged",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
function diffOperations(fromOps, toOps) {
|
|
141
|
+
const fromMap = new Map(fromOps.map((op) => [op.seqNo, op]));
|
|
142
|
+
const toMap = new Map(toOps.map((op) => [op.seqNo, op]));
|
|
143
|
+
const allSeqNos = new Set([...fromMap.keys(), ...toMap.keys()]);
|
|
144
|
+
const result = [];
|
|
145
|
+
for (const seqNo of [...allSeqNos].sort((a, b) => a - b)) {
|
|
146
|
+
const from = fromMap.get(seqNo);
|
|
147
|
+
const to = toMap.get(seqNo);
|
|
148
|
+
if (!from && to) {
|
|
149
|
+
result.push({ seqNo, title: to.title, status: "added" });
|
|
150
|
+
}
|
|
151
|
+
else if (from && !to) {
|
|
152
|
+
result.push({ seqNo, title: from.title, status: "removed" });
|
|
153
|
+
}
|
|
154
|
+
else if (from && to) {
|
|
155
|
+
const changes = compareProps([
|
|
156
|
+
["title", from.title, to.title],
|
|
157
|
+
["description", from.description, to.description],
|
|
158
|
+
]);
|
|
159
|
+
const steps = diffSteps(from.steps, to.steps);
|
|
160
|
+
const deps = diffDeps(from.predecessors, to.predecessors);
|
|
161
|
+
const hasStepChanges = steps.some((s) => s.status !== "unchanged");
|
|
162
|
+
const hasDepChanges = deps.some((d) => d.status !== "unchanged");
|
|
163
|
+
const isModified = changes.length > 0 || hasStepChanges || hasDepChanges;
|
|
164
|
+
result.push({
|
|
165
|
+
seqNo,
|
|
166
|
+
title: to.title,
|
|
167
|
+
status: isModified ? "modified" : "unchanged",
|
|
168
|
+
...(changes.length > 0 ? { changes } : {}),
|
|
169
|
+
...(hasStepChanges ? { steps } : {}),
|
|
170
|
+
...(hasDepChanges ? { dependencies: deps } : {}),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
// --- Public API ---
|
|
177
|
+
export async function diffRevisions(orderId, fromRevNo, toRevNo) {
|
|
178
|
+
const [fromRev, toRev] = await Promise.all([
|
|
179
|
+
getRevisionTree(orderId, fromRevNo),
|
|
180
|
+
getRevisionTree(orderId, toRevNo),
|
|
181
|
+
]);
|
|
182
|
+
if (!fromRev || !toRev)
|
|
183
|
+
return null;
|
|
184
|
+
const revisionChanges = compareProps([
|
|
185
|
+
["description", fromRev.description, toRev.description],
|
|
186
|
+
]);
|
|
187
|
+
return {
|
|
188
|
+
fromRevNo,
|
|
189
|
+
toRevNo,
|
|
190
|
+
revisionChanges,
|
|
191
|
+
operations: diffOperations(fromRev.operations, toRev.operations),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=revision-diff-service.js.map
|