@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.

Files changed (201) hide show
  1. package/bin/naisys-erp +2 -0
  2. package/client-dist/android-chrome-192x192.png +0 -0
  3. package/client-dist/android-chrome-512x512.png +0 -0
  4. package/client-dist/apple-touch-icon.png +0 -0
  5. package/client-dist/assets/index-45dVo30p.css +1 -0
  6. package/client-dist/assets/index-Dffms7F_.js +168 -0
  7. package/client-dist/assets/naisys-logo-CzoPnn5I.webp +0 -0
  8. package/client-dist/favicon.ico +0 -0
  9. package/client-dist/index.html +42 -0
  10. package/client-dist/site.webmanifest +22 -0
  11. package/dist/api-reference.d.ts +10 -0
  12. package/dist/api-reference.js +101 -0
  13. package/dist/audit.d.ts +5 -0
  14. package/dist/audit.js +14 -0
  15. package/dist/auth-middleware.d.ts +18 -0
  16. package/dist/auth-middleware.js +203 -0
  17. package/dist/dbConfig.d.ts +5 -0
  18. package/dist/dbConfig.js +10 -0
  19. package/dist/erpDb.d.ts +10 -0
  20. package/dist/erpDb.js +34 -0
  21. package/dist/erpServer.d.ts +10 -0
  22. package/dist/erpServer.js +321 -0
  23. package/dist/error-handler.d.ts +7 -0
  24. package/dist/error-handler.js +17 -0
  25. package/dist/generated/prisma/client.d.ts +154 -0
  26. package/dist/generated/prisma/client.js +35 -0
  27. package/dist/generated/prisma/commonInputTypes.d.ts +637 -0
  28. package/dist/generated/prisma/commonInputTypes.js +11 -0
  29. package/dist/generated/prisma/enums.d.ts +59 -0
  30. package/dist/generated/prisma/enums.js +60 -0
  31. package/dist/generated/prisma/internal/class.d.ts +406 -0
  32. package/dist/generated/prisma/internal/class.js +50 -0
  33. package/dist/generated/prisma/internal/prismaNamespace.d.ts +2722 -0
  34. package/dist/generated/prisma/internal/prismaNamespace.js +366 -0
  35. package/dist/generated/prisma/models/Attachment.d.ts +1455 -0
  36. package/dist/generated/prisma/models/Attachment.js +2 -0
  37. package/dist/generated/prisma/models/AuditLog.d.ts +1359 -0
  38. package/dist/generated/prisma/models/AuditLog.js +2 -0
  39. package/dist/generated/prisma/models/Field.d.ts +1880 -0
  40. package/dist/generated/prisma/models/Field.js +2 -0
  41. package/dist/generated/prisma/models/FieldAttachment.d.ts +1245 -0
  42. package/dist/generated/prisma/models/FieldAttachment.js +2 -0
  43. package/dist/generated/prisma/models/FieldRecord.d.ts +1625 -0
  44. package/dist/generated/prisma/models/FieldRecord.js +2 -0
  45. package/dist/generated/prisma/models/FieldSet.d.ts +1577 -0
  46. package/dist/generated/prisma/models/FieldSet.js +2 -0
  47. package/dist/generated/prisma/models/FieldValue.d.ts +1908 -0
  48. package/dist/generated/prisma/models/FieldValue.js +2 -0
  49. package/dist/generated/prisma/models/Item.d.ts +1858 -0
  50. package/dist/generated/prisma/models/Item.js +2 -0
  51. package/dist/generated/prisma/models/ItemInstance.d.ts +1987 -0
  52. package/dist/generated/prisma/models/ItemInstance.js +2 -0
  53. package/dist/generated/prisma/models/LaborTicket.d.ts +1867 -0
  54. package/dist/generated/prisma/models/LaborTicket.js +2 -0
  55. package/dist/generated/prisma/models/Operation.d.ts +2578 -0
  56. package/dist/generated/prisma/models/Operation.js +2 -0
  57. package/dist/generated/prisma/models/OperationDependency.d.ts +1434 -0
  58. package/dist/generated/prisma/models/OperationDependency.js +2 -0
  59. package/dist/generated/prisma/models/OperationFieldRef.d.ts +1539 -0
  60. package/dist/generated/prisma/models/OperationFieldRef.js +2 -0
  61. package/dist/generated/prisma/models/OperationRun.d.ts +2563 -0
  62. package/dist/generated/prisma/models/OperationRun.js +2 -0
  63. package/dist/generated/prisma/models/OperationRunComment.d.ts +1366 -0
  64. package/dist/generated/prisma/models/OperationRunComment.js +2 -0
  65. package/dist/generated/prisma/models/Order.d.ts +1931 -0
  66. package/dist/generated/prisma/models/Order.js +2 -0
  67. package/dist/generated/prisma/models/OrderRevision.d.ts +1962 -0
  68. package/dist/generated/prisma/models/OrderRevision.js +2 -0
  69. package/dist/generated/prisma/models/OrderRun.d.ts +2310 -0
  70. package/dist/generated/prisma/models/OrderRun.js +2 -0
  71. package/dist/generated/prisma/models/SchemaVersion.d.ts +985 -0
  72. package/dist/generated/prisma/models/SchemaVersion.js +2 -0
  73. package/dist/generated/prisma/models/Session.d.ts +1213 -0
  74. package/dist/generated/prisma/models/Session.js +2 -0
  75. package/dist/generated/prisma/models/Step.d.ts +2180 -0
  76. package/dist/generated/prisma/models/Step.js +2 -0
  77. package/dist/generated/prisma/models/StepRun.d.ts +1963 -0
  78. package/dist/generated/prisma/models/StepRun.js +2 -0
  79. package/dist/generated/prisma/models/User.d.ts +11819 -0
  80. package/dist/generated/prisma/models/User.js +2 -0
  81. package/dist/generated/prisma/models/UserPermission.d.ts +1348 -0
  82. package/dist/generated/prisma/models/UserPermission.js +2 -0
  83. package/dist/generated/prisma/models/WorkCenter.d.ts +1657 -0
  84. package/dist/generated/prisma/models/WorkCenter.js +2 -0
  85. package/dist/generated/prisma/models/WorkCenterUser.d.ts +1390 -0
  86. package/dist/generated/prisma/models/WorkCenterUser.js +2 -0
  87. package/dist/generated/prisma/models.d.ts +28 -0
  88. package/dist/generated/prisma/models.js +2 -0
  89. package/dist/hateoas.d.ts +7 -0
  90. package/dist/hateoas.js +61 -0
  91. package/dist/route-helpers.d.ts +318 -0
  92. package/dist/route-helpers.js +220 -0
  93. package/dist/routes/admin.d.ts +3 -0
  94. package/dist/routes/admin.js +147 -0
  95. package/dist/routes/audit.d.ts +3 -0
  96. package/dist/routes/audit.js +36 -0
  97. package/dist/routes/auth.d.ts +3 -0
  98. package/dist/routes/auth.js +112 -0
  99. package/dist/routes/dispatch.d.ts +3 -0
  100. package/dist/routes/dispatch.js +174 -0
  101. package/dist/routes/inventory.d.ts +3 -0
  102. package/dist/routes/inventory.js +70 -0
  103. package/dist/routes/item-fields.d.ts +3 -0
  104. package/dist/routes/item-fields.js +220 -0
  105. package/dist/routes/item-instances.d.ts +3 -0
  106. package/dist/routes/item-instances.js +426 -0
  107. package/dist/routes/items.d.ts +3 -0
  108. package/dist/routes/items.js +252 -0
  109. package/dist/routes/labor-tickets.d.ts +3 -0
  110. package/dist/routes/labor-tickets.js +268 -0
  111. package/dist/routes/operation-dependencies.d.ts +3 -0
  112. package/dist/routes/operation-dependencies.js +170 -0
  113. package/dist/routes/operation-field-refs.d.ts +3 -0
  114. package/dist/routes/operation-field-refs.js +263 -0
  115. package/dist/routes/operation-run-comments.d.ts +3 -0
  116. package/dist/routes/operation-run-comments.js +108 -0
  117. package/dist/routes/operation-run-transitions.d.ts +3 -0
  118. package/dist/routes/operation-run-transitions.js +249 -0
  119. package/dist/routes/operation-runs.d.ts +112 -0
  120. package/dist/routes/operation-runs.js +299 -0
  121. package/dist/routes/operations.d.ts +3 -0
  122. package/dist/routes/operations.js +283 -0
  123. package/dist/routes/order-revision-transitions.d.ts +3 -0
  124. package/dist/routes/order-revision-transitions.js +86 -0
  125. package/dist/routes/order-revisions.d.ts +51 -0
  126. package/dist/routes/order-revisions.js +327 -0
  127. package/dist/routes/order-run-transitions.d.ts +3 -0
  128. package/dist/routes/order-run-transitions.js +215 -0
  129. package/dist/routes/order-runs.d.ts +58 -0
  130. package/dist/routes/order-runs.js +335 -0
  131. package/dist/routes/orders.d.ts +3 -0
  132. package/dist/routes/orders.js +262 -0
  133. package/dist/routes/root.d.ts +3 -0
  134. package/dist/routes/root.js +123 -0
  135. package/dist/routes/schemas.d.ts +3 -0
  136. package/dist/routes/schemas.js +31 -0
  137. package/dist/routes/step-field-attachments.d.ts +3 -0
  138. package/dist/routes/step-field-attachments.js +231 -0
  139. package/dist/routes/step-fields.d.ts +100 -0
  140. package/dist/routes/step-fields.js +315 -0
  141. package/dist/routes/step-run-fields.d.ts +3 -0
  142. package/dist/routes/step-run-fields.js +438 -0
  143. package/dist/routes/step-run-transitions.d.ts +3 -0
  144. package/dist/routes/step-run-transitions.js +113 -0
  145. package/dist/routes/step-runs.d.ts +332 -0
  146. package/dist/routes/step-runs.js +324 -0
  147. package/dist/routes/steps.d.ts +3 -0
  148. package/dist/routes/steps.js +283 -0
  149. package/dist/routes/user-permissions.d.ts +3 -0
  150. package/dist/routes/user-permissions.js +100 -0
  151. package/dist/routes/users.d.ts +57 -0
  152. package/dist/routes/users.js +381 -0
  153. package/dist/routes/work-centers.d.ts +3 -0
  154. package/dist/routes/work-centers.js +280 -0
  155. package/dist/schema-registry.d.ts +3 -0
  156. package/dist/schema-registry.js +45 -0
  157. package/dist/services/attachment-service.d.ts +33 -0
  158. package/dist/services/attachment-service.js +118 -0
  159. package/dist/services/field-ref-service.d.ts +96 -0
  160. package/dist/services/field-ref-service.js +74 -0
  161. package/dist/services/field-service.d.ts +49 -0
  162. package/dist/services/field-service.js +114 -0
  163. package/dist/services/field-value-service.d.ts +61 -0
  164. package/dist/services/field-value-service.js +256 -0
  165. package/dist/services/item-instance-service.d.ts +152 -0
  166. package/dist/services/item-instance-service.js +155 -0
  167. package/dist/services/item-service.d.ts +47 -0
  168. package/dist/services/item-service.js +56 -0
  169. package/dist/services/labor-ticket-service.d.ts +40 -0
  170. package/dist/services/labor-ticket-service.js +148 -0
  171. package/dist/services/log-file-service.d.ts +4 -0
  172. package/dist/services/log-file-service.js +11 -0
  173. package/dist/services/operation-dependency-service.d.ts +33 -0
  174. package/dist/services/operation-dependency-service.js +30 -0
  175. package/dist/services/operation-run-comment-service.d.ts +17 -0
  176. package/dist/services/operation-run-comment-service.js +26 -0
  177. package/dist/services/operation-run-service.d.ts +126 -0
  178. package/dist/services/operation-run-service.js +347 -0
  179. package/dist/services/operation-service.d.ts +47 -0
  180. package/dist/services/operation-service.js +132 -0
  181. package/dist/services/order-revision-service.d.ts +53 -0
  182. package/dist/services/order-revision-service.js +264 -0
  183. package/dist/services/order-run-service.d.ts +138 -0
  184. package/dist/services/order-run-service.js +356 -0
  185. package/dist/services/order-service.d.ts +15 -0
  186. package/dist/services/order-service.js +68 -0
  187. package/dist/services/revision-diff-service.d.ts +3 -0
  188. package/dist/services/revision-diff-service.js +194 -0
  189. package/dist/services/step-run-service.d.ts +172 -0
  190. package/dist/services/step-run-service.js +106 -0
  191. package/dist/services/step-service.d.ts +104 -0
  192. package/dist/services/step-service.js +89 -0
  193. package/dist/services/user-service.d.ts +185 -0
  194. package/dist/services/user-service.js +132 -0
  195. package/dist/services/work-center-service.d.ts +29 -0
  196. package/dist/services/work-center-service.js +106 -0
  197. package/dist/supervisorAuth.d.ts +3 -0
  198. package/dist/supervisorAuth.js +16 -0
  199. package/dist/userService.d.ts +20 -0
  200. package/dist/userService.js +118 -0
  201. 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,3 @@
1
+ import type { RevisionDiffResponse } from "@naisys/erp-shared";
2
+ export declare function diffRevisions(orderId: number, fromRevNo: number, toRevNo: number): Promise<RevisionDiffResponse | null>;
3
+ //# sourceMappingURL=revision-diff-service.d.ts.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