@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,220 @@
1
+ import { CreateFieldSchema, ErrorResponseSchema, FieldListResponseSchema, FieldSchema, MutateResponseSchema, SeqNoCreateResponseSchema, UpdateFieldSchema, } from "@naisys/erp-shared";
2
+ import { z } from "zod/v4";
3
+ import { hasPermission, requirePermission } from "../auth-middleware.js";
4
+ import erpDb from "../erpDb.js";
5
+ import { notFound } from "../error-handler.js";
6
+ import { API_PREFIX, selfLink } from "../hateoas.js";
7
+ import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, } from "../route-helpers.js";
8
+ import { createField, deleteField, ensureFieldSet, findExistingField, getField, listFields, updateField, } from "../services/field-service.js";
9
+ import { findExisting as findExistingItem } from "../services/item-service.js";
10
+ const ParamsSchema = z.object({ key: z.string() });
11
+ const FieldParamsSchema = z.object({
12
+ key: z.string(),
13
+ fieldSeqNo: z.coerce.number().int(),
14
+ });
15
+ function fieldBasePath(key) {
16
+ return `/items/${key}/fields`;
17
+ }
18
+ function formatField(key, user, field) {
19
+ const base = fieldBasePath(key);
20
+ return {
21
+ id: field.id,
22
+ fieldSetId: field.fieldSetId,
23
+ seqNo: field.seqNo,
24
+ label: field.label,
25
+ type: field.type,
26
+ isArray: field.isArray,
27
+ required: field.required,
28
+ ...formatAuditFields(field),
29
+ _links: childItemLinks(base, field.seqNo, "Fields", `/items/${key}`, "Item", "Field"),
30
+ _actions: hasPermission(user, "item_manager")
31
+ ? [
32
+ {
33
+ rel: "update",
34
+ href: `${API_PREFIX}${base}/${field.seqNo}`,
35
+ method: "PUT",
36
+ title: "Update",
37
+ schema: `${API_PREFIX}/schemas/UpdateField`,
38
+ },
39
+ {
40
+ rel: "delete",
41
+ href: `${API_PREFIX}${base}/${field.seqNo}`,
42
+ method: "DELETE",
43
+ title: "Delete",
44
+ },
45
+ ]
46
+ : [],
47
+ };
48
+ }
49
+ export default function itemFieldRoutes(fastify) {
50
+ const app = fastify.withTypeProvider();
51
+ // LIST
52
+ app.get("/", {
53
+ schema: {
54
+ description: "List fields for an item",
55
+ tags: ["Item Fields"],
56
+ params: ParamsSchema,
57
+ response: {
58
+ 200: FieldListResponseSchema,
59
+ 404: ErrorResponseSchema,
60
+ },
61
+ },
62
+ handler: async (request, reply) => {
63
+ const { key } = request.params;
64
+ const item = await findExistingItem(key);
65
+ if (!item)
66
+ return notFound(reply, `Item '${key}' not found`);
67
+ const fields = item.fieldSetId ? await listFields(item.fieldSetId) : [];
68
+ const maxSeq = fields.length > 0 ? fields[fields.length - 1].seqNo : 0;
69
+ const base = fieldBasePath(key);
70
+ return {
71
+ items: fields.map((f) => {
72
+ const { _links, ...rest } = formatField(key, request.erpUser, f);
73
+ return rest;
74
+ }),
75
+ total: fields.length,
76
+ nextSeqNo: calcNextSeqNo(maxSeq),
77
+ _links: [selfLink(base)],
78
+ _linkTemplates: [
79
+ {
80
+ rel: "item",
81
+ hrefTemplate: `${API_PREFIX}${base}/{seqNo}`,
82
+ },
83
+ ],
84
+ _actions: hasPermission(request.erpUser, "item_manager")
85
+ ? [
86
+ {
87
+ rel: "create",
88
+ href: `${API_PREFIX}${base}`,
89
+ method: "POST",
90
+ title: "Add Field",
91
+ schema: `${API_PREFIX}/schemas/CreateField`,
92
+ },
93
+ ]
94
+ : [],
95
+ };
96
+ },
97
+ });
98
+ // CREATE
99
+ app.post("/", {
100
+ schema: {
101
+ description: "Create a field for an item",
102
+ tags: ["Item Fields"],
103
+ params: ParamsSchema,
104
+ body: CreateFieldSchema,
105
+ response: {
106
+ 201: SeqNoCreateResponseSchema,
107
+ 404: ErrorResponseSchema,
108
+ },
109
+ },
110
+ preHandler: requirePermission("item_manager"),
111
+ handler: async (request, reply) => {
112
+ const { key } = request.params;
113
+ const { seqNo: requestedSeqNo, label, type, isArray, required, } = request.body;
114
+ const userId = request.erpUser.id;
115
+ const item = await findExistingItem(key);
116
+ if (!item)
117
+ return notFound(reply, `Item '${key}' not found`);
118
+ let fieldSetId = item.fieldSetId;
119
+ if (!fieldSetId) {
120
+ fieldSetId = await ensureFieldSet(null, userId);
121
+ await erpDb.item.update({
122
+ where: { key },
123
+ data: { fieldSetId },
124
+ });
125
+ }
126
+ const field = await createField(fieldSetId, { seqNo: requestedSeqNo, label, type, isArray, required }, userId);
127
+ const full = formatField(key, request.erpUser, field);
128
+ reply.status(201);
129
+ return mutationResult(request, reply, full, {
130
+ id: full.id,
131
+ seqNo: full.seqNo,
132
+ _links: full._links,
133
+ _actions: full._actions,
134
+ });
135
+ },
136
+ });
137
+ // GET
138
+ app.get("/:fieldSeqNo", {
139
+ schema: {
140
+ description: "Get an item field",
141
+ tags: ["Item Fields"],
142
+ params: FieldParamsSchema,
143
+ response: {
144
+ 200: FieldSchema,
145
+ 404: ErrorResponseSchema,
146
+ },
147
+ },
148
+ handler: async (request, reply) => {
149
+ const { key, fieldSeqNo } = request.params;
150
+ const item = await findExistingItem(key);
151
+ if (!item)
152
+ return notFound(reply, `Item '${key}' not found`);
153
+ if (!item.fieldSetId)
154
+ return notFound(reply, `Field ${fieldSeqNo} not found`);
155
+ const field = await getField(item.fieldSetId, fieldSeqNo);
156
+ if (!field)
157
+ return notFound(reply, `Field ${fieldSeqNo} not found`);
158
+ return formatField(key, request.erpUser, field);
159
+ },
160
+ });
161
+ // UPDATE
162
+ app.put("/:fieldSeqNo", {
163
+ schema: {
164
+ description: "Update an item field",
165
+ tags: ["Item Fields"],
166
+ params: FieldParamsSchema,
167
+ body: UpdateFieldSchema,
168
+ response: {
169
+ 200: MutateResponseSchema,
170
+ 404: ErrorResponseSchema,
171
+ },
172
+ },
173
+ preHandler: requirePermission("item_manager"),
174
+ handler: async (request, reply) => {
175
+ const { key, fieldSeqNo } = request.params;
176
+ const { label, type, isArray, required, seqNo: newSeqNo } = request.body;
177
+ const userId = request.erpUser.id;
178
+ const item = await findExistingItem(key);
179
+ if (!item)
180
+ return notFound(reply, `Item '${key}' not found`);
181
+ if (!item.fieldSetId)
182
+ return notFound(reply, `Field ${fieldSeqNo} not found`);
183
+ const existing = await findExistingField(item.fieldSetId, fieldSeqNo);
184
+ if (!existing)
185
+ return notFound(reply, `Field ${fieldSeqNo} not found`);
186
+ const field = await updateField(existing.id, { label, type, isArray, required, seqNo: newSeqNo }, userId);
187
+ const full = formatField(key, request.erpUser, field);
188
+ return mutationResult(request, reply, full, {
189
+ _actions: full._actions,
190
+ });
191
+ },
192
+ });
193
+ // DELETE
194
+ app.delete("/:fieldSeqNo", {
195
+ schema: {
196
+ description: "Delete an item field",
197
+ tags: ["Item Fields"],
198
+ params: FieldParamsSchema,
199
+ response: {
200
+ 204: z.void(),
201
+ 404: ErrorResponseSchema,
202
+ },
203
+ },
204
+ preHandler: requirePermission("item_manager"),
205
+ handler: async (request, reply) => {
206
+ const { key, fieldSeqNo } = request.params;
207
+ const item = await findExistingItem(key);
208
+ if (!item)
209
+ return notFound(reply, `Item '${key}' not found`);
210
+ if (!item.fieldSetId)
211
+ return notFound(reply, `Field ${fieldSeqNo} not found`);
212
+ const existing = await findExistingField(item.fieldSetId, fieldSeqNo);
213
+ if (!existing)
214
+ return notFound(reply, `Field ${fieldSeqNo} not found`);
215
+ await deleteField(existing.id);
216
+ reply.status(204);
217
+ },
218
+ });
219
+ }
220
+ //# sourceMappingURL=item-fields.js.map
@@ -0,0 +1,3 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export default function itemInstanceRoutes(fastify: FastifyInstance): void;
3
+ //# sourceMappingURL=item-instances.d.ts.map
@@ -0,0 +1,426 @@
1
+ import { CreateItemInstanceSchema, DeleteSetMutateResponseSchema, ErrorResponseSchema, fieldTypeString, FieldValueMutateResponseSchema, getValueFormatHint, ItemInstanceListQuerySchema, ItemInstanceListResponseSchema, ItemInstanceSchema, KeyCreateResponseSchema, MutateResponseSchema, UpdateFieldValueSchema, UpdateItemInstanceSchema, } from "@naisys/erp-shared";
2
+ import { z } from "zod/v4";
3
+ import { hasPermission, requirePermission } from "../auth-middleware.js";
4
+ import { notFound, unprocessable } from "../error-handler.js";
5
+ import { API_PREFIX, paginationLinks, schemaLink, selfLink, } from "../hateoas.js";
6
+ import { formatAuditFields, mutationResult, useFullSerializer, wantsFullResponse, } from "../route-helpers.js";
7
+ import { checkFieldValueShape, deleteFieldValueSet, deserializeFieldValue, serializeFieldValue, upsertFieldValue, validateFieldValue, } from "../services/field-value-service.js";
8
+ import { createItemInstance, deleteItemInstance, ensureItemInstanceFieldRecord, findItemInstance, findItemInstanceWithField, listItemInstances, updateItemInstance, } from "../services/item-instance-service.js";
9
+ import { findExisting as findItem } from "../services/item-service.js";
10
+ const ParamsSchema = z.object({
11
+ key: z.string(),
12
+ });
13
+ const InstanceParamsSchema = z.object({
14
+ key: z.string(),
15
+ instanceId: z.coerce.number(),
16
+ });
17
+ const FieldSeqNoParamsSchema = z.object({
18
+ key: z.string(),
19
+ instanceId: z.coerce.number(),
20
+ fieldSeqNo: z.coerce.number().int(),
21
+ });
22
+ const SetIndexParamsSchema = z.object({
23
+ key: z.string(),
24
+ instanceId: z.coerce.number(),
25
+ setIndex: z.coerce.number().int(),
26
+ });
27
+ const SetFieldSeqNoParamsSchema = z.object({
28
+ key: z.string(),
29
+ instanceId: z.coerce.number(),
30
+ setIndex: z.coerce.number().int().min(0),
31
+ fieldSeqNo: z.coerce.number().int(),
32
+ });
33
+ function instanceBasePath(itemKey) {
34
+ return `items/${itemKey}/instances`;
35
+ }
36
+ function instanceLinks(itemKey, instanceId, inst) {
37
+ const base = instanceBasePath(itemKey);
38
+ const links = [
39
+ selfLink(`/${base}/${instanceId}`),
40
+ {
41
+ rel: "collection",
42
+ href: `${API_PREFIX}/${base}`,
43
+ title: "Instances",
44
+ },
45
+ {
46
+ rel: "parent",
47
+ href: `${API_PREFIX}/items/${itemKey}`,
48
+ title: "Item",
49
+ },
50
+ schemaLink("ItemInstance"),
51
+ ];
52
+ if (inst.orderRun) {
53
+ links.push({
54
+ rel: "orderRun",
55
+ href: `${API_PREFIX}/orders/${inst.orderRun.order.key}/runs/${inst.orderRun.runNo}`,
56
+ title: "Order Run",
57
+ });
58
+ }
59
+ return links;
60
+ }
61
+ function instanceActions(itemKey, instanceId, user) {
62
+ if (!hasPermission(user, "item_manager"))
63
+ return [];
64
+ const href = `${API_PREFIX}/${instanceBasePath(itemKey)}/${instanceId}`;
65
+ return [
66
+ {
67
+ rel: "update",
68
+ href,
69
+ method: "PUT",
70
+ title: "Update",
71
+ schema: `${API_PREFIX}/schemas/UpdateItemInstance`,
72
+ body: { key: "" },
73
+ },
74
+ {
75
+ rel: "delete",
76
+ href,
77
+ method: "DELETE",
78
+ title: "Delete",
79
+ },
80
+ ];
81
+ }
82
+ function orderKey(inst) {
83
+ return inst.orderRun?.order.key ?? null;
84
+ }
85
+ function orderRunNo(inst) {
86
+ return inst.orderRun?.runNo ?? null;
87
+ }
88
+ function buildFieldValues(inst) {
89
+ const fields = inst.item.fieldSet?.fields ?? [];
90
+ if (fields.length === 0)
91
+ return [];
92
+ const storedFieldValues = inst.fieldRecord?.fieldValues ?? [];
93
+ const maxSetIndex = storedFieldValues.reduce((max, fv) => Math.max(max, fv.setIndex), -1);
94
+ const setCount = Math.max(1, maxSetIndex + 1);
95
+ const fieldValues = [];
96
+ for (let si = 0; si < setCount; si++) {
97
+ for (const field of fields) {
98
+ const stored = storedFieldValues.find((fv) => fv.fieldId === field.id && fv.setIndex === si);
99
+ const value = deserializeFieldValue(stored?.value ?? "", field.isArray);
100
+ const attachments = field.type === "attachment" && stored
101
+ ? stored.fieldAttachments.map((sfa) => ({
102
+ id: sfa.attachment.publicId,
103
+ filename: sfa.attachment.filename,
104
+ fileSize: sfa.attachment.fileSize,
105
+ }))
106
+ : undefined;
107
+ const fieldType = fieldTypeString(field.type, field.isArray);
108
+ fieldValues.push({
109
+ fieldId: field.id,
110
+ fieldSeqNo: field.seqNo,
111
+ label: field.label,
112
+ type: fieldType,
113
+ valueFormat: getValueFormatHint(fieldType),
114
+ required: field.required,
115
+ setIndex: si,
116
+ value,
117
+ attachments,
118
+ validation: validateFieldValue(field.type, field.isArray, field.required, value),
119
+ });
120
+ }
121
+ }
122
+ return fieldValues;
123
+ }
124
+ function buildActionTemplates(itemKey, instanceId, user, hasFields) {
125
+ if (!hasPermission(user, "item_manager") || !hasFields)
126
+ return [];
127
+ const instanceHref = `${API_PREFIX}/${instanceBasePath(itemKey)}/${instanceId}`;
128
+ return [
129
+ {
130
+ rel: "updateField",
131
+ hrefTemplate: `${instanceHref}/fields/{fieldSeqNo}`,
132
+ method: "PUT",
133
+ title: "Update Field Value",
134
+ schema: `${API_PREFIX}/schemas/UpdateFieldValue`,
135
+ body: { value: "" },
136
+ },
137
+ ];
138
+ }
139
+ function formatInstance(inst, user) {
140
+ const hasFields = (inst.item.fieldSet?.fields ?? []).length > 0;
141
+ return {
142
+ id: inst.id,
143
+ itemKey: inst.item.key,
144
+ orderKey: orderKey(inst),
145
+ orderRunNo: orderRunNo(inst),
146
+ key: inst.key,
147
+ quantity: inst.quantity,
148
+ fieldValues: buildFieldValues(inst),
149
+ ...formatAuditFields(inst),
150
+ _links: instanceLinks(inst.item.key, inst.id, inst),
151
+ _actions: instanceActions(inst.item.key, inst.id, user),
152
+ _actionTemplates: buildActionTemplates(inst.item.key, inst.id, user, hasFields),
153
+ };
154
+ }
155
+ function formatListInstance(inst, _user) {
156
+ return {
157
+ id: inst.id,
158
+ itemKey: inst.item.key,
159
+ orderKey: orderKey(inst),
160
+ orderRunNo: orderRunNo(inst),
161
+ key: inst.key,
162
+ quantity: inst.quantity,
163
+ fieldValues: buildFieldValues(inst),
164
+ ...formatAuditFields(inst),
165
+ };
166
+ }
167
+ export default function itemInstanceRoutes(fastify) {
168
+ const app = fastify.withTypeProvider();
169
+ // LIST
170
+ app.get("/", {
171
+ schema: {
172
+ description: "List item instances with pagination and search",
173
+ tags: ["Item Instances"],
174
+ params: ParamsSchema,
175
+ querystring: ItemInstanceListQuerySchema,
176
+ response: {
177
+ 200: ItemInstanceListResponseSchema,
178
+ 404: ErrorResponseSchema,
179
+ },
180
+ },
181
+ handler: async (request, reply) => {
182
+ const { key } = request.params;
183
+ const { page, pageSize, search } = request.query;
184
+ const item = await findItem(key);
185
+ if (!item)
186
+ return notFound(reply, `Item '${key}' not found`);
187
+ const where = { itemId: item.id };
188
+ if (search) {
189
+ where.key = { contains: search };
190
+ }
191
+ const [instances, total] = await listItemInstances(where, page, pageSize);
192
+ const base = instanceBasePath(key);
193
+ return {
194
+ items: instances.map((inst) => formatListInstance(inst, request.erpUser)),
195
+ total,
196
+ page,
197
+ pageSize,
198
+ _links: paginationLinks(base, page, pageSize, total, { search }),
199
+ _linkTemplates: [
200
+ {
201
+ rel: "item",
202
+ hrefTemplate: `${API_PREFIX}/items/${key}/instances/{id}`,
203
+ },
204
+ ],
205
+ _actions: hasPermission(request.erpUser, "item_manager")
206
+ ? [
207
+ {
208
+ rel: "create",
209
+ href: `${API_PREFIX}/${base}`,
210
+ method: "POST",
211
+ title: "Create Instance",
212
+ schema: `${API_PREFIX}/schemas/CreateItemInstance`,
213
+ body: { key: "" },
214
+ },
215
+ ]
216
+ : [],
217
+ };
218
+ },
219
+ });
220
+ // CREATE
221
+ app.post("/", {
222
+ schema: {
223
+ description: "Create a new item instance",
224
+ tags: ["Item Instances"],
225
+ params: ParamsSchema,
226
+ body: CreateItemInstanceSchema,
227
+ response: {
228
+ 201: KeyCreateResponseSchema,
229
+ 404: ErrorResponseSchema,
230
+ },
231
+ },
232
+ preHandler: requirePermission("item_manager"),
233
+ handler: async (request, reply) => {
234
+ const { key: itemKey } = request.params;
235
+ const { key, quantity, orderRunId } = request.body;
236
+ const userId = request.erpUser.id;
237
+ const item = await findItem(itemKey);
238
+ if (!item)
239
+ return notFound(reply, `Item '${itemKey}' not found`);
240
+ const inst = await createItemInstance(item.id, key, quantity, orderRunId, userId);
241
+ const full = formatInstance(inst, request.erpUser);
242
+ reply.status(201);
243
+ return mutationResult(request, reply, full, {
244
+ id: full.id,
245
+ key: full.key,
246
+ _links: full._links,
247
+ _actions: full._actions,
248
+ });
249
+ },
250
+ });
251
+ // GET by id
252
+ app.get("/:instanceId", {
253
+ schema: {
254
+ description: "Get a single item instance",
255
+ tags: ["Item Instances"],
256
+ params: InstanceParamsSchema,
257
+ response: {
258
+ 200: ItemInstanceSchema,
259
+ 404: ErrorResponseSchema,
260
+ },
261
+ },
262
+ handler: async (request, reply) => {
263
+ const { instanceId } = request.params;
264
+ const inst = await findItemInstance(instanceId);
265
+ if (!inst)
266
+ return notFound(reply, `Item instance ${instanceId} not found`);
267
+ return formatInstance(inst, request.erpUser);
268
+ },
269
+ });
270
+ // UPDATE
271
+ app.put("/:instanceId", {
272
+ schema: {
273
+ description: "Update an item instance",
274
+ tags: ["Item Instances"],
275
+ params: InstanceParamsSchema,
276
+ body: UpdateItemInstanceSchema,
277
+ response: {
278
+ 200: MutateResponseSchema,
279
+ 404: ErrorResponseSchema,
280
+ },
281
+ },
282
+ preHandler: requirePermission("item_manager"),
283
+ handler: async (request, reply) => {
284
+ const { instanceId } = request.params;
285
+ const data = request.body;
286
+ const userId = request.erpUser.id;
287
+ const existing = await findItemInstance(instanceId);
288
+ if (!existing)
289
+ return notFound(reply, `Item instance ${instanceId} not found`);
290
+ const inst = await updateItemInstance(instanceId, data, userId);
291
+ const full = formatInstance(inst, request.erpUser);
292
+ return mutationResult(request, reply, full, {
293
+ _actions: full._actions,
294
+ });
295
+ },
296
+ });
297
+ // DELETE
298
+ app.delete("/:instanceId", {
299
+ schema: {
300
+ description: "Delete an item instance",
301
+ tags: ["Item Instances"],
302
+ params: InstanceParamsSchema,
303
+ response: {
304
+ 204: z.void(),
305
+ 404: ErrorResponseSchema,
306
+ },
307
+ },
308
+ preHandler: requirePermission("item_manager"),
309
+ handler: async (request, reply) => {
310
+ const { instanceId } = request.params;
311
+ const existing = await findItemInstance(instanceId);
312
+ if (!existing)
313
+ return notFound(reply, `Item instance ${instanceId} not found`);
314
+ await deleteItemInstance(instanceId);
315
+ reply.status(204);
316
+ },
317
+ });
318
+ // Shared handler for updating a single field value
319
+ async function handleFieldUpdate(request, reply, setIndex) {
320
+ const { instanceId, fieldSeqNo } = request.params;
321
+ const { value } = request.body;
322
+ const userId = request.erpUser.id;
323
+ const inst = await findItemInstanceWithField(instanceId, fieldSeqNo);
324
+ if (!inst)
325
+ return notFound(reply, `Item instance ${instanceId} not found`);
326
+ const field = inst.item.fieldSet?.fields[0];
327
+ if (!field)
328
+ return notFound(reply, `Field not found`);
329
+ const shapeErr = checkFieldValueShape(field.label, field.type, field.isArray, value);
330
+ if (shapeErr)
331
+ return unprocessable(reply, shapeErr);
332
+ const fieldRecordId = await ensureItemInstanceFieldRecord(instanceId, userId);
333
+ if (!fieldRecordId)
334
+ return notFound(reply, "Item has no field set");
335
+ await upsertFieldValue(fieldRecordId, field.id, setIndex, value, userId);
336
+ // Return deserialized value
337
+ const responseValue = deserializeFieldValue(serializeFieldValue(value), field.isArray);
338
+ const validation = validateFieldValue(field.type, field.isArray, field.required, responseValue);
339
+ const fieldType = fieldTypeString(field.type, field.isArray);
340
+ const full = {
341
+ fieldId: field.id,
342
+ fieldSeqNo: field.seqNo,
343
+ label: field.label,
344
+ type: fieldType,
345
+ valueFormat: getValueFormatHint(fieldType),
346
+ required: field.required,
347
+ setIndex,
348
+ value: responseValue,
349
+ validation,
350
+ };
351
+ return mutationResult(request, reply, full, {
352
+ value: responseValue,
353
+ validation,
354
+ });
355
+ }
356
+ // UPDATE single field value (implicit set 0)
357
+ app.put("/:instanceId/fields/:fieldSeqNo", {
358
+ schema: {
359
+ description: "Update a single field value on an item instance (implicit set 0). " +
360
+ "For multi-set items, use /sets/{setIndex}/fields/{fieldSeqNo} instead.",
361
+ tags: ["Item Instances"],
362
+ params: FieldSeqNoParamsSchema,
363
+ body: UpdateFieldValueSchema,
364
+ response: {
365
+ 200: FieldValueMutateResponseSchema,
366
+ 404: ErrorResponseSchema,
367
+ },
368
+ },
369
+ preHandler: requirePermission("item_manager"),
370
+ handler: async (request, reply) => handleFieldUpdate(request, reply, 0),
371
+ });
372
+ // UPDATE single field value (explicit set index)
373
+ app.put("/:instanceId/sets/:setIndex/fields/:fieldSeqNo", {
374
+ schema: {
375
+ description: "Update a single field value on a specific set of an item instance",
376
+ tags: ["Item Instances"],
377
+ params: SetFieldSeqNoParamsSchema,
378
+ body: UpdateFieldValueSchema,
379
+ response: {
380
+ 200: FieldValueMutateResponseSchema,
381
+ 404: ErrorResponseSchema,
382
+ },
383
+ },
384
+ preHandler: requirePermission("item_manager"),
385
+ handler: async (request, reply) => handleFieldUpdate(request, reply, request.params.setIndex),
386
+ });
387
+ // DELETE a field value set
388
+ app.delete("/:instanceId/sets/:setIndex", {
389
+ schema: {
390
+ description: "Delete all field values for a set and re-index remaining sets",
391
+ tags: ["Item Instances"],
392
+ params: SetIndexParamsSchema,
393
+ response: {
394
+ 200: DeleteSetMutateResponseSchema,
395
+ 404: ErrorResponseSchema,
396
+ },
397
+ },
398
+ preHandler: requirePermission("item_manager"),
399
+ handler: async (request, reply) => {
400
+ const { instanceId, setIndex } = request.params;
401
+ const existing = await findItemInstance(instanceId);
402
+ if (!existing)
403
+ return notFound(reply, `Item instance ${instanceId} not found`);
404
+ if (!existing.fieldRecord) {
405
+ return notFound(reply, "No field values to delete");
406
+ }
407
+ await deleteFieldValueSet(existing.fieldRecord.id, setIndex);
408
+ const inst = await findItemInstance(instanceId);
409
+ if (!inst)
410
+ return notFound(reply, `Item instance ${instanceId} not found`);
411
+ if (wantsFullResponse(request)) {
412
+ useFullSerializer(reply);
413
+ return formatInstance(inst, request.erpUser);
414
+ }
415
+ // Compute set count from remaining field values
416
+ const storedFieldValues = inst.fieldRecord?.fieldValues ?? [];
417
+ const maxSetIndex = storedFieldValues.reduce((max, fv) => Math.max(max, fv.setIndex), -1);
418
+ const setCount = Math.max(1, maxSetIndex + 1);
419
+ return {
420
+ setCount,
421
+ _actions: instanceActions(inst.item.key, inst.id, request.erpUser),
422
+ };
423
+ },
424
+ });
425
+ }
426
+ //# sourceMappingURL=item-instances.js.map
@@ -0,0 +1,3 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ export default function itemRoutes(fastify: FastifyInstance): void;
3
+ //# sourceMappingURL=items.d.ts.map