@naisys/erp 3.0.0-beta.8 → 3.0.0
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/.env.example +5 -0
- package/client-dist/assets/index-CSiMTJfw.css +14 -0
- package/client-dist/assets/index-D6lSIioV.js +11167 -0
- package/client-dist/assets/rolldown-runtime-CvHMtSRF.js +33 -0
- package/client-dist/assets/vendor-CJ0ET9hP.js +75181 -0
- package/client-dist/assets/vendor-CLUPjUnv.css +8747 -0
- package/client-dist/favicon-16x16.png +0 -0
- package/client-dist/favicon-32x32.png +0 -0
- package/client-dist/favicon.ico +0 -0
- package/client-dist/index.html +17 -2
- package/dist/{dbConfig.js → database/dbConfig.js} +1 -1
- package/dist/{erpDb.js → database/erpDb.js} +1 -1
- package/dist/erpRoutes.js +115 -0
- package/dist/erpServer.js +85 -161
- package/dist/error-handler.js +3 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.js +3 -1
- package/dist/middleware/auth-middleware.js +146 -0
- package/dist/route-helpers.js +2 -2
- package/dist/routes/admin.js +15 -7
- package/dist/routes/audit.js +1 -1
- package/dist/routes/{item-fields.js → items/item-fields.js} +24 -22
- package/dist/routes/{item-instances.js → items/item-instances.js} +42 -24
- package/dist/routes/{items.js → items/items.js} +35 -33
- package/dist/routes/{operation-dependencies.js → operations/operation-dependencies.js} +6 -6
- package/dist/routes/{operation-field-refs.js → operations/operation-field-refs.js} +6 -6
- package/dist/routes/{operation-run-comments.js → operations/operation-run-comments.js} +5 -5
- package/dist/routes/{operation-run-transitions.js → operations/operation-run-transitions.js} +29 -13
- package/dist/routes/{operation-runs.js → operations/operation-runs.js} +48 -10
- package/dist/routes/{operations.js → operations/operations.js} +6 -6
- package/dist/routes/{order-revision-transitions.js → orders/order-revision-transitions.js} +4 -4
- package/dist/routes/{order-revisions.js → orders/order-revisions.js} +6 -6
- package/dist/routes/{order-run-transitions.js → orders/order-run-transitions.js} +11 -5
- package/dist/routes/{order-runs.js → orders/order-runs.js} +7 -5
- package/dist/routes/{orders.js → orders/orders.js} +15 -11
- package/dist/routes/{dispatch.js → production/dispatch.js} +88 -7
- package/dist/routes/{inventory.js → production/inventory.js} +33 -10
- package/dist/routes/{labor-tickets.js → production/labor-tickets.js} +7 -7
- package/dist/routes/{work-centers.js → production/work-centers.js} +29 -29
- package/dist/routes/root.js +1 -1
- package/dist/routes/{step-field-attachments.js → steps/step-field-attachments.js} +8 -8
- package/dist/routes/{step-fields.js → steps/step-fields.js} +6 -6
- package/dist/routes/{step-run-fields.js → steps/step-run-fields.js} +9 -9
- package/dist/routes/{step-run-transitions.js → steps/step-run-transitions.js} +6 -6
- package/dist/routes/{step-runs.js → steps/step-runs.js} +7 -7
- package/dist/routes/{steps.js → steps/steps.js} +5 -5
- package/dist/routes/{auth.js → users/auth.js} +11 -23
- package/dist/routes/{user-permissions.js → users/user-permissions.js} +21 -7
- package/dist/routes/{users.js → users/users.js} +43 -21
- package/dist/services/attachment-service.js +2 -2
- package/dist/services/{item-instance-service.js → inventory/item-instance-service.js} +2 -2
- package/dist/services/{item-service.js → inventory/item-service.js} +2 -2
- package/dist/services/{operation-dependency-service.js → operations/operation-dependency-service.js} +1 -1
- package/dist/services/{operation-run-comment-service.js → operations/operation-run-comment-service.js} +1 -1
- package/dist/services/{operation-run-service.js → operations/operation-run-service.js} +15 -4
- package/dist/services/{operation-service.js → operations/operation-service.js} +2 -2
- package/dist/services/{step-run-service.js → operations/step-run-service.js} +1 -1
- package/dist/services/{step-service.js → operations/step-service.js} +2 -2
- package/dist/services/{order-revision-service.js → orders/order-revision-service.js} +4 -5
- package/dist/services/{order-run-service.js → orders/order-run-service.js} +68 -22
- package/dist/services/{order-service.js → orders/order-service.js} +11 -2
- package/dist/services/{revision-diff-service.js → orders/revision-diff-service.js} +11 -10
- package/dist/services/{field-ref-service.js → production/field-ref-service.js} +1 -1
- package/dist/services/{field-service.js → production/field-service.js} +2 -2
- package/dist/services/{field-value-service.js → production/field-value-service.js} +27 -3
- package/dist/services/production/labor-ticket-backfill.js +67 -0
- package/dist/services/{labor-ticket-service.js → production/labor-ticket-service.js} +21 -15
- package/dist/services/{work-center-service.js → production/work-center-service.js} +2 -2
- package/dist/services/user-service.js +94 -28
- package/dist/version.js +12 -0
- package/npm-shrinkwrap.json +3000 -0
- package/package.json +11 -9
- package/prisma/migrations/20260427010000_hash_user_api_keys/migration.sql +10 -0
- package/prisma/migrations/20260427020000_nullable_user_password_hash/migration.sql +39 -0
- package/prisma/migrations/20260517000000_add_op_run_tokens/migration.sql +2 -0
- package/prisma/schema.prisma +4 -2
- package/client-dist/assets/index-45dVo30p.css +0 -1
- package/client-dist/assets/index-C9uuPHLH.js +0 -168
- package/dist/auth-middleware.js +0 -203
- package/dist/userService.js +0 -118
- /package/bin/{naisys-erp → naisys-erp.js} +0 -0
- /package/dist/{supervisorAuth.js → middleware/supervisorAuth.js} +0 -0
- /package/dist/{audit.js → services/audit.js} +0 -0
|
@@ -225,6 +225,7 @@ export const OperationRunScalarFieldEnum = {
|
|
|
225
225
|
status: 'status',
|
|
226
226
|
assignedToId: 'assignedToId',
|
|
227
227
|
cost: 'cost',
|
|
228
|
+
tokens: 'tokens',
|
|
228
229
|
statusNote: 'statusNote',
|
|
229
230
|
completedAt: 'completedAt',
|
|
230
231
|
createdAt: 'createdAt',
|
|
@@ -282,7 +283,7 @@ export const UserScalarFieldEnum = {
|
|
|
282
283
|
uuid: 'uuid',
|
|
283
284
|
username: 'username',
|
|
284
285
|
passwordHash: 'passwordHash',
|
|
285
|
-
|
|
286
|
+
apiKeyHash: 'apiKeyHash',
|
|
286
287
|
isAgent: 'isAgent',
|
|
287
288
|
createdAt: 'createdAt',
|
|
288
289
|
updatedAt: 'updatedAt',
|
|
@@ -315,6 +316,7 @@ export const LaborTicketScalarFieldEnum = {
|
|
|
315
316
|
clockIn: 'clockIn',
|
|
316
317
|
clockOut: 'clockOut',
|
|
317
318
|
cost: 'cost',
|
|
319
|
+
tokens: 'tokens',
|
|
318
320
|
createdAt: 'createdAt',
|
|
319
321
|
createdById: 'createdById',
|
|
320
322
|
updatedAt: 'updatedAt',
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { AuthCache, urlMatchesPrefix } from "@naisys/common";
|
|
2
|
+
import { extractBearerToken, hashToken, SESSION_COOKIE_NAME, } from "@naisys/common-node";
|
|
3
|
+
import { findAgentByApiKey } from "@naisys/hub-database";
|
|
4
|
+
import { findSession, findUserByApiKey } from "@naisys/supervisor-database";
|
|
5
|
+
import erpDb from "../database/erpDb.js";
|
|
6
|
+
import { isSupervisorAuth } from "./supervisorAuth.js";
|
|
7
|
+
const PUBLIC_PREFIXES = ["/erp/api/auth/login", "/erp/api/client-config"];
|
|
8
|
+
export const authCache = new AuthCache();
|
|
9
|
+
async function loadPermissions(userId) {
|
|
10
|
+
const perms = await erpDb.userPermission.findMany({
|
|
11
|
+
where: { userId },
|
|
12
|
+
select: { permission: true },
|
|
13
|
+
});
|
|
14
|
+
return perms.map((p) => p.permission);
|
|
15
|
+
}
|
|
16
|
+
async function materializeErpUser(localUser) {
|
|
17
|
+
return {
|
|
18
|
+
id: localUser.id,
|
|
19
|
+
username: localUser.username,
|
|
20
|
+
permissions: await loadPermissions(localUser.id),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function resolveCookie(token) {
|
|
24
|
+
const tokenHash = hashToken(token);
|
|
25
|
+
return authCache.getOrLoad(`cookie:${tokenHash}`, async () => {
|
|
26
|
+
const localUser = isSupervisorAuth()
|
|
27
|
+
? await loadCookieUserSso(tokenHash)
|
|
28
|
+
: await loadCookieUserStandalone(tokenHash);
|
|
29
|
+
return localUser ? materializeErpUser(localUser) : null;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function loadCookieUserSso(tokenHash) {
|
|
33
|
+
const session = await findSession(tokenHash);
|
|
34
|
+
if (!session)
|
|
35
|
+
return null;
|
|
36
|
+
return erpDb.user.upsert({
|
|
37
|
+
where: { uuid: session.uuid },
|
|
38
|
+
create: { uuid: session.uuid, username: session.username },
|
|
39
|
+
update: {},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function loadCookieUserStandalone(tokenHash) {
|
|
43
|
+
const session = await erpDb.session.findUnique({
|
|
44
|
+
where: { tokenHash, expiresAt: { gt: new Date() } },
|
|
45
|
+
include: { user: true },
|
|
46
|
+
});
|
|
47
|
+
return session?.user ?? null;
|
|
48
|
+
}
|
|
49
|
+
async function resolveApiKey(apiKey) {
|
|
50
|
+
const apiKeyHash = hashToken(apiKey);
|
|
51
|
+
return authCache.getOrLoad(`apikey:${apiKeyHash}`, async () => {
|
|
52
|
+
const localUser = isSupervisorAuth()
|
|
53
|
+
? await loadApiKeyUserSso(apiKey)
|
|
54
|
+
: await erpDb.user.findUnique({ where: { apiKeyHash } });
|
|
55
|
+
return localUser ? materializeErpUser(localUser) : null;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function loadApiKeyUserSso(apiKey) {
|
|
59
|
+
// Try supervisor DB (humans + agents with external keys),
|
|
60
|
+
// then hub DB (agents matching their hub-issued runtime key).
|
|
61
|
+
const supervisorUser = await findUserByApiKey(apiKey);
|
|
62
|
+
const hubAgent = supervisorUser ? null : await findAgentByApiKey(apiKey);
|
|
63
|
+
const match = supervisorUser ?? hubAgent;
|
|
64
|
+
if (!match)
|
|
65
|
+
return null;
|
|
66
|
+
const isAgent = supervisorUser?.isAgent ?? !!hubAgent;
|
|
67
|
+
return erpDb.user.upsert({
|
|
68
|
+
where: { uuid: match.uuid },
|
|
69
|
+
create: { uuid: match.uuid, username: match.username, isAgent },
|
|
70
|
+
update: {},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function hasPermission(user, permission) {
|
|
74
|
+
if (!user)
|
|
75
|
+
return false;
|
|
76
|
+
return (user.permissions.includes(permission) ||
|
|
77
|
+
user.permissions.includes("erp_admin"));
|
|
78
|
+
}
|
|
79
|
+
export function requirePermission(permission) {
|
|
80
|
+
return async (request, reply) => {
|
|
81
|
+
if (!request.erpUser) {
|
|
82
|
+
reply.status(401).send({
|
|
83
|
+
statusCode: 401,
|
|
84
|
+
error: "Unauthorized",
|
|
85
|
+
message: "Authentication required",
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!hasPermission(request.erpUser, permission)) {
|
|
90
|
+
reply.status(403).send({
|
|
91
|
+
statusCode: 403,
|
|
92
|
+
error: "Forbidden",
|
|
93
|
+
message: `Permission '${permission}' required`,
|
|
94
|
+
missingPermission: permission,
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function isPublicRoute(url) {
|
|
101
|
+
// Exact match: API root
|
|
102
|
+
if (url === "/erp/api/" || url === "/erp/api")
|
|
103
|
+
return true;
|
|
104
|
+
for (const prefix of PUBLIC_PREFIXES) {
|
|
105
|
+
if (urlMatchesPrefix(url, prefix))
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (urlMatchesPrefix(url, "/erp/api/schemas"))
|
|
109
|
+
return true;
|
|
110
|
+
// Non-ERP-API paths (static files, supervisor routes, etc.)
|
|
111
|
+
if (!url.startsWith("/erp/api"))
|
|
112
|
+
return true;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
export function registerAuthMiddleware(fastify) {
|
|
116
|
+
const publicRead = process.env.PUBLIC_READ === "true";
|
|
117
|
+
fastify.decorateRequest("erpUser", undefined);
|
|
118
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
119
|
+
const token = request.cookies?.[SESSION_COOKIE_NAME];
|
|
120
|
+
if (token) {
|
|
121
|
+
const user = await resolveCookie(token);
|
|
122
|
+
if (user)
|
|
123
|
+
request.erpUser = user;
|
|
124
|
+
}
|
|
125
|
+
if (!request.erpUser) {
|
|
126
|
+
const apiKey = extractBearerToken(request.headers.authorization);
|
|
127
|
+
if (apiKey) {
|
|
128
|
+
const user = await resolveApiKey(apiKey);
|
|
129
|
+
if (user)
|
|
130
|
+
request.erpUser = user;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (request.erpUser)
|
|
134
|
+
return; // Authenticated, always allowed
|
|
135
|
+
if (isPublicRoute(request.url))
|
|
136
|
+
return; // Public route
|
|
137
|
+
if (publicRead && request.method === "GET")
|
|
138
|
+
return; // Public read mode
|
|
139
|
+
reply.status(401).send({
|
|
140
|
+
statusCode: 401,
|
|
141
|
+
error: "Unauthorized",
|
|
142
|
+
message: "Authentication required",
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=auth-middleware.js.map
|
package/dist/route-helpers.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { permGate, resolveActions as resolveActionsBase, } from "@naisys/common";
|
|
2
2
|
import { OperationRunStatus, OrderRunStatus, RevisionStatus, } from "@naisys/erp-shared";
|
|
3
|
-
import
|
|
4
|
-
import erpDb from "./erpDb.js";
|
|
3
|
+
import erpDb from "./database/erpDb.js";
|
|
5
4
|
import { API_PREFIX, schemaLink, selfLink } from "./hateoas.js";
|
|
5
|
+
import { hasPermission } from "./middleware/auth-middleware.js";
|
|
6
6
|
// --- Prefer: return=representation (RFC 7240) ---
|
|
7
7
|
/**
|
|
8
8
|
* Returns true when the caller wants the full entity in the mutation response.
|
package/dist/routes/admin.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { AdminAttachmentListRequestSchema, AdminAttachmentListResponseSchema, AdminInfoResponseSchema, ErrorResponseSchema, ServerLogRequestSchema, ServerLogResponseSchema, } from "@naisys/erp-shared";
|
|
4
|
+
import { getHubVariable } from "@naisys/hub-database";
|
|
4
5
|
import { z } from "zod/v4";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import erpDb from "../erpDb.js";
|
|
6
|
+
import { ERP_DB_VERSION, erpDbPath } from "../database/dbConfig.js";
|
|
7
|
+
import erpDb from "../database/erpDb.js";
|
|
8
8
|
import { notFound } from "../error-handler.js";
|
|
9
9
|
import { paginationLinks } from "../hateoas.js";
|
|
10
|
+
import { hasPermission, requirePermission, } from "../middleware/auth-middleware.js";
|
|
10
11
|
import { getErpLogPath, tailLogFile } from "../services/log-file-service.js";
|
|
12
|
+
import { getPackageVersion } from "../version.js";
|
|
11
13
|
const API_PREFIX = "/erp/api";
|
|
12
14
|
function adminActions(hasAdminPermission) {
|
|
13
15
|
const actions = [];
|
|
@@ -44,13 +46,19 @@ export default function adminRoutes(fastify, _options) {
|
|
|
44
46
|
const hasAdminPerm = hasPermission(request.erpUser, "erp_admin");
|
|
45
47
|
const actions = adminActions(hasAdminPerm);
|
|
46
48
|
const dbPath = erpDbPath();
|
|
47
|
-
const erpDbSize = await
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const [erpDbSize, targetVersion] = await Promise.all([
|
|
50
|
+
fs
|
|
51
|
+
.stat(dbPath)
|
|
52
|
+
.then((s) => s.size)
|
|
53
|
+
.catch(() => undefined),
|
|
54
|
+
getHubVariable("TARGET_VERSION"),
|
|
55
|
+
]);
|
|
51
56
|
return {
|
|
57
|
+
erpVersion: getPackageVersion(),
|
|
52
58
|
erpDbPath: dbPath,
|
|
53
59
|
erpDbSize,
|
|
60
|
+
erpDbVersion: ERP_DB_VERSION,
|
|
61
|
+
targetVersion: targetVersion || undefined,
|
|
54
62
|
_actions: actions.length > 0 ? actions : undefined,
|
|
55
63
|
};
|
|
56
64
|
});
|
package/dist/routes/audit.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CreateFieldSchema, ErrorResponseSchema, FieldListResponseSchema, FieldSchema, MutateResponseSchema, SeqNoCreateResponseSchema, UpdateFieldSchema, } from "@naisys/erp-shared";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, } from "
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
3
|
+
import erpDb from "../../database/erpDb.js";
|
|
4
|
+
import { notFound } from "../../error-handler.js";
|
|
5
|
+
import { API_PREFIX, selfLink } from "../../hateoas.js";
|
|
6
|
+
import { hasPermission, requirePermission, } from "../../middleware/auth-middleware.js";
|
|
7
|
+
import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, permGate, } from "../../route-helpers.js";
|
|
8
|
+
import { findExisting as findExistingItem } from "../../services/inventory/item-service.js";
|
|
9
|
+
import { createField, deleteField, ensureFieldSet, findExistingField, getField, listFields, updateField, } from "../../services/production/field-service.js";
|
|
10
10
|
const ParamsSchema = z.object({ key: z.string() });
|
|
11
11
|
const FieldParamsSchema = z.object({
|
|
12
12
|
key: z.string(),
|
|
@@ -27,23 +27,26 @@ function formatField(key, user, field) {
|
|
|
27
27
|
required: field.required,
|
|
28
28
|
...formatAuditFields(field),
|
|
29
29
|
_links: childItemLinks(base, field.seqNo, "Fields", `/items/${key}`, "Item", "Field"),
|
|
30
|
-
_actions:
|
|
31
|
-
|
|
30
|
+
_actions: (() => {
|
|
31
|
+
const gate = permGate(hasPermission(user, "item_manager"), "item_manager");
|
|
32
|
+
return [
|
|
32
33
|
{
|
|
33
34
|
rel: "update",
|
|
34
35
|
href: `${API_PREFIX}${base}/${field.seqNo}`,
|
|
35
36
|
method: "PUT",
|
|
36
37
|
title: "Update",
|
|
37
38
|
schema: `${API_PREFIX}/schemas/UpdateField`,
|
|
39
|
+
...gate,
|
|
38
40
|
},
|
|
39
41
|
{
|
|
40
42
|
rel: "delete",
|
|
41
43
|
href: `${API_PREFIX}${base}/${field.seqNo}`,
|
|
42
44
|
method: "DELETE",
|
|
43
45
|
title: "Delete",
|
|
46
|
+
...gate,
|
|
44
47
|
},
|
|
45
|
-
]
|
|
46
|
-
|
|
48
|
+
];
|
|
49
|
+
})(),
|
|
47
50
|
};
|
|
48
51
|
}
|
|
49
52
|
export default function itemFieldRoutes(fastify) {
|
|
@@ -81,17 +84,16 @@ export default function itemFieldRoutes(fastify) {
|
|
|
81
84
|
hrefTemplate: `${API_PREFIX}${base}/{seqNo}`,
|
|
82
85
|
},
|
|
83
86
|
],
|
|
84
|
-
_actions:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
: [],
|
|
87
|
+
_actions: [
|
|
88
|
+
{
|
|
89
|
+
rel: "create",
|
|
90
|
+
href: `${API_PREFIX}${base}`,
|
|
91
|
+
method: "POST",
|
|
92
|
+
title: "Add Field",
|
|
93
|
+
schema: `${API_PREFIX}/schemas/CreateField`,
|
|
94
|
+
...permGate(hasPermission(request.erpUser, "item_manager"), "item_manager"),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
95
97
|
};
|
|
96
98
|
},
|
|
97
99
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CreateItemInstanceSchema, DeleteSetMutateResponseSchema, ErrorResponseSchema, fieldTypeString, FieldValueMutateResponseSchema, getValueFormatHint, ItemInstanceListQuerySchema, ItemInstanceListResponseSchema, ItemInstanceSchema, KeyCreateResponseSchema, MutateResponseSchema, UpdateFieldValueSchema, UpdateItemInstanceSchema, } from "@naisys/erp-shared";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { formatAuditFields, mutationResult, useFullSerializer, wantsFullResponse, } from "
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
3
|
+
import { notFound, unprocessable } from "../../error-handler.js";
|
|
4
|
+
import { API_PREFIX, paginationLinks, schemaLink, selfLink, } from "../../hateoas.js";
|
|
5
|
+
import { hasPermission, requirePermission, } from "../../middleware/auth-middleware.js";
|
|
6
|
+
import { formatAuditFields, mutationResult, permGate, useFullSerializer, wantsFullResponse, } from "../../route-helpers.js";
|
|
7
|
+
import { createItemInstance, deleteItemInstance, ensureItemInstanceFieldRecord, findItemInstance, findItemInstanceWithField, listItemInstances, updateItemInstance, } from "../../services/inventory/item-instance-service.js";
|
|
8
|
+
import { findExisting as findItem } from "../../services/inventory/item-service.js";
|
|
9
|
+
import { checkFieldValueShape, deleteFieldValueSet, deserializeFieldValue, serializeFieldValue, upsertFieldValue, validateFieldValue, } from "../../services/production/field-value-service.js";
|
|
10
10
|
const ParamsSchema = z.object({
|
|
11
11
|
key: z.string(),
|
|
12
12
|
});
|
|
@@ -59,9 +59,8 @@ function instanceLinks(itemKey, instanceId, inst) {
|
|
|
59
59
|
return links;
|
|
60
60
|
}
|
|
61
61
|
function instanceActions(itemKey, instanceId, user) {
|
|
62
|
-
if (!hasPermission(user, "item_manager"))
|
|
63
|
-
return [];
|
|
64
62
|
const href = `${API_PREFIX}/${instanceBasePath(itemKey)}/${instanceId}`;
|
|
63
|
+
const gate = permGate(hasPermission(user, "item_manager"), "item_manager");
|
|
65
64
|
return [
|
|
66
65
|
{
|
|
67
66
|
rel: "update",
|
|
@@ -70,12 +69,14 @@ function instanceActions(itemKey, instanceId, user) {
|
|
|
70
69
|
title: "Update",
|
|
71
70
|
schema: `${API_PREFIX}/schemas/UpdateItemInstance`,
|
|
72
71
|
body: { key: "" },
|
|
72
|
+
...gate,
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
rel: "delete",
|
|
76
76
|
href,
|
|
77
77
|
method: "DELETE",
|
|
78
78
|
title: "Delete",
|
|
79
|
+
...gate,
|
|
79
80
|
},
|
|
80
81
|
];
|
|
81
82
|
}
|
|
@@ -122,17 +123,35 @@ function buildFieldValues(inst) {
|
|
|
122
123
|
return fieldValues;
|
|
123
124
|
}
|
|
124
125
|
function buildActionTemplates(itemKey, instanceId, user, hasFields) {
|
|
125
|
-
if (!
|
|
126
|
+
if (!hasFields)
|
|
126
127
|
return [];
|
|
127
128
|
const instanceHref = `${API_PREFIX}/${instanceBasePath(itemKey)}/${instanceId}`;
|
|
129
|
+
const gate = permGate(hasPermission(user, "item_manager"), "item_manager");
|
|
128
130
|
return [
|
|
129
131
|
{
|
|
130
|
-
rel: "
|
|
132
|
+
rel: "update-field-value",
|
|
131
133
|
hrefTemplate: `${instanceHref}/fields/{fieldSeqNo}`,
|
|
132
134
|
method: "PUT",
|
|
133
|
-
title: "Update Field Value",
|
|
135
|
+
title: "Update Field Value (implicit set 0)",
|
|
136
|
+
schema: `${API_PREFIX}/schemas/UpdateFieldValue`,
|
|
137
|
+
body: { value: "" },
|
|
138
|
+
...gate,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
rel: "update-set-field-value",
|
|
142
|
+
hrefTemplate: `${instanceHref}/sets/{setIndex}/fields/{fieldSeqNo}`,
|
|
143
|
+
method: "PUT",
|
|
144
|
+
title: "Update Field Value (explicit set index)",
|
|
134
145
|
schema: `${API_PREFIX}/schemas/UpdateFieldValue`,
|
|
135
146
|
body: { value: "" },
|
|
147
|
+
...gate,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
rel: "delete-set",
|
|
151
|
+
hrefTemplate: `${instanceHref}/sets/{setIndex}`,
|
|
152
|
+
method: "DELETE",
|
|
153
|
+
title: "Delete Field Value Set",
|
|
154
|
+
...gate,
|
|
136
155
|
},
|
|
137
156
|
];
|
|
138
157
|
}
|
|
@@ -202,18 +221,17 @@ export default function itemInstanceRoutes(fastify) {
|
|
|
202
221
|
hrefTemplate: `${API_PREFIX}/items/${key}/instances/{id}`,
|
|
203
222
|
},
|
|
204
223
|
],
|
|
205
|
-
_actions:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
: [],
|
|
224
|
+
_actions: [
|
|
225
|
+
{
|
|
226
|
+
rel: "create",
|
|
227
|
+
href: `${API_PREFIX}/${base}`,
|
|
228
|
+
method: "POST",
|
|
229
|
+
title: "Create Instance",
|
|
230
|
+
schema: `${API_PREFIX}/schemas/CreateItemInstance`,
|
|
231
|
+
body: { key: "" },
|
|
232
|
+
...permGate(hasPermission(request.erpUser, "item_manager"), "item_manager"),
|
|
233
|
+
},
|
|
234
|
+
],
|
|
217
235
|
};
|
|
218
236
|
},
|
|
219
237
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CreateItemSchema, ErrorResponseSchema, ItemListQuerySchema, ItemListResponseSchema, ItemSchema, KeyCreateResponseSchema, MutateResponseSchema, UpdateItemSchema, } from "@naisys/erp-shared";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, } from "
|
|
7
|
-
import { createItem, deleteItem, findExisting, listItems, updateItem, } from "
|
|
3
|
+
import { notFound } from "../../error-handler.js";
|
|
4
|
+
import { API_PREFIX, collectionLink, paginationLinks, schemaLink, selfLink, } from "../../hateoas.js";
|
|
5
|
+
import { hasPermission, requirePermission, } from "../../middleware/auth-middleware.js";
|
|
6
|
+
import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, permGate, } from "../../route-helpers.js";
|
|
7
|
+
import { createItem, deleteItem, findExisting, listItems, updateItem, } from "../../services/inventory/item-service.js";
|
|
8
8
|
const RESOURCE = "items";
|
|
9
9
|
const KeyParamsSchema = z.object({
|
|
10
10
|
key: z.string(),
|
|
@@ -17,9 +17,8 @@ function itemLinks(key) {
|
|
|
17
17
|
];
|
|
18
18
|
}
|
|
19
19
|
function itemActions(key, user) {
|
|
20
|
-
if (!hasPermission(user, "item_manager"))
|
|
21
|
-
return [];
|
|
22
20
|
const href = `${API_PREFIX}/${RESOURCE}/${key}`;
|
|
21
|
+
const gate = permGate(hasPermission(user, "item_manager"), "item_manager");
|
|
23
22
|
return [
|
|
24
23
|
{
|
|
25
24
|
rel: "update",
|
|
@@ -27,12 +26,14 @@ function itemActions(key, user) {
|
|
|
27
26
|
method: "PUT",
|
|
28
27
|
title: "Update",
|
|
29
28
|
schema: `${API_PREFIX}/schemas/UpdateItem`,
|
|
29
|
+
...gate,
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
rel: "delete",
|
|
33
33
|
href,
|
|
34
34
|
method: "DELETE",
|
|
35
35
|
title: "Delete",
|
|
36
|
+
...gate,
|
|
36
37
|
},
|
|
37
38
|
];
|
|
38
39
|
}
|
|
@@ -44,17 +45,16 @@ function formatItemFieldListResponse(itemKey, user, fields) {
|
|
|
44
45
|
total: fields.length,
|
|
45
46
|
nextSeqNo: calcNextSeqNo(maxSeq),
|
|
46
47
|
_links: [selfLink(base)],
|
|
47
|
-
_actions:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
: [],
|
|
48
|
+
_actions: [
|
|
49
|
+
{
|
|
50
|
+
rel: "create",
|
|
51
|
+
href: `${API_PREFIX}${base}`,
|
|
52
|
+
method: "POST",
|
|
53
|
+
title: "Add Field",
|
|
54
|
+
schema: `${API_PREFIX}/schemas/CreateField`,
|
|
55
|
+
...permGate(hasPermission(user, "item_manager"), "item_manager"),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
function formatItemField(itemKey, user, field) {
|
|
@@ -69,23 +69,26 @@ function formatItemField(itemKey, user, field) {
|
|
|
69
69
|
required: field.required,
|
|
70
70
|
...formatAuditFields(field),
|
|
71
71
|
_links: childItemLinks(base, field.seqNo, "Fields", `/items/${itemKey}`, "Item", "Field"),
|
|
72
|
-
_actions:
|
|
73
|
-
|
|
72
|
+
_actions: (() => {
|
|
73
|
+
const gate = permGate(hasPermission(user, "item_manager"), "item_manager");
|
|
74
|
+
return [
|
|
74
75
|
{
|
|
75
76
|
rel: "update",
|
|
76
77
|
href: `${API_PREFIX}${base}/${field.seqNo}`,
|
|
77
78
|
method: "PUT",
|
|
78
79
|
title: "Update",
|
|
79
80
|
schema: `${API_PREFIX}/schemas/UpdateField`,
|
|
81
|
+
...gate,
|
|
80
82
|
},
|
|
81
83
|
{
|
|
82
84
|
rel: "delete",
|
|
83
85
|
href: `${API_PREFIX}${base}/${field.seqNo}`,
|
|
84
86
|
method: "DELETE",
|
|
85
87
|
title: "Delete",
|
|
88
|
+
...gate,
|
|
86
89
|
},
|
|
87
|
-
]
|
|
88
|
-
|
|
90
|
+
];
|
|
91
|
+
})(),
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
94
|
function formatItem(item, user) {
|
|
@@ -139,17 +142,16 @@ export default function itemRoutes(fastify) {
|
|
|
139
142
|
_linkTemplates: [
|
|
140
143
|
{ rel: "item", hrefTemplate: `${API_PREFIX}/items/{key}` },
|
|
141
144
|
],
|
|
142
|
-
_actions:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
: [],
|
|
145
|
+
_actions: [
|
|
146
|
+
{
|
|
147
|
+
rel: "create",
|
|
148
|
+
href: `${API_PREFIX}/${RESOURCE}`,
|
|
149
|
+
method: "POST",
|
|
150
|
+
title: "Create Item",
|
|
151
|
+
schema: `${API_PREFIX}/schemas/CreateItem`,
|
|
152
|
+
...permGate(hasPermission(request.erpUser, "item_manager"), "item_manager"),
|
|
153
|
+
},
|
|
154
|
+
],
|
|
153
155
|
};
|
|
154
156
|
},
|
|
155
157
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { CreateOperationDependencySchema, CreateResponseSchema, ErrorResponseSchema, OperationDependencyListResponseSchema, RevisionStatus, } from "@naisys/erp-shared";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { mutationResult, resolveRevision } from "
|
|
7
|
-
import { createDependency, deleteDependency, listDependencies, } from "
|
|
8
|
-
import { findExisting } from "
|
|
3
|
+
import { conflict, notFound } from "../../error-handler.js";
|
|
4
|
+
import { API_PREFIX, selfLink } from "../../hateoas.js";
|
|
5
|
+
import { hasPermission, requirePermission, } from "../../middleware/auth-middleware.js";
|
|
6
|
+
import { mutationResult, resolveRevision } from "../../route-helpers.js";
|
|
7
|
+
import { createDependency, deleteDependency, listDependencies, } from "../../services/operations/operation-dependency-service.js";
|
|
8
|
+
import { findExisting } from "../../services/operations/operation-service.js";
|
|
9
9
|
const ParamsSchema = z.object({
|
|
10
10
|
orderKey: z.string(),
|
|
11
11
|
revNo: z.coerce.number().int(),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { CreateFieldRefSchema, ErrorResponseSchema, FieldRefListResponseSchema, RevisionStatus, SeqNoCreateResponseSchema, } from "@naisys/erp-shared";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { calcNextSeqNo, childItemLinks, mutationResult, resolveActions, resolveOperation, } from "
|
|
8
|
-
import { checkDuplicateSource, createFieldRef, deleteFieldRef, findExistingFieldRef, listFieldRefs, } from "
|
|
3
|
+
import erpDb from "../../database/erpDb.js";
|
|
4
|
+
import { conflict, notFound } from "../../error-handler.js";
|
|
5
|
+
import { API_PREFIX, selfLink } from "../../hateoas.js";
|
|
6
|
+
import { requirePermission } from "../../middleware/auth-middleware.js";
|
|
7
|
+
import { calcNextSeqNo, childItemLinks, mutationResult, resolveActions, resolveOperation, } from "../../route-helpers.js";
|
|
8
|
+
import { checkDuplicateSource, createFieldRef, deleteFieldRef, findExistingFieldRef, listFieldRefs, } from "../../services/production/field-ref-service.js";
|
|
9
9
|
const ParamsSchema = z.object({
|
|
10
10
|
orderKey: z.string(),
|
|
11
11
|
revNo: z.coerce.number().int(),
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CreateOperationRunCommentSchema, CreateResponseSchema, ErrorResponseSchema, OperationRunCommentListResponseSchema, OperationRunCommentType, } from "@naisys/erp-shared";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { mutationResult, resolveActions, resolveOpRun, } from "
|
|
7
|
-
import { createComment, listComments, } from "
|
|
3
|
+
import { notFound } from "../../error-handler.js";
|
|
4
|
+
import { API_PREFIX, selfLink } from "../../hateoas.js";
|
|
5
|
+
import { requirePermission } from "../../middleware/auth-middleware.js";
|
|
6
|
+
import { mutationResult, resolveActions, resolveOpRun, } from "../../route-helpers.js";
|
|
7
|
+
import { createComment, listComments, } from "../../services/operations/operation-run-comment-service.js";
|
|
8
8
|
function commentResource(orderKey, runNo, seqNo) {
|
|
9
9
|
return `orders/${orderKey}/runs/${runNo}/ops/${seqNo}/comments`;
|
|
10
10
|
}
|