@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,283 @@
1
+ import { CreateOperationSchema, ErrorResponseSchema, MutateResponseSchema, OperationListResponseSchema, OperationSchema, RevisionStatus, SeqNoCreateResponseSchema, UpdateOperationSchema, } 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, selfLink } from "../hateoas.js";
6
+ import { calcNextSeqNo, childItemLinks, draftCrudActions, formatAuditFields, mutationResult, permGate, resolveRevision, } from "../route-helpers.js";
7
+ import { createOperation, deleteOperation, findExisting, getOperation, listOperations, updateOperation, } from "../services/operation-service.js";
8
+ import { findWorkCenterByKey } from "../services/work-center-service.js";
9
+ /** Resolve an optional workCenterKey to a workCenterId. Returns undefined (skip), null (clear), or the ID. */
10
+ async function resolveWorkCenterId(workCenterKey) {
11
+ if (workCenterKey === undefined)
12
+ return { id: undefined };
13
+ if (workCenterKey === null)
14
+ return { id: null };
15
+ const wc = await findWorkCenterByKey(workCenterKey);
16
+ if (!wc)
17
+ return { error: `Work center '${workCenterKey}' not found` };
18
+ return { id: wc.id };
19
+ }
20
+ const ParamsSchema = z.object({
21
+ orderKey: z.string(),
22
+ revNo: z.coerce.number().int(),
23
+ });
24
+ const OpParamsSchema = z.object({
25
+ orderKey: z.string(),
26
+ revNo: z.coerce.number().int(),
27
+ seqNo: z.coerce.number().int(),
28
+ });
29
+ function opBasePath(orderKey, revNo) {
30
+ return `/orders/${orderKey}/revs/${revNo}/ops`;
31
+ }
32
+ function formatOperation(orderKey, revNo, revStatus, user, operation, summary) {
33
+ const base = opBasePath(orderKey, revNo);
34
+ return {
35
+ id: operation.id,
36
+ orderRevId: operation.orderRevId,
37
+ seqNo: operation.seqNo,
38
+ title: operation.title,
39
+ description: operation.description,
40
+ workCenterKey: operation.workCenter?.key ?? null,
41
+ ...(summary?.stepCount !== undefined
42
+ ? { stepCount: summary.stepCount }
43
+ : {}),
44
+ ...(summary?.stepSummary ? { stepSummary: summary.stepSummary } : {}),
45
+ ...(summary?.predecessors ? { predecessors: summary.predecessors } : {}),
46
+ ...formatAuditFields(operation),
47
+ _links: [
48
+ ...childItemLinks(base, operation.seqNo, "Operations", `/orders/${orderKey}/revs/${revNo}`, "Revision", "Operation"),
49
+ {
50
+ rel: "steps",
51
+ href: `${API_PREFIX}${base}/${operation.seqNo}/steps`,
52
+ title: "Steps",
53
+ },
54
+ {
55
+ rel: "dependencies",
56
+ href: `${API_PREFIX}${base}/${operation.seqNo}/deps`,
57
+ title: "Dependencies",
58
+ },
59
+ ...(operation.workCenter
60
+ ? [
61
+ {
62
+ rel: "workCenter",
63
+ href: `${API_PREFIX}/work-centers/${operation.workCenter.key}`,
64
+ title: "Work Center",
65
+ },
66
+ ]
67
+ : []),
68
+ ],
69
+ _actions: draftCrudActions(`${API_PREFIX}${base}/${operation.seqNo}`, "UpdateOperation", revStatus, user),
70
+ };
71
+ }
72
+ export default function operationRoutes(fastify) {
73
+ const app = fastify.withTypeProvider();
74
+ // LIST
75
+ app.get("/", {
76
+ schema: {
77
+ description: "List operations for a revision",
78
+ tags: ["Operations"],
79
+ params: ParamsSchema,
80
+ response: {
81
+ 200: OperationListResponseSchema,
82
+ 404: ErrorResponseSchema,
83
+ },
84
+ },
85
+ handler: async (request, reply) => {
86
+ const { orderKey, revNo } = request.params;
87
+ const resolved = await resolveRevision(orderKey, revNo);
88
+ if (!resolved) {
89
+ return notFound(reply, `Revision not found`);
90
+ }
91
+ const items = await listOperations(resolved.rev.id);
92
+ const maxSeq = items.length > 0 ? items[items.length - 1].seqNo : 0;
93
+ const user = request.erpUser;
94
+ const base = opBasePath(orderKey, revNo);
95
+ return {
96
+ items: items.map((operation) => {
97
+ const formatted = formatOperation(orderKey, revNo, resolved.rev.status, user, operation, {
98
+ stepCount: operation._count.steps,
99
+ predecessors: operation.predecessors.map((d) => ({
100
+ seqNo: d.predecessor.seqNo,
101
+ title: d.predecessor.title,
102
+ })),
103
+ });
104
+ const { _links, ...rest } = formatted;
105
+ return rest;
106
+ }),
107
+ total: items.length,
108
+ nextSeqNo: calcNextSeqNo(maxSeq),
109
+ _links: [selfLink(base)],
110
+ _linkTemplates: [
111
+ {
112
+ rel: "item",
113
+ hrefTemplate: `${API_PREFIX}/orders/${orderKey}/revs/${revNo}/ops/{seqNo}`,
114
+ },
115
+ ],
116
+ _actions: [
117
+ {
118
+ rel: "create",
119
+ href: `${API_PREFIX}${base}`,
120
+ method: "POST",
121
+ title: "Add Operation",
122
+ schema: `${API_PREFIX}/schemas/CreateOperation`,
123
+ ...(!hasPermission(user, "order_planner")
124
+ ? permGate(false, "order_planner")
125
+ : resolved.rev.status !== RevisionStatus.draft
126
+ ? {
127
+ disabled: true,
128
+ disabledReason: "Can only add operations in draft revisions",
129
+ }
130
+ : {}),
131
+ },
132
+ ],
133
+ };
134
+ },
135
+ });
136
+ // CREATE
137
+ app.post("/", {
138
+ schema: {
139
+ description: "Create an operation for a revision",
140
+ tags: ["Operations"],
141
+ params: ParamsSchema,
142
+ body: CreateOperationSchema,
143
+ response: {
144
+ 201: SeqNoCreateResponseSchema,
145
+ 404: ErrorResponseSchema,
146
+ 409: ErrorResponseSchema,
147
+ },
148
+ },
149
+ preHandler: requirePermission("order_planner"),
150
+ handler: async (request, reply) => {
151
+ const { orderKey, revNo } = request.params;
152
+ const { seqNo: requestedSeqNo, title, description, workCenterKey, predecessorSeqNos, } = request.body;
153
+ const userId = request.erpUser.id;
154
+ const resolved = await resolveRevision(orderKey, revNo);
155
+ if (!resolved) {
156
+ return notFound(reply, `Revision not found`);
157
+ }
158
+ if (resolved.rev.status !== RevisionStatus.draft) {
159
+ return conflict(reply, `Cannot add operations to a ${resolved.rev.status} revision`);
160
+ }
161
+ const wcResult = await resolveWorkCenterId(workCenterKey);
162
+ if ("error" in wcResult) {
163
+ return notFound(reply, wcResult.error);
164
+ }
165
+ const operation = await createOperation(resolved.rev.id, requestedSeqNo, title, description, wcResult.id, predecessorSeqNos, userId);
166
+ const full = formatOperation(orderKey, revNo, resolved.rev.status, request.erpUser, operation, {
167
+ stepCount: operation._count.steps,
168
+ predecessors: operation.predecessors.map((d) => ({
169
+ seqNo: d.predecessor.seqNo,
170
+ title: d.predecessor.title,
171
+ })),
172
+ });
173
+ reply.status(201);
174
+ return mutationResult(request, reply, full, {
175
+ id: full.id,
176
+ seqNo: full.seqNo,
177
+ _links: full._links,
178
+ _actions: full._actions,
179
+ });
180
+ },
181
+ });
182
+ // GET by seqNo
183
+ app.get("/:seqNo", {
184
+ schema: {
185
+ description: "Get an operation by sequence number",
186
+ tags: ["Operations"],
187
+ params: OpParamsSchema,
188
+ response: {
189
+ 200: OperationSchema,
190
+ 404: ErrorResponseSchema,
191
+ },
192
+ },
193
+ handler: async (request, reply) => {
194
+ const { orderKey, revNo, seqNo } = request.params;
195
+ const resolved = await resolveRevision(orderKey, revNo);
196
+ if (!resolved) {
197
+ return notFound(reply, `Revision not found`);
198
+ }
199
+ const operation = await getOperation(resolved.rev.id, seqNo);
200
+ if (!operation) {
201
+ return notFound(reply, `Operation ${seqNo} not found`);
202
+ }
203
+ return formatOperation(orderKey, revNo, resolved.rev.status, request.erpUser, operation, {
204
+ stepCount: operation.steps.length,
205
+ stepSummary: operation.steps.map((s) => ({
206
+ seqNo: s.seqNo,
207
+ title: s.title,
208
+ })),
209
+ });
210
+ },
211
+ });
212
+ // UPDATE (draft only)
213
+ app.put("/:seqNo", {
214
+ schema: {
215
+ description: "Update an operation (draft revision only)",
216
+ tags: ["Operations"],
217
+ params: OpParamsSchema,
218
+ body: UpdateOperationSchema,
219
+ response: {
220
+ 200: MutateResponseSchema,
221
+ 404: ErrorResponseSchema,
222
+ 409: ErrorResponseSchema,
223
+ },
224
+ },
225
+ preHandler: requirePermission("order_planner"),
226
+ handler: async (request, reply) => {
227
+ const { orderKey, revNo, seqNo } = request.params;
228
+ const { title, description, workCenterKey, seqNo: newSeqNo, } = request.body;
229
+ const userId = request.erpUser.id;
230
+ const resolved = await resolveRevision(orderKey, revNo);
231
+ if (!resolved) {
232
+ return notFound(reply, `Revision not found`);
233
+ }
234
+ if (resolved.rev.status !== RevisionStatus.draft) {
235
+ return conflict(reply, `Cannot update operations on a ${resolved.rev.status} revision`);
236
+ }
237
+ const existing = await findExisting(resolved.rev.id, seqNo);
238
+ if (!existing) {
239
+ return notFound(reply, `Operation ${seqNo} not found`);
240
+ }
241
+ const wcResult = await resolveWorkCenterId(workCenterKey);
242
+ if ("error" in wcResult) {
243
+ return notFound(reply, wcResult.error);
244
+ }
245
+ const operation = await updateOperation(existing.id, { title, description, workCenterId: wcResult.id, seqNo: newSeqNo }, userId);
246
+ const full = formatOperation(orderKey, revNo, resolved.rev.status, request.erpUser, operation);
247
+ return mutationResult(request, reply, full, {
248
+ _actions: full._actions,
249
+ });
250
+ },
251
+ });
252
+ // DELETE (draft only)
253
+ app.delete("/:seqNo", {
254
+ schema: {
255
+ description: "Delete an operation (draft revision only)",
256
+ tags: ["Operations"],
257
+ params: OpParamsSchema,
258
+ response: {
259
+ 204: z.void(),
260
+ 404: ErrorResponseSchema,
261
+ 409: ErrorResponseSchema,
262
+ },
263
+ },
264
+ preHandler: requirePermission("order_planner"),
265
+ handler: async (request, reply) => {
266
+ const { orderKey, revNo, seqNo } = request.params;
267
+ const resolved = await resolveRevision(orderKey, revNo);
268
+ if (!resolved) {
269
+ return notFound(reply, `Revision not found`);
270
+ }
271
+ if (resolved.rev.status !== RevisionStatus.draft) {
272
+ return conflict(reply, `Cannot delete operations on a ${resolved.rev.status} revision`);
273
+ }
274
+ const existing = await findExisting(resolved.rev.id, seqNo);
275
+ if (!existing) {
276
+ return notFound(reply, `Operation ${seqNo} not found`);
277
+ }
278
+ await deleteOperation(existing.id);
279
+ reply.status(204);
280
+ },
281
+ });
282
+ }
283
+ //# sourceMappingURL=operations.js.map
@@ -0,0 +1,3 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export default function orderRevisionTransitionRoutes(fastify: FastifyInstance): void;
3
+ //# sourceMappingURL=order-revision-transitions.d.ts.map
@@ -0,0 +1,86 @@
1
+ import { ErrorResponseSchema, OrderRevisionTransitionSchema, RevisionStatus, } from "@naisys/erp-shared";
2
+ import { requirePermission } from "../auth-middleware.js";
3
+ import { conflict, notFound } from "../error-handler.js";
4
+ import { resolveOrder, useFullSerializer, wantsFullResponse, } from "../route-helpers.js";
5
+ import { findExisting, transitionStatus, } from "../services/order-revision-service.js";
6
+ import { formatRevision, revisionItemActions, RevNoParamsSchema, } from "./order-revisions.js";
7
+ export default function orderRevisionTransitionRoutes(fastify) {
8
+ const app = fastify.withTypeProvider();
9
+ // APPROVE (draft → approved)
10
+ app.post("/:revNo/approve", {
11
+ schema: {
12
+ description: "Approve a draft revision",
13
+ tags: ["Order Revisions"],
14
+ params: RevNoParamsSchema,
15
+ response: {
16
+ 200: OrderRevisionTransitionSchema,
17
+ 404: ErrorResponseSchema,
18
+ 409: ErrorResponseSchema,
19
+ },
20
+ },
21
+ preHandler: requirePermission("order_planner"),
22
+ handler: async (request, reply) => {
23
+ const { orderKey, revNo } = request.params;
24
+ const order = await resolveOrder(orderKey);
25
+ if (!order) {
26
+ return notFound(reply, `Order '${orderKey}' not found`);
27
+ }
28
+ const existing = await findExisting(order.id, revNo);
29
+ if (!existing) {
30
+ return notFound(reply, `Revision ${revNo} not found for order '${orderKey}'`);
31
+ }
32
+ if (existing.status !== RevisionStatus.draft) {
33
+ return conflict(reply, `Cannot approve revision in ${existing.status} status`);
34
+ }
35
+ const userId = request.erpUser.id;
36
+ const revision = await transitionStatus(existing.id, "approve", RevisionStatus.draft, RevisionStatus.approved, userId);
37
+ if (wantsFullResponse(request)) {
38
+ useFullSerializer(reply);
39
+ return await formatRevision(orderKey, request.erpUser, revision);
40
+ }
41
+ return {
42
+ status: revision.status,
43
+ _actions: revisionItemActions("orders", orderKey, revNo, revision.status, request.erpUser),
44
+ };
45
+ },
46
+ });
47
+ // OBSOLETE (approved → obsolete)
48
+ app.post("/:revNo/obsolete", {
49
+ schema: {
50
+ description: "Mark an approved revision as obsolete",
51
+ tags: ["Order Revisions"],
52
+ params: RevNoParamsSchema,
53
+ response: {
54
+ 200: OrderRevisionTransitionSchema,
55
+ 404: ErrorResponseSchema,
56
+ 409: ErrorResponseSchema,
57
+ },
58
+ },
59
+ preHandler: requirePermission("order_planner"),
60
+ handler: async (request, reply) => {
61
+ const { orderKey, revNo } = request.params;
62
+ const order = await resolveOrder(orderKey);
63
+ if (!order) {
64
+ return notFound(reply, `Order '${orderKey}' not found`);
65
+ }
66
+ const existing = await findExisting(order.id, revNo);
67
+ if (!existing) {
68
+ return notFound(reply, `Revision ${revNo} not found for order '${orderKey}'`);
69
+ }
70
+ if (existing.status !== RevisionStatus.approved) {
71
+ return conflict(reply, `Cannot mark revision as obsolete from ${existing.status} status`);
72
+ }
73
+ const userId = request.erpUser.id;
74
+ const revision = await transitionStatus(existing.id, "obsolete", RevisionStatus.approved, RevisionStatus.obsolete, userId);
75
+ if (wantsFullResponse(request)) {
76
+ useFullSerializer(reply);
77
+ return await formatRevision(orderKey, request.erpUser, revision);
78
+ }
79
+ return {
80
+ status: revision.status,
81
+ _actions: revisionItemActions("orders", orderKey, revNo, revision.status, request.erpUser),
82
+ };
83
+ },
84
+ });
85
+ }
86
+ //# sourceMappingURL=order-revision-transitions.js.map
@@ -0,0 +1,51 @@
1
+ import type { HateoasAction } from "@naisys/common";
2
+ import type { FastifyInstance } from "fastify";
3
+ import { z } from "zod/v4";
4
+ import type { ErpUser } from "../auth-middleware.js";
5
+ import { type OrderRevisionWithRelations } from "../services/order-revision-service.js";
6
+ export declare function revisionItemActions(parentResource: string, orderKey: string, revNo: number, status: string, user: ErpUser | undefined): HateoasAction[];
7
+ export declare const RevNoParamsSchema: z.ZodObject<{
8
+ orderKey: z.ZodString;
9
+ revNo: z.ZodCoercedNumber<unknown>;
10
+ }, z.core.$strip>;
11
+ export declare function formatRevision(orderKey: string, user: ErpUser | undefined, revision: OrderRevisionWithRelations): Promise<{
12
+ _links: {
13
+ rel: string;
14
+ href: string;
15
+ method?: string | undefined;
16
+ title?: string | undefined;
17
+ schema?: string | undefined;
18
+ }[];
19
+ _actions: {
20
+ rel: string;
21
+ href: string;
22
+ method: string;
23
+ title?: string | undefined;
24
+ schema?: string | undefined;
25
+ body?: Record<string, unknown> | undefined;
26
+ alternateEncoding?: {
27
+ contentType: string;
28
+ fileFields: string[];
29
+ description?: string | undefined;
30
+ } | undefined;
31
+ disabled?: boolean | undefined;
32
+ disabledReason?: string | string[] | undefined;
33
+ }[];
34
+ createdAt: string;
35
+ createdBy: string;
36
+ updatedAt: string;
37
+ updatedBy: string;
38
+ id: number;
39
+ orderId: number;
40
+ revNo: number;
41
+ status: import("../generated/prisma/enums.js").RevisionStatus;
42
+ description: string;
43
+ changeSummary: string | null;
44
+ itemKey: string | null;
45
+ operationSummary: {
46
+ seqNo: number;
47
+ title: string;
48
+ }[];
49
+ }>;
50
+ export default function orderRevisionRoutes(fastify: FastifyInstance): void;
51
+ //# sourceMappingURL=order-revisions.d.ts.map