@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,347 @@
1
+ import { OperationRunStatus as OperationRunStatusValues, } from "@naisys/erp-shared";
2
+ import { fieldTypeString, getValueFormatHint, } from "@naisys/erp-shared";
3
+ import { writeAuditEntry } from "../audit.js";
4
+ import erpDb from "../erpDb.js";
5
+ import { API_PREFIX } from "../hateoas.js";
6
+ import { deserializeFieldValue, validateFieldValue, } from "./field-value-service.js";
7
+ // --- Prisma include & result type ---
8
+ export const includeOp = {
9
+ operation: {
10
+ select: {
11
+ seqNo: true,
12
+ title: true,
13
+ description: true,
14
+ workCenter: { select: { key: true } },
15
+ },
16
+ },
17
+ assignedTo: { select: { username: true } },
18
+ createdBy: { select: { username: true } },
19
+ updatedBy: { select: { username: true } },
20
+ };
21
+ // --- Lookups ---
22
+ export async function listOpRuns(runId) {
23
+ return erpDb.operationRun.findMany({
24
+ where: { orderRunId: runId },
25
+ include: {
26
+ operation: {
27
+ select: {
28
+ seqNo: true,
29
+ title: true,
30
+ description: true,
31
+ workCenter: { select: { key: true } },
32
+ predecessors: {
33
+ include: { predecessor: { select: { seqNo: true, title: true } } },
34
+ orderBy: { predecessor: { seqNo: "asc" } },
35
+ },
36
+ },
37
+ },
38
+ _count: { select: { stepRuns: true } },
39
+ assignedTo: { select: { username: true } },
40
+ createdBy: { select: { username: true } },
41
+ updatedBy: { select: { username: true } },
42
+ },
43
+ orderBy: { operation: { seqNo: "asc" } },
44
+ });
45
+ }
46
+ export async function getOpRun(id) {
47
+ return erpDb.operationRun.findUnique({
48
+ where: { id },
49
+ include: includeOp,
50
+ });
51
+ }
52
+ const includeOpSeqNo = {
53
+ operation: { select: { seqNo: true } },
54
+ };
55
+ export async function findExisting(id, runId) {
56
+ const existing = await erpDb.operationRun.findUnique({
57
+ where: { id },
58
+ include: includeOpSeqNo,
59
+ });
60
+ if (!existing || existing.orderRunId !== runId)
61
+ return null;
62
+ return existing;
63
+ }
64
+ export async function getOpRunStepSummary(opRunId) {
65
+ return erpDb.stepRun.findMany({
66
+ where: { operationRunId: opRunId },
67
+ select: {
68
+ step: { select: { seqNo: true, title: true } },
69
+ completed: true,
70
+ },
71
+ orderBy: { step: { seqNo: "asc" } },
72
+ });
73
+ }
74
+ /**
75
+ * Resolve field reference values for an operation run.
76
+ * Looks up the plan-level field refs, finds the corresponding step runs
77
+ * in the same order run, and returns FieldValueEntry-shaped data
78
+ * compatible with the FieldValueRunList component.
79
+ */
80
+ export async function getOpRunFieldRefSummary(operationId, orderRunId, orderKey, runNo) {
81
+ // Get field refs from the plan-level operation
82
+ const fieldRefs = await erpDb.operationFieldRef.findMany({
83
+ where: { operationId },
84
+ include: {
85
+ sourceStep: {
86
+ select: {
87
+ id: true,
88
+ seqNo: true,
89
+ title: true,
90
+ multiSet: true,
91
+ operation: { select: { seqNo: true, title: true } },
92
+ fieldSet: {
93
+ select: {
94
+ fields: {
95
+ select: {
96
+ id: true,
97
+ seqNo: true,
98
+ label: true,
99
+ type: true,
100
+ isArray: true,
101
+ required: true,
102
+ },
103
+ orderBy: { seqNo: "asc" },
104
+ },
105
+ },
106
+ },
107
+ },
108
+ },
109
+ },
110
+ orderBy: { seqNo: "asc" },
111
+ });
112
+ if (fieldRefs.length === 0)
113
+ return [];
114
+ // Collect all source step IDs to batch-fetch their step runs
115
+ const sourceStepIds = fieldRefs.map((r) => r.sourceStep.id);
116
+ // Find the step runs for these source steps in the same order run
117
+ // Include attachments for attachment-type fields
118
+ const stepRuns = await erpDb.stepRun.findMany({
119
+ where: {
120
+ operationRun: { orderRunId },
121
+ stepId: { in: sourceStepIds },
122
+ },
123
+ select: {
124
+ stepId: true,
125
+ fieldRecord: {
126
+ select: {
127
+ fieldValues: {
128
+ select: {
129
+ fieldId: true,
130
+ setIndex: true,
131
+ value: true,
132
+ fieldAttachments: {
133
+ include: {
134
+ attachment: {
135
+ select: { publicId: true, filename: true, fileSize: true },
136
+ },
137
+ },
138
+ },
139
+ },
140
+ orderBy: { setIndex: "asc" },
141
+ },
142
+ },
143
+ },
144
+ },
145
+ });
146
+ const stepRunMap = new Map(stepRuns.map((sr) => [sr.stepId, sr]));
147
+ return fieldRefs.map((ref) => {
148
+ const sr = stepRunMap.get(ref.sourceStep.id);
149
+ const storedFieldValues = sr?.fieldRecord?.fieldValues ?? [];
150
+ const fields = ref.sourceStep.fieldSet?.fields ?? [];
151
+ // Determine set count
152
+ const maxSetIndex = storedFieldValues.reduce((max, fv) => Math.max(max, fv.setIndex), -1);
153
+ const setCount = Math.max(1, maxSetIndex + 1);
154
+ // Build FieldValueEntry-shaped objects for each set × field
155
+ const fieldValues = [];
156
+ for (let si = 0; si < setCount; si++) {
157
+ for (const field of fields) {
158
+ const stored = storedFieldValues.find((fv) => fv.fieldId === field.id && fv.setIndex === si);
159
+ const value = deserializeFieldValue(stored?.value ?? "", field.isArray);
160
+ const setPath = ref.sourceStep.multiSet
161
+ ? `/sets/${si}/fields/${field.seqNo}`
162
+ : `/fields/${field.seqNo}`;
163
+ const stepsHref = `${API_PREFIX}/orders/${orderKey}/runs/${runNo}/ops/${ref.sourceStep.operation.seqNo}/steps/${ref.sourceStep.seqNo}`;
164
+ const attachments = field.type === "attachment" && stored
165
+ ? stored.fieldAttachments.map((sfa) => ({
166
+ id: sfa.attachment.publicId,
167
+ filename: sfa.attachment.filename,
168
+ fileSize: sfa.attachment.fileSize,
169
+ downloadHref: `${stepsHref}${setPath}/attachments/${sfa.attachment.publicId}`,
170
+ }))
171
+ : undefined;
172
+ const fieldType = fieldTypeString(field.type, field.isArray);
173
+ fieldValues.push({
174
+ fieldId: field.id,
175
+ fieldSeqNo: field.seqNo,
176
+ label: field.label,
177
+ type: fieldType,
178
+ valueFormat: getValueFormatHint(fieldType),
179
+ required: field.required,
180
+ setIndex: si,
181
+ value,
182
+ attachments,
183
+ validation: validateFieldValue(field.type, field.isArray, field.required, value),
184
+ });
185
+ }
186
+ }
187
+ return {
188
+ seqNo: ref.seqNo,
189
+ title: ref.title,
190
+ sourceOpSeqNo: ref.sourceStep.operation.seqNo,
191
+ sourceOpTitle: ref.sourceStep.operation.title,
192
+ sourceStepSeqNo: ref.sourceStep.seqNo,
193
+ sourceStepTitle: ref.sourceStep.title,
194
+ multiSet: ref.sourceStep.multiSet,
195
+ fieldValues,
196
+ };
197
+ });
198
+ }
199
+ // --- Validation ---
200
+ export function validateStatusFor(action, currentStatus, allowedStatuses) {
201
+ if (!allowedStatuses.includes(currentStatus)) {
202
+ return `Cannot ${action} operation run in ${currentStatus} status`;
203
+ }
204
+ return null;
205
+ }
206
+ /**
207
+ * Check that all predecessor dependencies for this operation are complete.
208
+ * Uses the OperationDependency graph rather than seqNo ordering.
209
+ */
210
+ export async function checkPredecessorsComplete(runId, operationId) {
211
+ // Get predecessor operation IDs from dependency graph
212
+ const deps = await erpDb.operationDependency.findMany({
213
+ where: { successorId: operationId },
214
+ select: { predecessorId: true },
215
+ });
216
+ if (deps.length === 0)
217
+ return null;
218
+ const predecessorIds = deps.map((d) => d.predecessorId);
219
+ const incompletePrior = await erpDb.operationRun.findMany({
220
+ where: {
221
+ orderRunId: runId,
222
+ operationId: { in: predecessorIds },
223
+ status: {
224
+ notIn: [
225
+ OperationRunStatusValues.completed,
226
+ OperationRunStatusValues.skipped,
227
+ ],
228
+ },
229
+ },
230
+ include: { operation: { select: { seqNo: true, title: true } } },
231
+ });
232
+ if (incompletePrior.length === 0)
233
+ return null;
234
+ const labels = incompletePrior.map((op) => `Op ${op.operation.seqNo} "${op.operation.title}" (${op.status})`);
235
+ return `Cannot start: predecessor operations not complete — ${labels.join(", ")}`;
236
+ }
237
+ /**
238
+ * After completing/skipping an operation, unblock successor ops
239
+ * whose predecessors are now all complete.
240
+ */
241
+ export async function unblockSuccessors(runId, operationId, userId) {
242
+ // Find successor operations via dependency graph
243
+ const successorDeps = await erpDb.operationDependency.findMany({
244
+ where: { predecessorId: operationId },
245
+ select: { successorId: true },
246
+ });
247
+ if (successorDeps.length === 0)
248
+ return;
249
+ for (const { successorId } of successorDeps) {
250
+ // Only unblock if the successor op run is currently blocked
251
+ const successorRun = await erpDb.operationRun.findFirst({
252
+ where: {
253
+ orderRunId: runId,
254
+ operationId: successorId,
255
+ status: OperationRunStatusValues.blocked,
256
+ },
257
+ });
258
+ if (!successorRun)
259
+ continue;
260
+ // Check if ALL predecessors of this successor are complete
261
+ const allPredDeps = await erpDb.operationDependency.findMany({
262
+ where: { successorId },
263
+ select: { predecessorId: true },
264
+ });
265
+ const predIds = allPredDeps.map((d) => d.predecessorId);
266
+ const incompleteCount = await erpDb.operationRun.count({
267
+ where: {
268
+ orderRunId: runId,
269
+ operationId: { in: predIds },
270
+ status: {
271
+ notIn: [
272
+ OperationRunStatusValues.completed,
273
+ OperationRunStatusValues.skipped,
274
+ ],
275
+ },
276
+ },
277
+ });
278
+ if (incompleteCount === 0) {
279
+ await erpDb.operationRun.update({
280
+ where: { id: successorRun.id },
281
+ data: {
282
+ status: OperationRunStatusValues.pending,
283
+ updatedById: userId,
284
+ },
285
+ });
286
+ }
287
+ }
288
+ }
289
+ /**
290
+ * After reopening an operation, re-block successor ops that are still pending
291
+ * (haven't been started yet) if this operation is one of their prerequisites.
292
+ */
293
+ export async function reblockSuccessors(runId, operationId, userId) {
294
+ const successorDeps = await erpDb.operationDependency.findMany({
295
+ where: { predecessorId: operationId },
296
+ select: { successorId: true },
297
+ });
298
+ if (successorDeps.length === 0)
299
+ return;
300
+ for (const { successorId } of successorDeps) {
301
+ // Only re-block if successor is still pending (not started/in_progress/etc.)
302
+ await erpDb.operationRun.updateMany({
303
+ where: {
304
+ orderRunId: runId,
305
+ operationId: successorId,
306
+ status: OperationRunStatusValues.pending,
307
+ },
308
+ data: {
309
+ status: OperationRunStatusValues.blocked,
310
+ updatedById: userId,
311
+ },
312
+ });
313
+ }
314
+ }
315
+ export async function checkStepsComplete(opRunId) {
316
+ const incompleteSteps = await erpDb.stepRun.findMany({
317
+ where: { operationRunId: opRunId, completed: false },
318
+ include: { step: { select: { seqNo: true, title: true } } },
319
+ });
320
+ if (incompleteSteps.length === 0)
321
+ return null;
322
+ const labels = incompleteSteps.map((s) => `Step ${s.step.seqNo}${s.step.title ? ` "${s.step.title}"` : ""}`);
323
+ return `Cannot complete operation: incomplete steps — ${labels.join(", ")}`;
324
+ }
325
+ // --- Mutations ---
326
+ export async function updateOpRun(id, data, userId) {
327
+ const updateData = { updatedById: userId };
328
+ if (data.assignedToId !== undefined)
329
+ updateData.assignedToId = data.assignedToId;
330
+ return erpDb.operationRun.update({
331
+ where: { id },
332
+ data: updateData,
333
+ include: includeOp,
334
+ });
335
+ }
336
+ export async function transitionStatus(id, action, fromStatus, toStatus, userId, extraData) {
337
+ return erpDb.$transaction(async (erpTx) => {
338
+ const updated = await erpTx.operationRun.update({
339
+ where: { id },
340
+ data: { status: toStatus, updatedById: userId, ...extraData },
341
+ include: includeOp,
342
+ });
343
+ await writeAuditEntry(erpTx, "OperationRun", id, action, "status", fromStatus, toStatus, userId);
344
+ return updated;
345
+ });
346
+ }
347
+ //# sourceMappingURL=operation-run-service.js.map
@@ -0,0 +1,47 @@
1
+ import type { OperationModel } from "../generated/prisma/models/Operation.js";
2
+ import { type WithAuditUsers } from "../route-helpers.js";
3
+ export type OperationWithUsers = OperationModel & WithAuditUsers & {
4
+ workCenter?: {
5
+ key: string;
6
+ } | null;
7
+ };
8
+ export type OperationWithSummary = OperationWithUsers & {
9
+ _count: {
10
+ steps: number;
11
+ };
12
+ predecessors: Array<{
13
+ predecessor: {
14
+ seqNo: number;
15
+ title: string;
16
+ };
17
+ }>;
18
+ };
19
+ export declare function listOperations(orderRevId: number): Promise<OperationWithSummary[]>;
20
+ export type OperationWithStepSummary = OperationWithUsers & {
21
+ steps: Array<{
22
+ seqNo: number;
23
+ title: string;
24
+ }>;
25
+ };
26
+ export declare function getOperation(orderRevId: number, seqNo: number): Promise<OperationWithStepSummary | null>;
27
+ export declare function findExisting(orderRevId: number, seqNo: number): Promise<{
28
+ title: string;
29
+ description: string;
30
+ createdAt: Date;
31
+ id: number;
32
+ updatedAt: Date;
33
+ seqNo: number;
34
+ orderRevId: number;
35
+ workCenterId: number | null;
36
+ createdById: number;
37
+ updatedById: number;
38
+ } | null>;
39
+ export declare function createOperation(orderRevId: number, requestedSeqNo: number | undefined, title: string, description: string | undefined, workCenterId: number | null | undefined, predecessorSeqNos: number[] | undefined, userId: number): Promise<OperationWithSummary>;
40
+ export declare function updateOperation(id: number, data: {
41
+ title?: string;
42
+ description?: string;
43
+ workCenterId?: number | null;
44
+ seqNo?: number;
45
+ }, userId: number): Promise<OperationWithUsers>;
46
+ export declare function deleteOperation(id: number): Promise<void>;
47
+ //# sourceMappingURL=operation-service.d.ts.map
@@ -0,0 +1,132 @@
1
+ import erpDb from "../erpDb.js";
2
+ import { calcNextSeqNo, includeUsers, } from "../route-helpers.js";
3
+ // --- Prisma include & result type ---
4
+ const includeWorkCenter = {
5
+ workCenter: { select: { key: true } },
6
+ };
7
+ // --- Lookups ---
8
+ export async function listOperations(orderRevId) {
9
+ return erpDb.operation.findMany({
10
+ where: { orderRevId },
11
+ include: {
12
+ ...includeUsers,
13
+ ...includeWorkCenter,
14
+ _count: { select: { steps: true } },
15
+ predecessors: {
16
+ include: { predecessor: { select: { seqNo: true, title: true } } },
17
+ orderBy: { predecessor: { seqNo: "asc" } },
18
+ },
19
+ },
20
+ orderBy: { seqNo: "asc" },
21
+ });
22
+ }
23
+ export async function getOperation(orderRevId, seqNo) {
24
+ return erpDb.operation.findFirst({
25
+ where: { orderRevId, seqNo },
26
+ include: {
27
+ ...includeUsers,
28
+ ...includeWorkCenter,
29
+ steps: {
30
+ select: { seqNo: true, title: true },
31
+ orderBy: { seqNo: "asc" },
32
+ },
33
+ },
34
+ });
35
+ }
36
+ export async function findExisting(orderRevId, seqNo) {
37
+ return erpDb.operation.findFirst({
38
+ where: { orderRevId, seqNo },
39
+ });
40
+ }
41
+ // --- Mutations ---
42
+ export async function createOperation(orderRevId, requestedSeqNo, title, description, workCenterId, predecessorSeqNos, userId) {
43
+ return erpDb.$transaction(async (erpTx) => {
44
+ const maxSeq = await erpTx.operation.findFirst({
45
+ where: { orderRevId },
46
+ orderBy: { seqNo: "desc" },
47
+ select: { seqNo: true },
48
+ });
49
+ const defaultSeqNo = calcNextSeqNo(maxSeq?.seqNo ?? 0);
50
+ const nextSeqNo = requestedSeqNo ?? defaultSeqNo;
51
+ const created = await erpTx.operation.create({
52
+ data: {
53
+ orderRevId,
54
+ seqNo: nextSeqNo,
55
+ title,
56
+ description: description ?? "",
57
+ ...(workCenterId !== undefined ? { workCenterId } : {}),
58
+ createdById: userId,
59
+ updatedById: userId,
60
+ },
61
+ });
62
+ if (predecessorSeqNos !== undefined) {
63
+ // Use explicitly provided predecessors
64
+ for (const predSeqNo of predecessorSeqNos) {
65
+ const predOp = await erpTx.operation.findFirst({
66
+ where: { orderRevId, seqNo: predSeqNo },
67
+ select: { id: true },
68
+ });
69
+ if (predOp) {
70
+ await erpTx.operationDependency.create({
71
+ data: {
72
+ successorId: created.id,
73
+ predecessorId: predOp.id,
74
+ createdById: userId,
75
+ },
76
+ });
77
+ }
78
+ }
79
+ }
80
+ else {
81
+ // Auto-create dependency on the previous operation (by seqNo)
82
+ const previousOp = await erpTx.operation.findFirst({
83
+ where: { orderRevId, seqNo: { lt: nextSeqNo } },
84
+ orderBy: { seqNo: "desc" },
85
+ select: { id: true },
86
+ });
87
+ if (previousOp) {
88
+ await erpTx.operationDependency.create({
89
+ data: {
90
+ successorId: created.id,
91
+ predecessorId: previousOp.id,
92
+ createdById: userId,
93
+ },
94
+ });
95
+ }
96
+ }
97
+ // Re-fetch with summary data (predecessors + step count)
98
+ return erpTx.operation.findUniqueOrThrow({
99
+ where: { id: created.id },
100
+ include: {
101
+ ...includeUsers,
102
+ ...includeWorkCenter,
103
+ _count: { select: { steps: true } },
104
+ predecessors: {
105
+ include: { predecessor: { select: { seqNo: true, title: true } } },
106
+ orderBy: { predecessor: { seqNo: "asc" } },
107
+ },
108
+ },
109
+ });
110
+ });
111
+ }
112
+ export async function updateOperation(id, data, userId) {
113
+ return erpDb.operation.update({
114
+ where: { id },
115
+ data: {
116
+ ...(data.title !== undefined ? { title: data.title } : {}),
117
+ ...(data.description !== undefined
118
+ ? { description: data.description }
119
+ : {}),
120
+ ...(data.workCenterId !== undefined
121
+ ? { workCenterId: data.workCenterId }
122
+ : {}),
123
+ ...(data.seqNo !== undefined ? { seqNo: data.seqNo } : {}),
124
+ updatedById: userId,
125
+ },
126
+ include: { ...includeUsers, ...includeWorkCenter },
127
+ });
128
+ }
129
+ export async function deleteOperation(id) {
130
+ await erpDb.operation.delete({ where: { id } });
131
+ }
132
+ //# sourceMappingURL=operation-service.js.map
@@ -0,0 +1,53 @@
1
+ import { type RevisionStatus } from "@naisys/erp-shared";
2
+ import type { OrderRevisionModel } from "../generated/prisma/models/OrderRevision.js";
3
+ import { type WithAuditUsers } from "../route-helpers.js";
4
+ export type OrderRevisionWithRelations = OrderRevisionModel & WithAuditUsers & {
5
+ order: {
6
+ item: {
7
+ key: string;
8
+ } | null;
9
+ };
10
+ };
11
+ export declare function getRevisionOpSummary(orderRevId: number): Promise<{
12
+ title: string;
13
+ seqNo: number;
14
+ }[]>;
15
+ export declare function listRevisions(orderId: number, where: Record<string, unknown>, page: number, pageSize: number): Promise<[OrderRevisionWithRelations[], number]>;
16
+ export declare function getRevision(orderId: number, revNo: number): Promise<OrderRevisionWithRelations | null>;
17
+ export declare function findExisting(orderId: number, revNo: number): Promise<({
18
+ order: {
19
+ item: {
20
+ key: string;
21
+ } | null;
22
+ };
23
+ createdBy: {
24
+ username: string;
25
+ };
26
+ updatedBy: {
27
+ username: string;
28
+ };
29
+ } & {
30
+ description: string;
31
+ createdAt: Date;
32
+ id: number;
33
+ updatedAt: Date;
34
+ status: import("../generated/prisma/enums.js").RevisionStatus;
35
+ changeSummary: string | null;
36
+ revNo: number;
37
+ createdById: number;
38
+ updatedById: number;
39
+ orderId: number;
40
+ }) | null>;
41
+ export declare function validateDraftStatus(status: string): string | null;
42
+ export declare function checkHasOrderRuns(revisionId: number): Promise<string | null>;
43
+ export declare function createRevision(orderId: number, data: {
44
+ description?: string;
45
+ changeSummary?: string | null;
46
+ }, userId: number): Promise<OrderRevisionWithRelations>;
47
+ export declare function updateRevision(id: number, data: {
48
+ description?: string;
49
+ changeSummary?: string | null;
50
+ }, userId: number): Promise<OrderRevisionWithRelations>;
51
+ export declare function deleteRevision(id: number): Promise<void>;
52
+ export declare function transitionStatus(id: number, action: string, fromStatus: RevisionStatus, toStatus: RevisionStatus, userId: number): Promise<OrderRevisionWithRelations>;
53
+ //# sourceMappingURL=order-revision-service.d.ts.map