@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,335 @@
1
+ import { CreateOrderRunSchema, ErrorResponseSchema, MutateResponseSchema, OrderRunListQuerySchema, OrderRunListResponseSchema, OrderRunSchema, OrderRunStatus, RunCreateResponseSchema, UpdateOrderRunSchema, } from "@naisys/erp-shared";
2
+ import { z } from "zod/v4";
3
+ import { hasPermission, requirePermission } from "../auth-middleware.js";
4
+ import { conflict, notFound } from "../error-handler.js";
5
+ import { API_PREFIX, paginationLinks } from "../hateoas.js";
6
+ import { childItemLinks, formatAuditFields, mutationResult, resolveActions, resolveOrder, resolveOrderRun, } from "../route-helpers.js";
7
+ import { checkOpsComplete, createOrderRun, deleteOrderRun, findLatestApprovedRevision, findOrderRevision, getOrderRun, getOrderRunOpSummary, listOrderRuns, updateOrderRun, validateStatusFor, } from "../services/order-run-service.js";
8
+ function runResource(orderKey) {
9
+ return `orders/${orderKey}/runs`;
10
+ }
11
+ export async function orderRunItemActions(orderKey, runNo, runId, status, itemKey, user) {
12
+ const href = `${API_PREFIX}/${runResource(orderKey)}/${runNo}`;
13
+ const isExecutor = hasPermission(user, "order_executor");
14
+ const opsErr = isExecutor && status === OrderRunStatus.started
15
+ ? await checkOpsComplete(runId)
16
+ : null;
17
+ return resolveActions([
18
+ {
19
+ rel: "start",
20
+ path: "/start",
21
+ method: "POST",
22
+ title: "Start",
23
+ permission: "order_executor",
24
+ statuses: [OrderRunStatus.released],
25
+ },
26
+ {
27
+ rel: "complete",
28
+ path: "/complete",
29
+ method: "POST",
30
+ title: "Complete",
31
+ schema: `${API_PREFIX}/schemas/CompleteOrderRun`,
32
+ permission: "order_executor",
33
+ statuses: [OrderRunStatus.started],
34
+ visibleWhen: (ctx) => !!ctx.itemKey,
35
+ disabledWhen: (ctx) => ctx.opsErr,
36
+ },
37
+ {
38
+ rel: "close",
39
+ path: "/close",
40
+ method: "POST",
41
+ title: "Close",
42
+ permission: "order_executor",
43
+ statuses: [OrderRunStatus.started],
44
+ visibleWhen: (ctx) => !ctx.itemKey,
45
+ disabledWhen: (ctx) => ctx.opsErr,
46
+ },
47
+ {
48
+ rel: "update",
49
+ method: "PUT",
50
+ title: "Update",
51
+ schema: `${API_PREFIX}/schemas/UpdateOrderRun`,
52
+ permission: "order_manager",
53
+ statuses: [OrderRunStatus.released, OrderRunStatus.started],
54
+ },
55
+ {
56
+ rel: "cancel",
57
+ path: "/cancel",
58
+ method: "POST",
59
+ title: "Cancel",
60
+ permission: "order_manager",
61
+ statuses: [OrderRunStatus.released, OrderRunStatus.started],
62
+ },
63
+ {
64
+ rel: "delete",
65
+ method: "DELETE",
66
+ title: "Delete",
67
+ permission: "order_manager",
68
+ statuses: [OrderRunStatus.released],
69
+ },
70
+ {
71
+ rel: "reopen",
72
+ path: "/reopen",
73
+ method: "POST",
74
+ title: "Reopen",
75
+ permission: "order_manager",
76
+ statuses: [OrderRunStatus.closed, OrderRunStatus.cancelled],
77
+ },
78
+ ], href, { status, user, itemKey, opsErr });
79
+ }
80
+ const OrderKeyParamsSchema = z.object({
81
+ orderKey: z.string(),
82
+ });
83
+ export const RunNoParamsSchema = z.object({
84
+ orderKey: z.string(),
85
+ runNo: z.coerce.number().int(),
86
+ });
87
+ export async function formatRun(orderKey, user, run) {
88
+ const itemKey = run.order?.item?.key ?? null;
89
+ const instance = run.itemInstances[0] ?? null;
90
+ const links = [
91
+ ...childItemLinks("/" + runResource(orderKey), run.runNo, "Runs", "/orders/" + orderKey, "Order", "OrderRun", "order"),
92
+ {
93
+ rel: "operations",
94
+ href: `${API_PREFIX}/${runResource(orderKey)}/${run.runNo}/ops`,
95
+ title: "Operation Runs",
96
+ },
97
+ ];
98
+ if (itemKey) {
99
+ links.push({
100
+ rel: "completion-fields",
101
+ href: `${API_PREFIX}/items/${itemKey}/fields`,
102
+ title: "Completion Fields",
103
+ });
104
+ }
105
+ if (instance) {
106
+ links.push({
107
+ rel: "itemInstance",
108
+ href: `${API_PREFIX}/items/${itemKey}/instances/${instance.id}`,
109
+ title: "Item Instance",
110
+ });
111
+ }
112
+ const opSummaryRows = await getOrderRunOpSummary(run.id);
113
+ return {
114
+ id: run.id,
115
+ runNo: run.runNo,
116
+ orderId: run.orderId,
117
+ orderKey,
118
+ revNo: run.orderRev.revNo,
119
+ itemKey,
120
+ instanceId: instance?.id ?? null,
121
+ instanceKey: instance?.key ?? null,
122
+ status: run.status,
123
+ priority: run.priority,
124
+ cost: run.cost,
125
+ dueAt: run.dueAt,
126
+ releaseNote: run.releaseNote,
127
+ operationSummary: opSummaryRows.map((r) => ({
128
+ seqNo: r.operation.seqNo,
129
+ title: r.operation.title,
130
+ status: r.status,
131
+ })),
132
+ ...formatAuditFields(run),
133
+ _links: links,
134
+ _actions: await orderRunItemActions(orderKey, run.runNo, run.id, run.status, itemKey, user),
135
+ };
136
+ }
137
+ function formatListRun(orderKey, run) {
138
+ const itemKey = run.order?.item?.key ?? null;
139
+ const instance = run.itemInstances[0] ?? null;
140
+ return {
141
+ id: run.id,
142
+ runNo: run.runNo,
143
+ orderId: run.orderId,
144
+ orderKey,
145
+ revNo: run.orderRev.revNo,
146
+ itemKey,
147
+ instanceId: instance?.id ?? null,
148
+ instanceKey: instance?.key ?? null,
149
+ status: run.status,
150
+ priority: run.priority,
151
+ cost: run.cost,
152
+ dueAt: run.dueAt,
153
+ releaseNote: run.releaseNote,
154
+ ...formatAuditFields(run),
155
+ };
156
+ }
157
+ export default function orderRunRoutes(fastify) {
158
+ const app = fastify.withTypeProvider();
159
+ // LIST
160
+ app.get("/", {
161
+ schema: {
162
+ description: "List order runs for an order",
163
+ tags: ["Order Runs"],
164
+ params: OrderKeyParamsSchema,
165
+ querystring: OrderRunListQuerySchema,
166
+ response: {
167
+ 200: OrderRunListResponseSchema,
168
+ 404: ErrorResponseSchema,
169
+ },
170
+ },
171
+ handler: async (request, reply) => {
172
+ const { orderKey } = request.params;
173
+ const { page, pageSize, status, priority, search } = request.query;
174
+ const order = await resolveOrder(orderKey);
175
+ if (!order) {
176
+ return notFound(reply, `Order '${orderKey}' not found`);
177
+ }
178
+ const where = { orderId: order.id };
179
+ if (status)
180
+ where.status = status;
181
+ if (priority)
182
+ where.priority = priority;
183
+ if (search) {
184
+ where.OR = [{ releaseNote: { contains: search } }];
185
+ }
186
+ const { items, total } = await listOrderRuns(where, page, pageSize);
187
+ const resource = runResource(orderKey);
188
+ return {
189
+ items: items.map((run) => formatListRun(orderKey, run)),
190
+ total,
191
+ page,
192
+ pageSize,
193
+ _links: paginationLinks(resource, page, pageSize, total, {
194
+ status,
195
+ priority,
196
+ search,
197
+ }),
198
+ _linkTemplates: [
199
+ {
200
+ rel: "item",
201
+ hrefTemplate: `${API_PREFIX}/orders/${orderKey}/runs/{runNo}`,
202
+ },
203
+ ],
204
+ };
205
+ },
206
+ });
207
+ // CREATE (cut order)
208
+ app.post("/", {
209
+ schema: {
210
+ description: "Create a new order run for an order",
211
+ tags: ["Order Runs"],
212
+ params: OrderKeyParamsSchema,
213
+ body: CreateOrderRunSchema,
214
+ response: {
215
+ 201: RunCreateResponseSchema,
216
+ 404: ErrorResponseSchema,
217
+ },
218
+ },
219
+ preHandler: requirePermission("order_manager"),
220
+ handler: async (request, reply) => {
221
+ const { orderKey } = request.params;
222
+ const { revNo, priority, dueAt, releaseNote } = request.body;
223
+ const userId = request.erpUser.id;
224
+ const order = await resolveOrder(orderKey);
225
+ if (!order) {
226
+ return notFound(reply, `Order '${orderKey}' not found`);
227
+ }
228
+ const orderId = order.id;
229
+ // Resolve revision: explicit revNo or latest approved
230
+ const orderRev = revNo
231
+ ? await findOrderRevision(orderId, revNo)
232
+ : await findLatestApprovedRevision(orderId);
233
+ if (!orderRev) {
234
+ return notFound(reply, revNo
235
+ ? `Order revision ${revNo} not found for order '${orderKey}'`
236
+ : `No approved revision found for order '${orderKey}'`);
237
+ }
238
+ const run = await createOrderRun(orderId, orderRev.id, { priority, dueAt, releaseNote }, userId);
239
+ const full = await formatRun(orderKey, request.erpUser, run);
240
+ reply.status(201);
241
+ return mutationResult(request, reply, full, {
242
+ id: full.id,
243
+ runNo: full.runNo,
244
+ _links: full._links,
245
+ _actions: full._actions,
246
+ });
247
+ },
248
+ });
249
+ // GET by runNo
250
+ app.get("/:runNo", {
251
+ schema: {
252
+ description: "Get a single order run by run number",
253
+ tags: ["Order Runs"],
254
+ params: RunNoParamsSchema,
255
+ response: {
256
+ 200: OrderRunSchema,
257
+ 404: ErrorResponseSchema,
258
+ },
259
+ },
260
+ handler: async (request, reply) => {
261
+ const { orderKey, runNo } = request.params;
262
+ const resolved = await resolveOrderRun(orderKey, runNo);
263
+ if (!resolved) {
264
+ return notFound(reply, `Order run not found for order '${orderKey}'`);
265
+ }
266
+ const run = await getOrderRun(resolved.run.id);
267
+ if (!run) {
268
+ return notFound(reply, `Order run not found`);
269
+ }
270
+ return formatRun(orderKey, request.erpUser, run);
271
+ },
272
+ });
273
+ // UPDATE (released/started only)
274
+ app.put("/:runNo", {
275
+ schema: {
276
+ description: "Update an order run (released or started status only)",
277
+ tags: ["Order Runs"],
278
+ params: RunNoParamsSchema,
279
+ body: UpdateOrderRunSchema,
280
+ response: {
281
+ 200: MutateResponseSchema,
282
+ 404: ErrorResponseSchema,
283
+ 409: ErrorResponseSchema,
284
+ },
285
+ },
286
+ preHandler: requirePermission("order_manager"),
287
+ handler: async (request, reply) => {
288
+ const { orderKey, runNo } = request.params;
289
+ const data = request.body;
290
+ const userId = request.erpUser.id;
291
+ const resolved = await resolveOrderRun(orderKey, runNo);
292
+ if (!resolved) {
293
+ return notFound(reply, `Order run not found for order '${orderKey}'`);
294
+ }
295
+ const statusErr = validateStatusFor("update", resolved.run.status, [
296
+ OrderRunStatus.released,
297
+ OrderRunStatus.started,
298
+ ]);
299
+ if (statusErr)
300
+ return conflict(reply, statusErr);
301
+ const run = await updateOrderRun(resolved.run.id, data, userId);
302
+ const full = await formatRun(orderKey, request.erpUser, run);
303
+ return mutationResult(request, reply, full, {
304
+ _actions: full._actions,
305
+ });
306
+ },
307
+ });
308
+ // DELETE (released only)
309
+ app.delete("/:runNo", {
310
+ schema: {
311
+ description: "Delete an order run (released status only)",
312
+ tags: ["Order Runs"],
313
+ params: RunNoParamsSchema,
314
+ response: {
315
+ 204: z.void(),
316
+ 404: ErrorResponseSchema,
317
+ 409: ErrorResponseSchema,
318
+ },
319
+ },
320
+ preHandler: requirePermission("order_manager"),
321
+ handler: async (request, reply) => {
322
+ const { orderKey, runNo } = request.params;
323
+ const resolved = await resolveOrderRun(orderKey, runNo);
324
+ if (!resolved) {
325
+ return notFound(reply, `Order run not found for order '${orderKey}'`);
326
+ }
327
+ if (resolved.run.status !== OrderRunStatus.released) {
328
+ return conflict(reply, `Cannot delete order run in ${resolved.run.status} status`);
329
+ }
330
+ await deleteOrderRun(resolved.run.id);
331
+ reply.status(204);
332
+ },
333
+ });
334
+ }
335
+ //# sourceMappingURL=order-runs.js.map
@@ -0,0 +1,3 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export default function orderRoutes(fastify: FastifyInstance): void;
3
+ //# sourceMappingURL=orders.d.ts.map
@@ -0,0 +1,262 @@
1
+ import { CreateOrderSchema, ErrorResponseSchema, KeyCreateResponseSchema, MutateResponseSchema, OrderListQuerySchema, OrderListResponseSchema, OrderSchema, OrderStatus, UpdateOrderSchema, } from "@naisys/erp-shared";
2
+ import { z } from "zod/v4";
3
+ import { hasPermission, requirePermission } from "../auth-middleware.js";
4
+ import { conflict, notFound } from "../error-handler.js";
5
+ import { API_PREFIX, collectionLink, paginationLinks, schemaLink, selfLink, } from "../hateoas.js";
6
+ import { formatAuditFields, mutationResult, permGate, resolveActions, } from "../route-helpers.js";
7
+ import { checkHasRevisions, createOrder, deleteOrder, findExisting, listOrders, resolveItemKey, updateOrder, } from "../services/order-service.js";
8
+ function orderLinks(resource, key, schemaName) {
9
+ return [
10
+ selfLink(`/${resource}/${key}`),
11
+ collectionLink(resource),
12
+ schemaLink(schemaName),
13
+ ];
14
+ }
15
+ function orderActions(resource, key, status, user) {
16
+ const href = `${API_PREFIX}/${resource}/${key}`;
17
+ return resolveActions([
18
+ {
19
+ rel: "update",
20
+ method: "PUT",
21
+ title: "Update",
22
+ schema: `${API_PREFIX}/schemas/UpdateOrder`,
23
+ permission: "order_planner",
24
+ },
25
+ {
26
+ rel: "archive",
27
+ method: "PUT",
28
+ title: "Archive",
29
+ body: { status: OrderStatus.archived },
30
+ permission: "order_planner",
31
+ statuses: [OrderStatus.active],
32
+ },
33
+ {
34
+ rel: "activate",
35
+ method: "PUT",
36
+ title: "Activate",
37
+ body: { status: OrderStatus.active },
38
+ permission: "order_planner",
39
+ statuses: [OrderStatus.archived],
40
+ },
41
+ {
42
+ rel: "delete",
43
+ method: "DELETE",
44
+ title: "Delete",
45
+ permission: "order_manager",
46
+ },
47
+ ], href, { status, user });
48
+ }
49
+ function revisionCollectionLink(parentResource, key) {
50
+ return {
51
+ rel: "revisions",
52
+ href: `${API_PREFIX}/${parentResource}/${key}/revs`,
53
+ title: "Revisions",
54
+ };
55
+ }
56
+ function runsCollectionLink(parentResource, key) {
57
+ return {
58
+ rel: "runs",
59
+ href: `${API_PREFIX}/${parentResource}/${key}/runs`,
60
+ title: "Order Runs",
61
+ };
62
+ }
63
+ const RESOURCE = "orders";
64
+ const KeyParamsSchema = z.object({
65
+ key: z.string(),
66
+ });
67
+ function formatOrder(order, user) {
68
+ return {
69
+ id: order.id,
70
+ key: order.key,
71
+ description: order.description,
72
+ status: order.status,
73
+ itemKey: order.item?.key ?? null,
74
+ ...formatAuditFields(order),
75
+ _links: [
76
+ ...orderLinks(RESOURCE, order.key, "Order"),
77
+ revisionCollectionLink(RESOURCE, order.key),
78
+ runsCollectionLink(RESOURCE, order.key),
79
+ ],
80
+ _actions: orderActions(RESOURCE, order.key, order.status, user),
81
+ };
82
+ }
83
+ function formatListOrder(order, user) {
84
+ const { _actions, ...rest } = formatOrder(order, user);
85
+ const { _links: _, ...withoutLinks } = rest;
86
+ return withoutLinks;
87
+ }
88
+ export default function orderRoutes(fastify) {
89
+ const app = fastify.withTypeProvider();
90
+ // LIST
91
+ app.get("/", {
92
+ schema: {
93
+ description: "List orders with pagination and filtering",
94
+ tags: ["Orders"],
95
+ querystring: OrderListQuerySchema,
96
+ response: {
97
+ 200: OrderListResponseSchema,
98
+ },
99
+ },
100
+ handler: async (request) => {
101
+ const { page, pageSize, status, search } = request.query;
102
+ const where = {};
103
+ if (status)
104
+ where.status = status;
105
+ if (search) {
106
+ where.OR = [
107
+ { key: { contains: search } },
108
+ { description: { contains: search } },
109
+ ];
110
+ }
111
+ const [items, total] = await listOrders(where, page, pageSize);
112
+ return {
113
+ items: items.map((order) => formatListOrder(order, request.erpUser)),
114
+ total,
115
+ page,
116
+ pageSize,
117
+ _links: paginationLinks(RESOURCE, page, pageSize, total, {
118
+ status,
119
+ search,
120
+ }),
121
+ _linkTemplates: [
122
+ { rel: "item", hrefTemplate: `${API_PREFIX}/orders/{key}` },
123
+ ],
124
+ _actions: [
125
+ {
126
+ rel: "create",
127
+ href: `${API_PREFIX}/${RESOURCE}`,
128
+ method: "POST",
129
+ title: "Create Order",
130
+ schema: `${API_PREFIX}/schemas/CreateOrder`,
131
+ ...permGate(hasPermission(request.erpUser, "order_planner"), "order_planner"),
132
+ },
133
+ ],
134
+ };
135
+ },
136
+ });
137
+ // CREATE
138
+ app.post("/", {
139
+ schema: {
140
+ description: "Create a new order",
141
+ tags: ["Orders"],
142
+ body: CreateOrderSchema,
143
+ response: {
144
+ 201: KeyCreateResponseSchema,
145
+ 404: ErrorResponseSchema,
146
+ },
147
+ },
148
+ preHandler: requirePermission("order_planner"),
149
+ handler: async (request, reply) => {
150
+ const { key, description, itemKey } = request.body;
151
+ const userId = request.erpUser.id;
152
+ let itemId = null;
153
+ if (itemKey) {
154
+ try {
155
+ itemId = await resolveItemKey(itemKey);
156
+ }
157
+ catch {
158
+ return notFound(reply, `Item '${itemKey}' not found`);
159
+ }
160
+ }
161
+ const order = await createOrder(key, description, itemId, userId);
162
+ const full = formatOrder(order, request.erpUser);
163
+ reply.status(201);
164
+ return mutationResult(request, reply, full, {
165
+ id: full.id,
166
+ key: full.key,
167
+ _links: full._links,
168
+ _actions: full._actions,
169
+ });
170
+ },
171
+ });
172
+ // GET by key
173
+ app.get("/:key", {
174
+ schema: {
175
+ description: "Get a single order by key",
176
+ tags: ["Orders"],
177
+ params: KeyParamsSchema,
178
+ response: {
179
+ 200: OrderSchema,
180
+ 404: ErrorResponseSchema,
181
+ },
182
+ },
183
+ handler: async (request, reply) => {
184
+ const { key } = request.params;
185
+ const order = await findExisting(key);
186
+ if (!order) {
187
+ return notFound(reply, `Order '${key}' not found`);
188
+ }
189
+ return formatOrder(order, request.erpUser);
190
+ },
191
+ });
192
+ // UPDATE
193
+ app.put("/:key", {
194
+ schema: {
195
+ description: "Update an order",
196
+ tags: ["Orders"],
197
+ params: KeyParamsSchema,
198
+ body: UpdateOrderSchema,
199
+ response: {
200
+ 200: MutateResponseSchema,
201
+ 404: ErrorResponseSchema,
202
+ },
203
+ },
204
+ preHandler: requirePermission("order_planner"),
205
+ handler: async (request, reply) => {
206
+ const { key } = request.params;
207
+ const { itemKey, ...rest } = request.body;
208
+ const userId = request.erpUser.id;
209
+ const existing = await findExisting(key);
210
+ if (!existing) {
211
+ return notFound(reply, `Order '${key}' not found`);
212
+ }
213
+ const dbData = { ...rest };
214
+ if (itemKey !== undefined) {
215
+ if (itemKey === null) {
216
+ dbData.itemId = null;
217
+ }
218
+ else {
219
+ try {
220
+ dbData.itemId = await resolveItemKey(itemKey);
221
+ }
222
+ catch {
223
+ return notFound(reply, `Item '${itemKey}' not found`);
224
+ }
225
+ }
226
+ }
227
+ const order = await updateOrder(key, dbData, userId);
228
+ const full = formatOrder(order, request.erpUser);
229
+ return mutationResult(request, reply, full, {
230
+ _actions: full._actions,
231
+ });
232
+ },
233
+ });
234
+ // DELETE
235
+ app.delete("/:key", {
236
+ schema: {
237
+ description: "Delete an order",
238
+ tags: ["Orders"],
239
+ params: KeyParamsSchema,
240
+ response: {
241
+ 204: z.void(),
242
+ 404: ErrorResponseSchema,
243
+ 409: ErrorResponseSchema,
244
+ },
245
+ },
246
+ preHandler: requirePermission("order_manager"),
247
+ handler: async (request, reply) => {
248
+ const { key } = request.params;
249
+ const existing = await findExisting(key);
250
+ if (!existing) {
251
+ return notFound(reply, `Order '${key}' not found`);
252
+ }
253
+ const hasRevisions = await checkHasRevisions(existing.id);
254
+ if (hasRevisions) {
255
+ return conflict(reply, "Cannot delete order with existing revisions. Archive it instead.");
256
+ }
257
+ await deleteOrder(key);
258
+ reply.status(204);
259
+ },
260
+ });
261
+ }
262
+ //# sourceMappingURL=orders.js.map
@@ -0,0 +1,3 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export default function rootRoute(fastify: FastifyInstance): void;
3
+ //# sourceMappingURL=root.d.ts.map