@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 { BatchCreateStepSchema, BatchSeqNoCreateResponseSchema, CreateStepSchema, ErrorResponseSchema, MutateResponseSchema, RevisionStatus, SeqNoCreateResponseSchema, StepListResponseSchema, StepSchema, UpdateStepSchema, } from "@naisys/erp-shared";
2
+ import { z } from "zod/v4";
3
+ import { 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, resolveActions, resolveOperation, } from "../route-helpers.js";
7
+ import { createStep, createSteps, deleteStep, findExisting, getStep, listSteps, updateStep, } from "../services/step-service.js";
8
+ import { formatFieldListResponse } from "./step-fields.js";
9
+ const ParamsSchema = z.object({
10
+ orderKey: z.string(),
11
+ revNo: z.coerce.number().int(),
12
+ seqNo: z.coerce.number().int(),
13
+ });
14
+ const StepParamsSchema = z.object({
15
+ orderKey: z.string(),
16
+ revNo: z.coerce.number().int(),
17
+ seqNo: z.coerce.number().int(),
18
+ stepSeqNo: z.coerce.number().int(),
19
+ });
20
+ function stepBasePath(orderKey, revNo, opSeqNo) {
21
+ return `/orders/${orderKey}/revs/${revNo}/ops/${opSeqNo}/steps`;
22
+ }
23
+ function formatStep(orderKey, revNo, opSeqNo, revStatus, user, step) {
24
+ return {
25
+ id: step.id,
26
+ operationId: step.operationId,
27
+ seqNo: step.seqNo,
28
+ title: step.title,
29
+ instructions: step.instructions,
30
+ multiSet: step.multiSet,
31
+ fieldCount: step.fieldSet?.fields.length ?? 0,
32
+ ...formatAuditFields(step),
33
+ fields: formatFieldListResponse(orderKey, revNo, opSeqNo, step.seqNo, revStatus, user, step.fieldSet?.fields ?? []),
34
+ _links: childItemLinks(stepBasePath(orderKey, revNo, opSeqNo), step.seqNo, "Steps", `/orders/${orderKey}/revs/${revNo}/ops/${opSeqNo}`, "Operation", "Step"),
35
+ _actions: draftCrudActions(`${API_PREFIX}${stepBasePath(orderKey, revNo, opSeqNo)}/${step.seqNo}`, "UpdateStep", revStatus, user),
36
+ };
37
+ }
38
+ const draftCreateDef = {
39
+ rel: "create",
40
+ method: "POST",
41
+ title: "Add Step",
42
+ schema: `${API_PREFIX}/schemas/CreateStep`,
43
+ permission: "order_planner",
44
+ disabledWhen: (ctx) => ctx.status !== RevisionStatus.draft
45
+ ? "Can only add steps in draft revisions"
46
+ : null,
47
+ };
48
+ const draftBatchCreateDef = {
49
+ rel: "batch-create",
50
+ path: "/batch",
51
+ method: "POST",
52
+ title: "Add Steps (Batch)",
53
+ schema: `${API_PREFIX}/schemas/BatchCreateStep`,
54
+ permission: "order_planner",
55
+ disabledWhen: (ctx) => ctx.status !== RevisionStatus.draft
56
+ ? "Can only add steps in draft revisions"
57
+ : null,
58
+ };
59
+ function stepListActions(base, revStatus, user) {
60
+ return resolveActions([draftCreateDef, draftBatchCreateDef], `${API_PREFIX}${base}`, { status: revStatus, user });
61
+ }
62
+ export default function stepRoutes(fastify) {
63
+ const app = fastify.withTypeProvider();
64
+ // LIST
65
+ app.get("/", {
66
+ schema: {
67
+ description: "List steps for an operation",
68
+ tags: ["Steps"],
69
+ params: ParamsSchema,
70
+ response: {
71
+ 200: StepListResponseSchema,
72
+ 404: ErrorResponseSchema,
73
+ },
74
+ },
75
+ handler: async (request, reply) => {
76
+ const { orderKey, revNo, seqNo } = request.params;
77
+ const resolved = await resolveOperation(orderKey, revNo, seqNo);
78
+ if (!resolved) {
79
+ return notFound(reply, "Operation not found");
80
+ }
81
+ const items = await listSteps(resolved.operation.id);
82
+ const maxSeq = items.length > 0 ? items[items.length - 1].seqNo : 0;
83
+ const user = request.erpUser;
84
+ const base = stepBasePath(orderKey, revNo, seqNo);
85
+ return {
86
+ items: items.map((step) => {
87
+ const { _links, ...rest } = formatStep(orderKey, revNo, seqNo, resolved.rev.status, user, step);
88
+ return rest;
89
+ }),
90
+ total: items.length,
91
+ nextSeqNo: calcNextSeqNo(maxSeq),
92
+ _links: [selfLink(base)],
93
+ _linkTemplates: [
94
+ {
95
+ rel: "item",
96
+ hrefTemplate: `${API_PREFIX}${stepBasePath(orderKey, revNo, seqNo)}/{seqNo}`,
97
+ },
98
+ ],
99
+ _actions: stepListActions(base, resolved.rev.status, user),
100
+ };
101
+ },
102
+ });
103
+ // BATCH CREATE
104
+ app.post("/batch", {
105
+ schema: {
106
+ description: "Create multiple steps for an operation in one request",
107
+ tags: ["Steps"],
108
+ params: ParamsSchema,
109
+ body: BatchCreateStepSchema,
110
+ response: {
111
+ 201: BatchSeqNoCreateResponseSchema,
112
+ 404: ErrorResponseSchema,
113
+ 409: ErrorResponseSchema,
114
+ },
115
+ },
116
+ preHandler: requirePermission("order_planner"),
117
+ handler: async (request, reply) => {
118
+ const { orderKey, revNo, seqNo } = request.params;
119
+ const { items } = request.body;
120
+ const userId = request.erpUser.id;
121
+ const resolved = await resolveOperation(orderKey, revNo, seqNo);
122
+ if (!resolved) {
123
+ return notFound(reply, "Operation not found");
124
+ }
125
+ if (resolved.rev.status !== RevisionStatus.draft) {
126
+ return conflict(reply, `Cannot add steps to a ${resolved.rev.status} revision`);
127
+ }
128
+ const created = await createSteps(resolved.operation.id, items, userId);
129
+ const maxSeq = created.length > 0 ? created[created.length - 1].seqNo : 0;
130
+ const user = request.erpUser;
131
+ const base = stepBasePath(orderKey, revNo, seqNo);
132
+ const full = {
133
+ items: created.map((step) => {
134
+ const { _links, ...rest } = formatStep(orderKey, revNo, seqNo, resolved.rev.status, user, step);
135
+ return rest;
136
+ }),
137
+ total: created.length,
138
+ nextSeqNo: calcNextSeqNo(maxSeq),
139
+ _links: [selfLink(base)],
140
+ _linkTemplates: [
141
+ {
142
+ rel: "item",
143
+ hrefTemplate: `${API_PREFIX}${stepBasePath(orderKey, revNo, seqNo)}/{seqNo}`,
144
+ },
145
+ ],
146
+ _actions: [],
147
+ };
148
+ reply.status(201);
149
+ return mutationResult(request, reply, full, {
150
+ items: created.map((s) => ({ id: s.id, seqNo: s.seqNo })),
151
+ total: created.length,
152
+ _actions: full._actions,
153
+ });
154
+ },
155
+ });
156
+ // CREATE
157
+ app.post("/", {
158
+ schema: {
159
+ description: "Create a step for an operation",
160
+ tags: ["Steps"],
161
+ params: ParamsSchema,
162
+ body: CreateStepSchema,
163
+ response: {
164
+ 201: SeqNoCreateResponseSchema,
165
+ 404: ErrorResponseSchema,
166
+ 409: ErrorResponseSchema,
167
+ },
168
+ },
169
+ preHandler: requirePermission("order_planner"),
170
+ handler: async (request, reply) => {
171
+ const { orderKey, revNo, seqNo } = request.params;
172
+ const { seqNo: requestedSeqNo, title, instructions, multiSet, } = request.body;
173
+ const userId = request.erpUser.id;
174
+ const resolved = await resolveOperation(orderKey, revNo, seqNo);
175
+ if (!resolved) {
176
+ return notFound(reply, "Operation not found");
177
+ }
178
+ if (resolved.rev.status !== RevisionStatus.draft) {
179
+ return conflict(reply, `Cannot add steps to a ${resolved.rev.status} revision`);
180
+ }
181
+ const step = await createStep(resolved.operation.id, requestedSeqNo, title, instructions, multiSet, userId);
182
+ const full = formatStep(orderKey, revNo, seqNo, resolved.rev.status, request.erpUser, step);
183
+ reply.status(201);
184
+ return mutationResult(request, reply, full, {
185
+ id: full.id,
186
+ seqNo: full.seqNo,
187
+ _links: full._links,
188
+ _actions: full._actions,
189
+ });
190
+ },
191
+ });
192
+ // GET by stepSeqNo
193
+ app.get("/:stepSeqNo", {
194
+ schema: {
195
+ description: "Get a step by sequence number",
196
+ tags: ["Steps"],
197
+ params: StepParamsSchema,
198
+ response: {
199
+ 200: StepSchema,
200
+ 404: ErrorResponseSchema,
201
+ },
202
+ },
203
+ handler: async (request, reply) => {
204
+ const { orderKey, revNo, seqNo, stepSeqNo } = request.params;
205
+ const resolved = await resolveOperation(orderKey, revNo, seqNo);
206
+ if (!resolved) {
207
+ return notFound(reply, "Operation not found");
208
+ }
209
+ const step = await getStep(resolved.operation.id, stepSeqNo);
210
+ if (!step) {
211
+ return notFound(reply, `Step ${stepSeqNo} not found`);
212
+ }
213
+ return formatStep(orderKey, revNo, seqNo, resolved.rev.status, request.erpUser, step);
214
+ },
215
+ });
216
+ // UPDATE (draft only)
217
+ app.put("/:stepSeqNo", {
218
+ schema: {
219
+ description: "Update a step (draft revision only)",
220
+ tags: ["Steps"],
221
+ params: StepParamsSchema,
222
+ body: UpdateStepSchema,
223
+ response: {
224
+ 200: MutateResponseSchema,
225
+ 404: ErrorResponseSchema,
226
+ 409: ErrorResponseSchema,
227
+ },
228
+ },
229
+ preHandler: requirePermission("order_planner"),
230
+ handler: async (request, reply) => {
231
+ const { orderKey, revNo, seqNo, stepSeqNo } = request.params;
232
+ const { title, instructions, seqNo: newSeqNo, multiSet } = request.body;
233
+ const userId = request.erpUser.id;
234
+ const resolved = await resolveOperation(orderKey, revNo, seqNo);
235
+ if (!resolved) {
236
+ return notFound(reply, "Operation not found");
237
+ }
238
+ if (resolved.rev.status !== RevisionStatus.draft) {
239
+ return conflict(reply, `Cannot update steps on a ${resolved.rev.status} revision`);
240
+ }
241
+ const existing = await findExisting(resolved.operation.id, stepSeqNo);
242
+ if (!existing) {
243
+ return notFound(reply, `Step ${stepSeqNo} not found`);
244
+ }
245
+ const step = await updateStep(existing.id, { title, instructions, seqNo: newSeqNo, multiSet }, userId);
246
+ const full = formatStep(orderKey, revNo, seqNo, resolved.rev.status, request.erpUser, step);
247
+ return mutationResult(request, reply, full, {
248
+ _actions: full._actions,
249
+ });
250
+ },
251
+ });
252
+ // DELETE (draft only)
253
+ app.delete("/:stepSeqNo", {
254
+ schema: {
255
+ description: "Delete a step (draft revision only)",
256
+ tags: ["Steps"],
257
+ params: StepParamsSchema,
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, stepSeqNo } = request.params;
267
+ const resolved = await resolveOperation(orderKey, revNo, seqNo);
268
+ if (!resolved) {
269
+ return notFound(reply, "Operation not found");
270
+ }
271
+ if (resolved.rev.status !== RevisionStatus.draft) {
272
+ return conflict(reply, `Cannot delete steps on a ${resolved.rev.status} revision`);
273
+ }
274
+ const existing = await findExisting(resolved.operation.id, stepSeqNo);
275
+ if (!existing) {
276
+ return notFound(reply, `Step ${stepSeqNo} not found`);
277
+ }
278
+ await deleteStep(existing.id);
279
+ reply.status(204);
280
+ },
281
+ });
282
+ }
283
+ //# sourceMappingURL=steps.js.map
@@ -0,0 +1,3 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export default function userPermissionRoutes(fastify: FastifyInstance): void;
3
+ //# sourceMappingURL=user-permissions.d.ts.map
@@ -0,0 +1,100 @@
1
+ import { ErpPermissionEnum, GrantPermissionSchema } from "@naisys/erp-shared";
2
+ import { z } from "zod/v4";
3
+ import { authCache, requirePermission } from "../auth-middleware.js";
4
+ import { mutationResult } from "../route-helpers.js";
5
+ import { getUserById, getUserByUsername, grantPermission, revokePermission, rotateUserApiKey, } from "../services/user-service.js";
6
+ import { formatUser } from "./users.js";
7
+ export default function userPermissionRoutes(fastify) {
8
+ const app = fastify.withTypeProvider();
9
+ const adminPreHandler = [requirePermission("erp_admin")];
10
+ const usernameParams = z.object({ username: z.string() });
11
+ // ROTATE API KEY
12
+ app.post("/:username/rotate-key", {
13
+ preHandler: adminPreHandler,
14
+ schema: {
15
+ description: "Rotate a user's API key",
16
+ tags: ["Users"],
17
+ params: usernameParams,
18
+ },
19
+ }, async (request, reply) => {
20
+ const targetUser = await getUserByUsername(request.params.username);
21
+ if (!targetUser) {
22
+ reply.code(404);
23
+ return { success: false, message: "User not found" };
24
+ }
25
+ await rotateUserApiKey(targetUser.id);
26
+ authCache.clear();
27
+ return { success: true, message: "API key rotated" };
28
+ });
29
+ // GRANT PERMISSION
30
+ app.post("/:username/permissions", {
31
+ preHandler: adminPreHandler,
32
+ schema: {
33
+ description: "Grant a permission to a user",
34
+ tags: ["Users"],
35
+ params: usernameParams,
36
+ body: GrantPermissionSchema,
37
+ },
38
+ }, async (request, reply) => {
39
+ const targetUser = await getUserByUsername(request.params.username);
40
+ if (!targetUser) {
41
+ reply.code(404);
42
+ return { success: false, message: "User not found" };
43
+ }
44
+ try {
45
+ await grantPermission(targetUser.id, request.body.permission, request.erpUser.id);
46
+ authCache.clear();
47
+ const user = await getUserById(targetUser.id);
48
+ const full = formatUser(user, request.erpUser.id, request.erpUser.permissions);
49
+ return mutationResult(request, reply, full, {
50
+ _actions: full._actions,
51
+ });
52
+ }
53
+ catch (err) {
54
+ if (err instanceof Error && err.message.includes("Unique constraint")) {
55
+ reply.code(409);
56
+ return {
57
+ success: false,
58
+ message: "Permission already granted",
59
+ };
60
+ }
61
+ throw err;
62
+ }
63
+ });
64
+ // REVOKE PERMISSION
65
+ app.delete("/:username/permissions/:permission", {
66
+ preHandler: adminPreHandler,
67
+ schema: {
68
+ description: "Revoke a permission from a user",
69
+ tags: ["Users"],
70
+ params: z.object({
71
+ username: z.string(),
72
+ permission: ErpPermissionEnum,
73
+ }),
74
+ },
75
+ }, async (request, reply) => {
76
+ const { username, permission } = request.params;
77
+ // Cannot revoke own erp_admin
78
+ if (username === request.erpUser.username &&
79
+ permission === "erp_admin") {
80
+ reply.code(409);
81
+ return {
82
+ success: false,
83
+ message: "Cannot revoke your own erp_admin permission",
84
+ };
85
+ }
86
+ const targetUser = await getUserByUsername(username);
87
+ if (!targetUser) {
88
+ reply.code(404);
89
+ return { success: false, message: "User not found" };
90
+ }
91
+ await revokePermission(targetUser.id, permission);
92
+ authCache.clear();
93
+ const user = await getUserById(targetUser.id);
94
+ const full = formatUser(user, request.erpUser.id, request.erpUser.permissions);
95
+ return mutationResult(request, reply, full, {
96
+ _actions: full._actions,
97
+ });
98
+ });
99
+ }
100
+ //# sourceMappingURL=user-permissions.js.map
@@ -0,0 +1,57 @@
1
+ import { type ErpPermission } from "@naisys/erp-shared";
2
+ import type { FastifyInstance } from "fastify";
3
+ import type { getUserById } from "../services/user-service.js";
4
+ export declare function formatUser(user: Awaited<ReturnType<typeof getUserById>>, currentUserId: number, currentUserPermissions: ErpPermission[], options?: {
5
+ apiKey?: string | null;
6
+ }): {
7
+ id: number;
8
+ username: string;
9
+ isAgent: boolean;
10
+ createdAt: string;
11
+ updatedAt: string;
12
+ apiKey: string | null | undefined;
13
+ permissions: {
14
+ permission: import("../generated/prisma/enums.js").ErpPermission;
15
+ grantedAt: string;
16
+ grantedBy: number | null;
17
+ _actions: {
18
+ rel: string;
19
+ href: string;
20
+ method: string;
21
+ title?: string | undefined;
22
+ schema?: string | undefined;
23
+ body?: Record<string, unknown> | undefined;
24
+ alternateEncoding?: {
25
+ contentType: string;
26
+ fileFields: string[];
27
+ description?: string | undefined;
28
+ } | undefined;
29
+ disabled?: boolean | undefined;
30
+ disabledReason?: string | string[] | undefined;
31
+ }[];
32
+ }[];
33
+ _links: {
34
+ rel: string;
35
+ href: string;
36
+ method?: string | undefined;
37
+ title?: string | undefined;
38
+ schema?: string | undefined;
39
+ }[];
40
+ _actions: {
41
+ rel: string;
42
+ href: string;
43
+ method: string;
44
+ title?: string | undefined;
45
+ schema?: string | undefined;
46
+ body?: Record<string, unknown> | undefined;
47
+ alternateEncoding?: {
48
+ contentType: string;
49
+ fileFields: string[];
50
+ description?: string | undefined;
51
+ } | undefined;
52
+ disabled?: boolean | undefined;
53
+ disabledReason?: string | string[] | undefined;
54
+ }[];
55
+ } | null;
56
+ export default function userRoutes(fastify: FastifyInstance): void;
57
+ //# sourceMappingURL=users.d.ts.map