@naisys/erp 3.0.0-beta.6
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.
- package/bin/naisys-erp +2 -0
- package/client-dist/android-chrome-192x192.png +0 -0
- package/client-dist/android-chrome-512x512.png +0 -0
- package/client-dist/apple-touch-icon.png +0 -0
- package/client-dist/assets/index-45dVo30p.css +1 -0
- package/client-dist/assets/index-Dffms7F_.js +168 -0
- package/client-dist/assets/naisys-logo-CzoPnn5I.webp +0 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +42 -0
- package/client-dist/site.webmanifest +22 -0
- package/dist/api-reference.js +101 -0
- package/dist/audit.js +14 -0
- package/dist/auth-middleware.js +203 -0
- package/dist/dbConfig.js +10 -0
- package/dist/erpDb.js +34 -0
- package/dist/erpServer.js +321 -0
- package/dist/error-handler.js +17 -0
- package/dist/generated/prisma/client.js +35 -0
- package/dist/generated/prisma/commonInputTypes.js +11 -0
- package/dist/generated/prisma/enums.js +60 -0
- package/dist/generated/prisma/internal/class.js +50 -0
- package/dist/generated/prisma/internal/prismaNamespace.js +366 -0
- package/dist/generated/prisma/models/Attachment.js +2 -0
- package/dist/generated/prisma/models/AuditLog.js +2 -0
- package/dist/generated/prisma/models/Field.js +2 -0
- package/dist/generated/prisma/models/FieldAttachment.js +2 -0
- package/dist/generated/prisma/models/FieldRecord.js +2 -0
- package/dist/generated/prisma/models/FieldSet.js +2 -0
- package/dist/generated/prisma/models/FieldValue.js +2 -0
- package/dist/generated/prisma/models/Item.js +2 -0
- package/dist/generated/prisma/models/ItemInstance.js +2 -0
- package/dist/generated/prisma/models/LaborTicket.js +2 -0
- package/dist/generated/prisma/models/Operation.js +2 -0
- package/dist/generated/prisma/models/OperationDependency.js +2 -0
- package/dist/generated/prisma/models/OperationFieldRef.js +2 -0
- package/dist/generated/prisma/models/OperationRun.js +2 -0
- package/dist/generated/prisma/models/OperationRunComment.js +2 -0
- package/dist/generated/prisma/models/Order.js +2 -0
- package/dist/generated/prisma/models/OrderRevision.js +2 -0
- package/dist/generated/prisma/models/OrderRun.js +2 -0
- package/dist/generated/prisma/models/SchemaVersion.js +2 -0
- package/dist/generated/prisma/models/Session.js +2 -0
- package/dist/generated/prisma/models/Step.js +2 -0
- package/dist/generated/prisma/models/StepRun.js +2 -0
- package/dist/generated/prisma/models/User.js +2 -0
- package/dist/generated/prisma/models/UserPermission.js +2 -0
- package/dist/generated/prisma/models/WorkCenter.js +2 -0
- package/dist/generated/prisma/models/WorkCenterUser.js +2 -0
- package/dist/generated/prisma/models.js +2 -0
- package/dist/hateoas.js +61 -0
- package/dist/route-helpers.js +220 -0
- package/dist/routes/admin.js +147 -0
- package/dist/routes/audit.js +36 -0
- package/dist/routes/auth.js +112 -0
- package/dist/routes/dispatch.js +174 -0
- package/dist/routes/inventory.js +70 -0
- package/dist/routes/item-fields.js +220 -0
- package/dist/routes/item-instances.js +426 -0
- package/dist/routes/items.js +252 -0
- package/dist/routes/labor-tickets.js +268 -0
- package/dist/routes/operation-dependencies.js +170 -0
- package/dist/routes/operation-field-refs.js +263 -0
- package/dist/routes/operation-run-comments.js +108 -0
- package/dist/routes/operation-run-transitions.js +249 -0
- package/dist/routes/operation-runs.js +299 -0
- package/dist/routes/operations.js +283 -0
- package/dist/routes/order-revision-transitions.js +86 -0
- package/dist/routes/order-revisions.js +327 -0
- package/dist/routes/order-run-transitions.js +215 -0
- package/dist/routes/order-runs.js +335 -0
- package/dist/routes/orders.js +262 -0
- package/dist/routes/root.js +123 -0
- package/dist/routes/schemas.js +31 -0
- package/dist/routes/step-field-attachments.js +231 -0
- package/dist/routes/step-fields.js +315 -0
- package/dist/routes/step-run-fields.js +438 -0
- package/dist/routes/step-run-transitions.js +113 -0
- package/dist/routes/step-runs.js +324 -0
- package/dist/routes/steps.js +283 -0
- package/dist/routes/user-permissions.js +100 -0
- package/dist/routes/users.js +381 -0
- package/dist/routes/work-centers.js +280 -0
- package/dist/schema-registry.js +45 -0
- package/dist/services/attachment-service.js +118 -0
- package/dist/services/field-ref-service.js +74 -0
- package/dist/services/field-service.js +114 -0
- package/dist/services/field-value-service.js +256 -0
- package/dist/services/item-instance-service.js +155 -0
- package/dist/services/item-service.js +56 -0
- package/dist/services/labor-ticket-service.js +148 -0
- package/dist/services/log-file-service.js +11 -0
- package/dist/services/operation-dependency-service.js +30 -0
- package/dist/services/operation-run-comment-service.js +26 -0
- package/dist/services/operation-run-service.js +347 -0
- package/dist/services/operation-service.js +132 -0
- package/dist/services/order-revision-service.js +264 -0
- package/dist/services/order-run-service.js +356 -0
- package/dist/services/order-service.js +68 -0
- package/dist/services/revision-diff-service.js +194 -0
- package/dist/services/step-run-service.js +106 -0
- package/dist/services/step-service.js +89 -0
- package/dist/services/user-service.js +132 -0
- package/dist/services/work-center-service.js +106 -0
- package/dist/supervisorAuth.js +16 -0
- package/dist/userService.js +118 -0
- package/package.json +75 -0
- package/prisma/migrations/20260212170352_init/migration.sql +125 -0
- package/prisma/migrations/20260308000000_multi_session/migration.sql +23 -0
- package/prisma/migrations/20260309000000_add_user_api_key/migration.sql +5 -0
- package/prisma/migrations/20260309010000_add_plan_operations/migration.sql +21 -0
- package/prisma/migrations/20260309020000_rename_exec_orders_to_order_runs/migration.sql +13 -0
- package/prisma/migrations/20260310000000_rename_plan_order_revs_to_order_revisions/migration.sql +22 -0
- package/prisma/migrations/20260310100000_rename_plan_orders_to_orders/migration.sql +23 -0
- package/prisma/migrations/20260310200000_rename_order_no_to_run_no/migration.sql +3 -0
- package/prisma/migrations/20260312000000_add_user_permissions/migration.sql +16 -0
- package/prisma/migrations/20260313000000_rename_plan_operations_to_operations/migration.sql +2 -0
- package/prisma/migrations/20260313100000_add_steps/migration.sql +20 -0
- package/prisma/migrations/20260314000000_add_step_fields/migration.sql +22 -0
- package/prisma/migrations/20260315000000_add_operation_runs/migration.sql +24 -0
- package/prisma/migrations/20260315100000_add_step_runs/migration.sql +40 -0
- package/prisma/migrations/20260316000000_drop_order_name/migration.sql +12 -0
- package/prisma/migrations/20260317000000_add_attachments/migration.sql +28 -0
- package/prisma/migrations/20260317000000_add_items/migration.sql +21 -0
- package/prisma/migrations/20260317100000_add_order_item_id/migration.sql +8 -0
- package/prisma/migrations/20260318000000_add_labor_tickets/migration.sql +27 -0
- package/prisma/migrations/20260319000000_add_operation_dependencies/migration.sql +17 -0
- package/prisma/migrations/20260320000000_step_field_is_array/migration.sql +5 -0
- package/prisma/migrations/20260320100000_rename_is_array_to_multi_value/migration.sql +2 -0
- package/prisma/migrations/20260320200000_add_field_types/migration.sql +2 -0
- package/prisma/migrations/20260321000000_add_multi_set/migration.sql +13 -0
- package/prisma/migrations/20260322000000_add_field_sets/migration.sql +90 -0
- package/prisma/migrations/20260323000000_add_item_instances/migration.sql +18 -0
- package/prisma/migrations/20260324000000_add_field_records/migration.sql +77 -0
- package/prisma/migrations/20260325000000_add_run_costs/migration.sql +3 -0
- package/prisma/migrations/20260326000000_add_comments/migration.sql +16 -0
- package/prisma/migrations/20260327000000_move_assigned_to_op_run/migration.sql +3 -0
- package/prisma/migrations/20260328000000_add_step_completion_note/migration.sql +2 -0
- package/prisma/migrations/20260328000000_add_step_title/migration.sql +2 -0
- package/prisma/migrations/20260329000000_rename_notes_to_release_note/migration.sql +2 -0
- package/prisma/migrations/20260329000000_simplify_order_run_dates/migration.sql +5 -0
- package/prisma/migrations/20260330000000_add_operation_run_completion_note/migration.sql +2 -0
- package/prisma/migrations/20260331000000_add_work_centers/migration.sql +30 -0
- package/prisma/migrations/20260401000000_fix_field_values_column_shift/migration.sql +26 -0
- package/prisma/migrations/20260402000000_rename_completion_note_to_status_note/migration.sql +5 -0
- package/prisma/migrations/20260403000000_add_operation_field_refs/migration.sql +16 -0
- package/prisma/migrations/20260404000000_rename_multi_value_to_is_array/migration.sql +2 -0
- package/prisma/migrations/20260404100000_add_attachment_public_id/migration.sql +8 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +595 -0
- package/prisma.config.ts +18 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { AssignWorkCenterUserSchema, CreateWorkCenterSchema, ErrorResponseSchema, KeyCreateResponseSchema, MutateResponseSchema, UpdateWorkCenterSchema, WorkCenterListQuerySchema, WorkCenterListResponseSchema, WorkCenterSchema, } from "@naisys/erp-shared";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
import { hasPermission, requirePermission } from "../auth-middleware.js";
|
|
4
|
+
import { notFound } from "../error-handler.js";
|
|
5
|
+
import { API_PREFIX, collectionLink, paginationLinks, schemaLink, selfLink, } from "../hateoas.js";
|
|
6
|
+
import { formatAuditFields, mutationResult } from "../route-helpers.js";
|
|
7
|
+
import { assignUser, createWorkCenter, deleteWorkCenter, findExisting, listWorkCenters, removeUser, updateWorkCenter, } from "../services/work-center-service.js";
|
|
8
|
+
const RESOURCE = "work-centers";
|
|
9
|
+
const KeyParamsSchema = z.object({
|
|
10
|
+
key: z.string(),
|
|
11
|
+
});
|
|
12
|
+
const UserParamsSchema = z.object({
|
|
13
|
+
key: z.string(),
|
|
14
|
+
username: z.string(),
|
|
15
|
+
});
|
|
16
|
+
function wcLinks(key) {
|
|
17
|
+
return [
|
|
18
|
+
selfLink(`/${RESOURCE}/${key}`),
|
|
19
|
+
collectionLink(RESOURCE),
|
|
20
|
+
schemaLink("WorkCenter"),
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
function wcActions(key, user) {
|
|
24
|
+
if (!hasPermission(user, "erp_admin"))
|
|
25
|
+
return [];
|
|
26
|
+
const href = `${API_PREFIX}/${RESOURCE}/${key}`;
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
rel: "update",
|
|
30
|
+
href,
|
|
31
|
+
method: "PUT",
|
|
32
|
+
title: "Update",
|
|
33
|
+
schema: `${API_PREFIX}/schemas/UpdateWorkCenter`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
rel: "delete",
|
|
37
|
+
href,
|
|
38
|
+
method: "DELETE",
|
|
39
|
+
title: "Delete",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
rel: "assignUser",
|
|
43
|
+
href: `${href}/users`,
|
|
44
|
+
method: "POST",
|
|
45
|
+
title: "Assign User",
|
|
46
|
+
schema: `${API_PREFIX}/schemas/AssignWorkCenterUser`,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
function formatWorkCenter(wc, user) {
|
|
51
|
+
const isAdmin = hasPermission(user, "erp_admin");
|
|
52
|
+
return {
|
|
53
|
+
id: wc.id,
|
|
54
|
+
key: wc.key,
|
|
55
|
+
description: wc.description,
|
|
56
|
+
userAssignments: wc.userAssignments.map((a) => ({
|
|
57
|
+
userId: a.user.id,
|
|
58
|
+
username: a.user.username,
|
|
59
|
+
createdAt: a.createdAt.toISOString(),
|
|
60
|
+
createdBy: a.createdBy?.username ?? null,
|
|
61
|
+
_actions: isAdmin
|
|
62
|
+
? [
|
|
63
|
+
{
|
|
64
|
+
rel: "remove",
|
|
65
|
+
href: `${API_PREFIX}/${RESOURCE}/${wc.key}/users/${a.user.username}`,
|
|
66
|
+
method: "DELETE",
|
|
67
|
+
title: "Remove",
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
: [],
|
|
71
|
+
})),
|
|
72
|
+
...formatAuditFields(wc),
|
|
73
|
+
_links: wcLinks(wc.key),
|
|
74
|
+
_actions: wcActions(wc.key, user),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function formatListItem(wc) {
|
|
78
|
+
return {
|
|
79
|
+
id: wc.id,
|
|
80
|
+
key: wc.key,
|
|
81
|
+
description: wc.description,
|
|
82
|
+
userCount: wc._count.userAssignments,
|
|
83
|
+
...formatAuditFields(wc),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export default function workCenterRoutes(fastify) {
|
|
87
|
+
const app = fastify.withTypeProvider();
|
|
88
|
+
// LIST
|
|
89
|
+
app.get("/", {
|
|
90
|
+
schema: {
|
|
91
|
+
description: "List work centers with pagination and search",
|
|
92
|
+
tags: ["Work Centers"],
|
|
93
|
+
querystring: WorkCenterListQuerySchema,
|
|
94
|
+
response: {
|
|
95
|
+
200: WorkCenterListResponseSchema,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
handler: async (request) => {
|
|
99
|
+
const { page, pageSize, search } = request.query;
|
|
100
|
+
const where = {};
|
|
101
|
+
if (search) {
|
|
102
|
+
where.OR = [
|
|
103
|
+
{ key: { contains: search } },
|
|
104
|
+
{ description: { contains: search } },
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
const [items, total] = await listWorkCenters(where, page, pageSize);
|
|
108
|
+
return {
|
|
109
|
+
items: items.map((wc) => formatListItem(wc)),
|
|
110
|
+
total,
|
|
111
|
+
page,
|
|
112
|
+
pageSize,
|
|
113
|
+
_links: paginationLinks(RESOURCE, page, pageSize, total, { search }),
|
|
114
|
+
_linkTemplates: [
|
|
115
|
+
{
|
|
116
|
+
rel: "item",
|
|
117
|
+
hrefTemplate: `${API_PREFIX}/work-centers/{key}`,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
_actions: hasPermission(request.erpUser, "erp_admin")
|
|
121
|
+
? [
|
|
122
|
+
{
|
|
123
|
+
rel: "create",
|
|
124
|
+
href: `${API_PREFIX}/${RESOURCE}`,
|
|
125
|
+
method: "POST",
|
|
126
|
+
title: "Create Work Center",
|
|
127
|
+
schema: `${API_PREFIX}/schemas/CreateWorkCenter`,
|
|
128
|
+
},
|
|
129
|
+
]
|
|
130
|
+
: [],
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
// CREATE
|
|
135
|
+
app.post("/", {
|
|
136
|
+
schema: {
|
|
137
|
+
description: "Create a new work center",
|
|
138
|
+
tags: ["Work Centers"],
|
|
139
|
+
body: CreateWorkCenterSchema,
|
|
140
|
+
response: {
|
|
141
|
+
201: KeyCreateResponseSchema,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
preHandler: requirePermission("erp_admin"),
|
|
145
|
+
handler: async (request, reply) => {
|
|
146
|
+
const { key, description } = request.body;
|
|
147
|
+
const userId = request.erpUser.id;
|
|
148
|
+
const wc = await createWorkCenter(key, description, userId);
|
|
149
|
+
const full = formatWorkCenter(wc, request.erpUser);
|
|
150
|
+
reply.status(201);
|
|
151
|
+
return mutationResult(request, reply, full, {
|
|
152
|
+
id: full.id,
|
|
153
|
+
key: full.key,
|
|
154
|
+
_links: full._links,
|
|
155
|
+
_actions: full._actions,
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
// GET by key
|
|
160
|
+
app.get("/:key", {
|
|
161
|
+
schema: {
|
|
162
|
+
description: "Get a single work center by key",
|
|
163
|
+
tags: ["Work Centers"],
|
|
164
|
+
params: KeyParamsSchema,
|
|
165
|
+
response: {
|
|
166
|
+
200: WorkCenterSchema,
|
|
167
|
+
404: ErrorResponseSchema,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
handler: async (request, reply) => {
|
|
171
|
+
const { key } = request.params;
|
|
172
|
+
const wc = await findExisting(key);
|
|
173
|
+
if (!wc) {
|
|
174
|
+
return notFound(reply, `Work center '${key}' not found`);
|
|
175
|
+
}
|
|
176
|
+
return formatWorkCenter(wc, request.erpUser);
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
// UPDATE
|
|
180
|
+
app.put("/:key", {
|
|
181
|
+
schema: {
|
|
182
|
+
description: "Update a work center",
|
|
183
|
+
tags: ["Work Centers"],
|
|
184
|
+
params: KeyParamsSchema,
|
|
185
|
+
body: UpdateWorkCenterSchema,
|
|
186
|
+
response: {
|
|
187
|
+
200: MutateResponseSchema,
|
|
188
|
+
404: ErrorResponseSchema,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
preHandler: requirePermission("erp_admin"),
|
|
192
|
+
handler: async (request, reply) => {
|
|
193
|
+
const { key } = request.params;
|
|
194
|
+
const data = request.body;
|
|
195
|
+
const userId = request.erpUser.id;
|
|
196
|
+
const existing = await findExisting(key);
|
|
197
|
+
if (!existing) {
|
|
198
|
+
return notFound(reply, `Work center '${key}' not found`);
|
|
199
|
+
}
|
|
200
|
+
const wc = await updateWorkCenter(key, data, userId);
|
|
201
|
+
const full = formatWorkCenter(wc, request.erpUser);
|
|
202
|
+
return mutationResult(request, reply, full, {
|
|
203
|
+
_actions: full._actions,
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
// DELETE
|
|
208
|
+
app.delete("/:key", {
|
|
209
|
+
schema: {
|
|
210
|
+
description: "Delete a work center",
|
|
211
|
+
tags: ["Work Centers"],
|
|
212
|
+
params: KeyParamsSchema,
|
|
213
|
+
response: {
|
|
214
|
+
204: z.void(),
|
|
215
|
+
404: ErrorResponseSchema,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
preHandler: requirePermission("erp_admin"),
|
|
219
|
+
handler: async (request, reply) => {
|
|
220
|
+
const { key } = request.params;
|
|
221
|
+
const existing = await findExisting(key);
|
|
222
|
+
if (!existing) {
|
|
223
|
+
return notFound(reply, `Work center '${key}' not found`);
|
|
224
|
+
}
|
|
225
|
+
await deleteWorkCenter(key);
|
|
226
|
+
reply.status(204);
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
// ASSIGN USER
|
|
230
|
+
app.post("/:key/users", {
|
|
231
|
+
schema: {
|
|
232
|
+
description: "Assign a user to a work center",
|
|
233
|
+
tags: ["Work Centers"],
|
|
234
|
+
params: KeyParamsSchema,
|
|
235
|
+
body: AssignWorkCenterUserSchema,
|
|
236
|
+
response: {
|
|
237
|
+
200: MutateResponseSchema,
|
|
238
|
+
404: ErrorResponseSchema,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
preHandler: requirePermission("erp_admin"),
|
|
242
|
+
handler: async (request, reply) => {
|
|
243
|
+
const { key } = request.params;
|
|
244
|
+
const { username } = request.body;
|
|
245
|
+
const userId = request.erpUser.id;
|
|
246
|
+
const existing = await findExisting(key);
|
|
247
|
+
if (!existing) {
|
|
248
|
+
return notFound(reply, `Work center '${key}' not found`);
|
|
249
|
+
}
|
|
250
|
+
const wc = await assignUser(key, username, userId);
|
|
251
|
+
const full = formatWorkCenter(wc, request.erpUser);
|
|
252
|
+
return mutationResult(request, reply, full, {
|
|
253
|
+
_actions: full._actions,
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
// REMOVE USER
|
|
258
|
+
app.delete("/:key/users/:username", {
|
|
259
|
+
schema: {
|
|
260
|
+
description: "Remove a user from a work center",
|
|
261
|
+
tags: ["Work Centers"],
|
|
262
|
+
params: UserParamsSchema,
|
|
263
|
+
response: {
|
|
264
|
+
204: z.void(),
|
|
265
|
+
404: ErrorResponseSchema,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
preHandler: requirePermission("erp_admin"),
|
|
269
|
+
handler: async (request, reply) => {
|
|
270
|
+
const { key, username } = request.params;
|
|
271
|
+
const existing = await findExisting(key);
|
|
272
|
+
if (!existing) {
|
|
273
|
+
return notFound(reply, `Work center '${key}' not found`);
|
|
274
|
+
}
|
|
275
|
+
await removeUser(key, username);
|
|
276
|
+
reply.status(204);
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=work-centers.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { AssignWorkCenterUserSchema, BatchCreateFieldSchema, BatchCreateStepSchema, BatchUpdateFieldValuesSchema, ChangePasswordSchema, ClockOutLaborTicketSchema, CompleteOrderRunSchema, CreateAgentUserSchema, CreateFieldRefSchema, CreateFieldSchema, CreateItemInstanceSchema, CreateItemSchema, CreateOperationDependencySchema, CreateOperationRunCommentSchema, CreateOperationSchema, CreateOrderRevisionSchema, CreateOrderRunSchema, CreateOrderSchema, CreateStepSchema, CreateUserSchema, CreateWorkCenterSchema, GrantPermissionSchema, LoginRequestSchema, TransitionNoteSchema, UpdateFieldSchema, UpdateFieldValueSchema, UpdateItemInstanceSchema, UpdateItemSchema, UpdateOperationRunSchema, UpdateOperationSchema, UpdateOrderRevisionSchema, UpdateOrderRunSchema, UpdateOrderSchema, UpdateStepSchema, UpdateUserSchema, UpdateWorkCenterSchema, } from "@naisys/erp-shared";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
export const schemaRegistry = {
|
|
4
|
+
CreateItem: CreateItemSchema,
|
|
5
|
+
UpdateItem: UpdateItemSchema,
|
|
6
|
+
CreateItemInstance: CreateItemInstanceSchema,
|
|
7
|
+
UpdateItemInstance: UpdateItemInstanceSchema,
|
|
8
|
+
CreateOrder: CreateOrderSchema,
|
|
9
|
+
UpdateOrder: UpdateOrderSchema,
|
|
10
|
+
CreateOrderRevision: CreateOrderRevisionSchema,
|
|
11
|
+
UpdateOrderRevision: UpdateOrderRevisionSchema,
|
|
12
|
+
CreateOrderRun: CreateOrderRunSchema,
|
|
13
|
+
UpdateOrderRun: UpdateOrderRunSchema,
|
|
14
|
+
CompleteOrderRun: CompleteOrderRunSchema,
|
|
15
|
+
CreateOperation: CreateOperationSchema,
|
|
16
|
+
CreateFieldRef: CreateFieldRefSchema,
|
|
17
|
+
CreateOperationDependency: CreateOperationDependencySchema,
|
|
18
|
+
UpdateOperation: UpdateOperationSchema,
|
|
19
|
+
TransitionNote: TransitionNoteSchema,
|
|
20
|
+
UpdateOperationRun: UpdateOperationRunSchema,
|
|
21
|
+
CreateOperationRunComment: CreateOperationRunCommentSchema,
|
|
22
|
+
CreateStep: CreateStepSchema,
|
|
23
|
+
BatchCreateStep: BatchCreateStepSchema,
|
|
24
|
+
UpdateStep: UpdateStepSchema,
|
|
25
|
+
CreateField: CreateFieldSchema,
|
|
26
|
+
BatchCreateField: BatchCreateFieldSchema,
|
|
27
|
+
UpdateField: UpdateFieldSchema,
|
|
28
|
+
UpdateFieldValue: UpdateFieldValueSchema,
|
|
29
|
+
BatchUpdateFieldValues: BatchUpdateFieldValuesSchema,
|
|
30
|
+
LoginRequest: LoginRequestSchema,
|
|
31
|
+
CreateUser: CreateUserSchema,
|
|
32
|
+
CreateAgentUser: CreateAgentUserSchema,
|
|
33
|
+
UpdateUser: UpdateUserSchema,
|
|
34
|
+
GrantPermission: GrantPermissionSchema,
|
|
35
|
+
ChangePassword: ChangePasswordSchema,
|
|
36
|
+
ClockOutLaborTicket: ClockOutLaborTicketSchema,
|
|
37
|
+
CreateWorkCenter: CreateWorkCenterSchema,
|
|
38
|
+
UpdateWorkCenter: UpdateWorkCenterSchema,
|
|
39
|
+
AssignWorkCenterUser: AssignWorkCenterUserSchema,
|
|
40
|
+
};
|
|
41
|
+
// Register schemas with Zod global registry for OpenAPI components/schemas population
|
|
42
|
+
for (const [name, schema] of Object.entries(schemaRegistry)) {
|
|
43
|
+
z.globalRegistry.add(schema, { id: name });
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=schema-registry.js.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { MAX_ATTACHMENT_SIZE } from "@naisys/common";
|
|
2
|
+
import { createHash, randomBytes } from "crypto";
|
|
3
|
+
import { createWriteStream, existsSync, mkdirSync, renameSync, unlinkSync, } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import erpDb from "../erpDb.js";
|
|
6
|
+
function attachmentsDir() {
|
|
7
|
+
return join(process.env.NAISYS_FOLDER || "", "attachments");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Store a file buffer as a content-addressable attachment under
|
|
11
|
+
* attachments/erp/<first2>/<next2>/<fullhash>
|
|
12
|
+
* and create the DB records (Attachment + FieldAttachment).
|
|
13
|
+
*/
|
|
14
|
+
export async function uploadAttachment(fileBuffer, filename, uploadedById, fieldValueId) {
|
|
15
|
+
if (fileBuffer.length === 0) {
|
|
16
|
+
throw new Error("Empty file");
|
|
17
|
+
}
|
|
18
|
+
if (fileBuffer.length > MAX_ATTACHMENT_SIZE) {
|
|
19
|
+
throw new Error(`File too large. Max size: ${MAX_ATTACHMENT_SIZE} bytes`);
|
|
20
|
+
}
|
|
21
|
+
const fileHash = createHash("sha256").update(fileBuffer).digest("hex");
|
|
22
|
+
// Write to temp, then move to content-addressable path
|
|
23
|
+
const tmpDir = join(attachmentsDir(), "tmp");
|
|
24
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
25
|
+
const tmpPath = join(tmpDir, `${Date.now()}_${uploadedById}_${Math.random().toString(36).slice(2)}`);
|
|
26
|
+
const ws = createWriteStream(tmpPath);
|
|
27
|
+
await new Promise((resolve, reject) => {
|
|
28
|
+
ws.on("finish", resolve);
|
|
29
|
+
ws.on("error", reject);
|
|
30
|
+
ws.end(fileBuffer);
|
|
31
|
+
});
|
|
32
|
+
const storageDir = join(attachmentsDir(), "erp", fileHash.slice(0, 2), fileHash.slice(2, 4));
|
|
33
|
+
mkdirSync(storageDir, { recursive: true });
|
|
34
|
+
const storagePath = join(storageDir, fileHash);
|
|
35
|
+
if (existsSync(storagePath)) {
|
|
36
|
+
try {
|
|
37
|
+
unlinkSync(tmpPath);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* ignore */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
renameSync(tmpPath, storagePath);
|
|
45
|
+
}
|
|
46
|
+
// Create Attachment + FieldAttachment in a transaction
|
|
47
|
+
const attachment = await erpDb.$transaction(async (tx) => {
|
|
48
|
+
const att = await tx.attachment.create({
|
|
49
|
+
data: {
|
|
50
|
+
publicId: randomBytes(8).toString("base64url").slice(0, 10),
|
|
51
|
+
filepath: storagePath,
|
|
52
|
+
filename,
|
|
53
|
+
fileSize: fileBuffer.length,
|
|
54
|
+
fileHash,
|
|
55
|
+
uploadedById,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
await tx.fieldAttachment.create({
|
|
59
|
+
data: {
|
|
60
|
+
fieldValueId,
|
|
61
|
+
attachmentId: att.id,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return att;
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
attachmentId: attachment.publicId,
|
|
68
|
+
filename: attachment.filename,
|
|
69
|
+
fileSize: attachment.fileSize,
|
|
70
|
+
fileHash: attachment.fileHash,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* List attachments for a field value.
|
|
75
|
+
*/
|
|
76
|
+
export async function listAttachmentsForFieldValue(fieldValueId) {
|
|
77
|
+
const links = await erpDb.fieldAttachment.findMany({
|
|
78
|
+
where: { fieldValueId },
|
|
79
|
+
include: {
|
|
80
|
+
attachment: {
|
|
81
|
+
select: { publicId: true, filename: true, fileSize: true },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
return links.map((l) => ({
|
|
86
|
+
id: l.attachment.publicId,
|
|
87
|
+
filename: l.attachment.filename,
|
|
88
|
+
fileSize: l.attachment.fileSize,
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get an attachment's file path for download.
|
|
93
|
+
*/
|
|
94
|
+
export async function getAttachmentFilePath(publicId) {
|
|
95
|
+
const att = await erpDb.attachment.findUnique({
|
|
96
|
+
where: { publicId },
|
|
97
|
+
select: { filepath: true, filename: true },
|
|
98
|
+
});
|
|
99
|
+
return att;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Delete a field attachment link. Does NOT delete the file on disk
|
|
103
|
+
* (other records may reference the same content-addressable file).
|
|
104
|
+
*/
|
|
105
|
+
export async function deleteFieldAttachment(fieldValueId, publicId) {
|
|
106
|
+
const att = await erpDb.attachment.findUnique({
|
|
107
|
+
where: { publicId },
|
|
108
|
+
select: { id: true },
|
|
109
|
+
});
|
|
110
|
+
if (!att)
|
|
111
|
+
throw new Error("Attachment not found");
|
|
112
|
+
await erpDb.fieldAttachment.delete({
|
|
113
|
+
where: {
|
|
114
|
+
fieldValueId_attachmentId: { fieldValueId, attachmentId: att.id },
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=attachment-service.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import erpDb from "../erpDb.js";
|
|
2
|
+
const includeFieldRef = {
|
|
3
|
+
sourceStep: {
|
|
4
|
+
select: {
|
|
5
|
+
seqNo: true,
|
|
6
|
+
title: true,
|
|
7
|
+
operation: { select: { seqNo: true, title: true } },
|
|
8
|
+
fieldSet: {
|
|
9
|
+
select: {
|
|
10
|
+
fields: {
|
|
11
|
+
select: { seqNo: true, label: true, type: true },
|
|
12
|
+
orderBy: { seqNo: "asc" },
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
createdBy: { select: { username: true } },
|
|
19
|
+
};
|
|
20
|
+
export async function listFieldRefs(operationId) {
|
|
21
|
+
return erpDb.operationFieldRef.findMany({
|
|
22
|
+
where: { operationId },
|
|
23
|
+
include: includeFieldRef,
|
|
24
|
+
orderBy: { seqNo: "asc" },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export async function getFieldRef(operationId, seqNo) {
|
|
28
|
+
return erpDb.operationFieldRef.findFirst({
|
|
29
|
+
where: { operationId, seqNo },
|
|
30
|
+
include: includeFieldRef,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export async function createFieldRef(operationId, seqNo, title, sourceStepId, userId) {
|
|
34
|
+
// Auto-assign seqNo if not provided
|
|
35
|
+
let assignedSeqNo = seqNo;
|
|
36
|
+
if (!assignedSeqNo) {
|
|
37
|
+
const maxRow = await erpDb.operationFieldRef.findFirst({
|
|
38
|
+
where: { operationId },
|
|
39
|
+
orderBy: { seqNo: "desc" },
|
|
40
|
+
select: { seqNo: true },
|
|
41
|
+
});
|
|
42
|
+
const maxSeq = maxRow?.seqNo ?? 0;
|
|
43
|
+
assignedSeqNo = Math.ceil((maxSeq + 1) / 10) * 10;
|
|
44
|
+
}
|
|
45
|
+
return erpDb.operationFieldRef.create({
|
|
46
|
+
data: {
|
|
47
|
+
operationId,
|
|
48
|
+
seqNo: assignedSeqNo,
|
|
49
|
+
title,
|
|
50
|
+
sourceStepId,
|
|
51
|
+
createdById: userId,
|
|
52
|
+
},
|
|
53
|
+
include: includeFieldRef,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export async function deleteFieldRef(id) {
|
|
57
|
+
await erpDb.operationFieldRef.delete({ where: { id } });
|
|
58
|
+
}
|
|
59
|
+
export async function findExistingFieldRef(operationId, seqNo) {
|
|
60
|
+
return erpDb.operationFieldRef.findFirst({
|
|
61
|
+
where: { operationId, seqNo },
|
|
62
|
+
select: { id: true },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if a source step already has a field ref for this operation.
|
|
67
|
+
*/
|
|
68
|
+
export async function checkDuplicateSource(operationId, sourceStepId) {
|
|
69
|
+
return erpDb.operationFieldRef.findFirst({
|
|
70
|
+
where: { operationId, sourceStepId },
|
|
71
|
+
select: { id: true },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=field-ref-service.js.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { FieldType } from "@naisys/erp-shared";
|
|
2
|
+
import erpDb from "../erpDb.js";
|
|
3
|
+
import { calcNextSeqNo, includeUsers, } from "../route-helpers.js";
|
|
4
|
+
// --- Lookups ---
|
|
5
|
+
export async function listFields(fieldSetId) {
|
|
6
|
+
return erpDb.field.findMany({
|
|
7
|
+
where: { fieldSetId },
|
|
8
|
+
include: includeUsers,
|
|
9
|
+
orderBy: { seqNo: "asc" },
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export async function getField(fieldSetId, fieldSeqNo) {
|
|
13
|
+
return erpDb.field.findFirst({
|
|
14
|
+
where: { fieldSetId, seqNo: fieldSeqNo },
|
|
15
|
+
include: includeUsers,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export async function findExistingField(fieldSetId, fieldSeqNo) {
|
|
19
|
+
return erpDb.field.findFirst({
|
|
20
|
+
where: { fieldSetId, seqNo: fieldSeqNo },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
// --- FieldSet / FieldRecord helpers ---
|
|
24
|
+
export async function ensureFieldSet(fieldSetId, userId) {
|
|
25
|
+
if (fieldSetId)
|
|
26
|
+
return fieldSetId;
|
|
27
|
+
const fs = await erpDb.fieldSet.create({
|
|
28
|
+
data: { createdById: userId },
|
|
29
|
+
});
|
|
30
|
+
return fs.id;
|
|
31
|
+
}
|
|
32
|
+
export async function ensureFieldRecord(fieldRecordId, fieldSetId, userId) {
|
|
33
|
+
if (fieldRecordId)
|
|
34
|
+
return fieldRecordId;
|
|
35
|
+
const fr = await erpDb.fieldRecord.create({
|
|
36
|
+
data: { fieldSetId, createdById: userId },
|
|
37
|
+
});
|
|
38
|
+
return fr.id;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get or create a FieldRecord for a StepRun, linking it back.
|
|
42
|
+
* Returns the fieldRecordId, or null if the step has no fieldSet.
|
|
43
|
+
*/
|
|
44
|
+
export async function ensureStepRunFieldRecord(stepRunId, userId) {
|
|
45
|
+
const sr = await erpDb.stepRun.findUniqueOrThrow({
|
|
46
|
+
where: { id: stepRunId },
|
|
47
|
+
select: { fieldRecordId: true, step: { select: { fieldSetId: true } } },
|
|
48
|
+
});
|
|
49
|
+
if (sr.fieldRecordId)
|
|
50
|
+
return sr.fieldRecordId;
|
|
51
|
+
if (!sr.step.fieldSetId)
|
|
52
|
+
return null;
|
|
53
|
+
const fieldRecordId = await ensureFieldRecord(null, sr.step.fieldSetId, userId);
|
|
54
|
+
await erpDb.stepRun.update({
|
|
55
|
+
where: { id: stepRunId },
|
|
56
|
+
data: { fieldRecordId },
|
|
57
|
+
});
|
|
58
|
+
return fieldRecordId;
|
|
59
|
+
}
|
|
60
|
+
// --- Mutations ---
|
|
61
|
+
export async function createFields(fieldSetId, items, userId) {
|
|
62
|
+
return erpDb.$transaction(async (erpTx) => {
|
|
63
|
+
const maxSeq = await erpTx.field.findFirst({
|
|
64
|
+
where: { fieldSetId },
|
|
65
|
+
orderBy: { seqNo: "desc" },
|
|
66
|
+
select: { seqNo: true },
|
|
67
|
+
});
|
|
68
|
+
let nextSeqNo = calcNextSeqNo(maxSeq?.seqNo ?? 0);
|
|
69
|
+
const created = [];
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
const seqNo = item.seqNo ?? nextSeqNo;
|
|
72
|
+
const field = await erpTx.field.create({
|
|
73
|
+
data: {
|
|
74
|
+
fieldSetId,
|
|
75
|
+
seqNo,
|
|
76
|
+
label: item.label,
|
|
77
|
+
type: item.type ?? FieldType.string,
|
|
78
|
+
isArray: item.isArray ?? false,
|
|
79
|
+
required: item.required ?? false,
|
|
80
|
+
createdById: userId,
|
|
81
|
+
updatedById: userId,
|
|
82
|
+
},
|
|
83
|
+
include: includeUsers,
|
|
84
|
+
});
|
|
85
|
+
created.push(field);
|
|
86
|
+
if (!item.seqNo) {
|
|
87
|
+
nextSeqNo = calcNextSeqNo(seqNo);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return created;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export async function createField(fieldSetId, data, userId) {
|
|
94
|
+
const [field] = await createFields(fieldSetId, [data], userId);
|
|
95
|
+
return field;
|
|
96
|
+
}
|
|
97
|
+
export async function updateField(id, data, userId) {
|
|
98
|
+
return erpDb.field.update({
|
|
99
|
+
where: { id },
|
|
100
|
+
data: {
|
|
101
|
+
...(data.label !== undefined ? { label: data.label } : {}),
|
|
102
|
+
...(data.type !== undefined ? { type: data.type } : {}),
|
|
103
|
+
...(data.isArray !== undefined ? { isArray: data.isArray } : {}),
|
|
104
|
+
...(data.required !== undefined ? { required: data.required } : {}),
|
|
105
|
+
...(data.seqNo !== undefined ? { seqNo: data.seqNo } : {}),
|
|
106
|
+
updatedById: userId,
|
|
107
|
+
},
|
|
108
|
+
include: includeUsers,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export async function deleteField(id) {
|
|
112
|
+
await erpDb.field.delete({ where: { id } });
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=field-service.js.map
|