@naisys/erp 3.0.0-beta.24 → 3.0.0-beta.25
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/client-dist/assets/{index-DycEn-R_.js → index-ey6Ixpe7.js} +245 -213
- package/client-dist/index.html +1 -1
- package/dist/auth-middleware.js +1 -0
- package/dist/error-handler.js +3 -0
- package/dist/routes/inventory.js +31 -8
- package/dist/routes/item-fields.js +18 -16
- package/dist/routes/item-instances.js +36 -18
- package/dist/routes/items.js +31 -29
- package/dist/routes/labor-tickets.js +1 -2
- package/dist/routes/operation-run-transitions.js +9 -2
- package/dist/routes/operation-runs.js +5 -1
- package/dist/routes/order-run-transitions.js +8 -2
- package/dist/routes/orders.js +11 -7
- package/dist/routes/users.js +1 -0
- package/dist/routes/work-centers.js +25 -25
- package/dist/services/field-value-service.js +26 -2
- package/dist/services/order-run-service.js +64 -17
- package/dist/services/order-service.js +9 -0
- package/npm-shrinkwrap.json +28 -28
- package/package.json +6 -6
package/client-dist/index.html
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<meta name="format-detection" content="telephone=no" />
|
|
34
34
|
|
|
35
35
|
<title>NAISYS ERP</title>
|
|
36
|
-
<script type="module" crossorigin src="/erp/assets/index-
|
|
36
|
+
<script type="module" crossorigin src="/erp/assets/index-ey6Ixpe7.js"></script>
|
|
37
37
|
<link rel="modulepreload" crossorigin href="/erp/assets/rolldown-runtime-CvHMtSRF.js">
|
|
38
38
|
<link rel="modulepreload" crossorigin href="/erp/assets/vendor-MNFI7PUp.js">
|
|
39
39
|
<link rel="stylesheet" crossorigin href="/erp/assets/vendor-CLUPjUnv.css">
|
package/dist/auth-middleware.js
CHANGED
package/dist/error-handler.js
CHANGED
|
@@ -2,6 +2,9 @@ function send(reply, statusCode, error, message) {
|
|
|
2
2
|
reply.status(statusCode);
|
|
3
3
|
return { statusCode, error, message };
|
|
4
4
|
}
|
|
5
|
+
export function badRequest(reply, message) {
|
|
6
|
+
return send(reply, 400, "Bad Request", message);
|
|
7
|
+
}
|
|
5
8
|
export function notFound(reply, message) {
|
|
6
9
|
return send(reply, 404, "Not Found", message);
|
|
7
10
|
}
|
package/dist/routes/inventory.js
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
import { InventoryListQuerySchema, InventoryListResponseSchema, } from "@naisys/erp-shared";
|
|
2
|
+
import { hasPermission } from "../auth-middleware.js";
|
|
2
3
|
import erpDb from "../erpDb.js";
|
|
3
4
|
import { API_PREFIX, paginationLinks } from "../hateoas.js";
|
|
5
|
+
function buildInventoryActionTemplates(user) {
|
|
6
|
+
const templates = [
|
|
7
|
+
{
|
|
8
|
+
rel: "viewInstance",
|
|
9
|
+
hrefTemplate: `${API_PREFIX}/items/{itemKey}/instances/{id}`,
|
|
10
|
+
method: "GET",
|
|
11
|
+
title: "View Instance",
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
if (hasPermission(user, "item_manager")) {
|
|
15
|
+
templates.push({
|
|
16
|
+
rel: "update-field-value",
|
|
17
|
+
hrefTemplate: `${API_PREFIX}/items/{itemKey}/instances/{id}/fields/{fieldSeqNo}`,
|
|
18
|
+
method: "PUT",
|
|
19
|
+
title: "Update Field Value (implicit set 0)",
|
|
20
|
+
schema: `${API_PREFIX}/schemas/UpdateFieldValue`,
|
|
21
|
+
body: { value: "" },
|
|
22
|
+
});
|
|
23
|
+
templates.push({
|
|
24
|
+
rel: "update-set-field-value",
|
|
25
|
+
hrefTemplate: `${API_PREFIX}/items/{itemKey}/instances/{id}/sets/{setIndex}/fields/{fieldSeqNo}`,
|
|
26
|
+
method: "PUT",
|
|
27
|
+
title: "Update Field Value (explicit set index)",
|
|
28
|
+
schema: `${API_PREFIX}/schemas/UpdateFieldValue`,
|
|
29
|
+
body: { value: "" },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return templates;
|
|
33
|
+
}
|
|
4
34
|
export default function inventoryRoutes(fastify) {
|
|
5
35
|
const app = fastify.withTypeProvider();
|
|
6
36
|
app.get("/", {
|
|
@@ -55,14 +85,7 @@ export default function inventoryRoutes(fastify) {
|
|
|
55
85
|
_links: paginationLinks("inventory", page, pageSize, total, {
|
|
56
86
|
search,
|
|
57
87
|
}),
|
|
58
|
-
_actionTemplates:
|
|
59
|
-
{
|
|
60
|
-
rel: "viewInstance",
|
|
61
|
-
hrefTemplate: `${API_PREFIX}/items/{itemKey}/instances/{id}`,
|
|
62
|
-
method: "GET",
|
|
63
|
-
title: "View Instance",
|
|
64
|
-
},
|
|
65
|
-
],
|
|
88
|
+
_actionTemplates: buildInventoryActionTemplates(request.erpUser),
|
|
66
89
|
};
|
|
67
90
|
},
|
|
68
91
|
});
|
|
@@ -4,7 +4,7 @@ import { hasPermission, requirePermission } from "../auth-middleware.js";
|
|
|
4
4
|
import erpDb from "../erpDb.js";
|
|
5
5
|
import { notFound } from "../error-handler.js";
|
|
6
6
|
import { API_PREFIX, selfLink } from "../hateoas.js";
|
|
7
|
-
import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, } from "../route-helpers.js";
|
|
7
|
+
import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, permGate, } from "../route-helpers.js";
|
|
8
8
|
import { createField, deleteField, ensureFieldSet, findExistingField, getField, listFields, updateField, } from "../services/field-service.js";
|
|
9
9
|
import { findExisting as findExistingItem } from "../services/item-service.js";
|
|
10
10
|
const ParamsSchema = z.object({ 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
|
});
|
|
@@ -3,7 +3,7 @@ import { z } from "zod/v4";
|
|
|
3
3
|
import { hasPermission, requirePermission } from "../auth-middleware.js";
|
|
4
4
|
import { notFound, unprocessable } from "../error-handler.js";
|
|
5
5
|
import { API_PREFIX, paginationLinks, schemaLink, selfLink, } from "../hateoas.js";
|
|
6
|
-
import { formatAuditFields, mutationResult, useFullSerializer, wantsFullResponse, } from "../route-helpers.js";
|
|
6
|
+
import { formatAuditFields, mutationResult, permGate, useFullSerializer, wantsFullResponse, } from "../route-helpers.js";
|
|
7
7
|
import { checkFieldValueShape, deleteFieldValueSet, deserializeFieldValue, serializeFieldValue, upsertFieldValue, validateFieldValue, } from "../services/field-value-service.js";
|
|
8
8
|
import { createItemInstance, deleteItemInstance, ensureItemInstanceFieldRecord, findItemInstance, findItemInstanceWithField, listItemInstances, updateItemInstance, } from "../services/item-instance-service.js";
|
|
9
9
|
import { findExisting as findItem } from "../services/item-service.js";
|
|
@@ -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
|
});
|
package/dist/routes/items.js
CHANGED
|
@@ -3,7 +3,7 @@ import { z } from "zod/v4";
|
|
|
3
3
|
import { hasPermission, requirePermission } from "../auth-middleware.js";
|
|
4
4
|
import { notFound } from "../error-handler.js";
|
|
5
5
|
import { API_PREFIX, collectionLink, paginationLinks, schemaLink, selfLink, } from "../hateoas.js";
|
|
6
|
-
import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, } from "../route-helpers.js";
|
|
6
|
+
import { calcNextSeqNo, childItemLinks, formatAuditFields, mutationResult, permGate, } from "../route-helpers.js";
|
|
7
7
|
import { createItem, deleteItem, findExisting, listItems, updateItem, } from "../services/item-service.js";
|
|
8
8
|
const RESOURCE = "items";
|
|
9
9
|
const KeyParamsSchema = z.object({
|
|
@@ -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
|
});
|
|
@@ -72,14 +72,13 @@ function laborTicketListActions(orderKey, runNo, seqNo, opRunStatus, user, ticke
|
|
|
72
72
|
return actions;
|
|
73
73
|
}
|
|
74
74
|
function laborTicketActionTemplates(orderKey, runNo, seqNo, user) {
|
|
75
|
-
if (!hasPermission(user, "order_manager"))
|
|
76
|
-
return [];
|
|
77
75
|
return [
|
|
78
76
|
{
|
|
79
77
|
rel: "deleteTicket",
|
|
80
78
|
hrefTemplate: `${API_PREFIX}/${laborResource(orderKey, runNo, seqNo)}/{ticketId}`,
|
|
81
79
|
method: "DELETE",
|
|
82
80
|
title: "Delete Ticket",
|
|
81
|
+
...permGate(hasPermission(user, "order_manager"), "order_manager"),
|
|
83
82
|
},
|
|
84
83
|
];
|
|
85
84
|
}
|
|
@@ -113,10 +113,11 @@ export default function operationRunTransitionRoutes(fastify) {
|
|
|
113
113
|
});
|
|
114
114
|
},
|
|
115
115
|
});
|
|
116
|
-
// SKIP (pending → skipped)
|
|
116
|
+
// SKIP (blocked/pending/in_progress → skipped)
|
|
117
117
|
app.post("/:seqNo/skip", {
|
|
118
118
|
schema: {
|
|
119
|
-
description: "Skip an operation run (pending → skipped)"
|
|
119
|
+
description: "Skip an operation run (blocked/pending/in_progress → skipped). " +
|
|
120
|
+
"When skipping an in_progress op, any open labor tickets are clocked out.",
|
|
120
121
|
tags: ["Operation Runs"],
|
|
121
122
|
params: SeqNoParamsSchema,
|
|
122
123
|
body: TransitionNoteSchema,
|
|
@@ -143,9 +144,15 @@ export default function operationRunTransitionRoutes(fastify) {
|
|
|
143
144
|
const statusErr = validateStatusFor("skip", resolved.opRun.status, [
|
|
144
145
|
OperationRunStatus.blocked,
|
|
145
146
|
OperationRunStatus.pending,
|
|
147
|
+
OperationRunStatus.in_progress,
|
|
146
148
|
]);
|
|
147
149
|
if (statusErr)
|
|
148
150
|
return conflict(reply, statusErr);
|
|
151
|
+
// If the op was in progress, close out any active labor tickets so the
|
|
152
|
+
// recorded cost is accurate before we mark the op skipped.
|
|
153
|
+
if (resolved.opRun.status === OperationRunStatus.in_progress) {
|
|
154
|
+
await clockOutAllForOpRun(resolved.opRun.id, userId);
|
|
155
|
+
}
|
|
149
156
|
const cost = await sumLaborTicketCosts(resolved.opRun.id);
|
|
150
157
|
const opRun = await transitionStatus(resolved.opRun.id, "skip", resolved.opRun.status, OperationRunStatus.skipped, userId, { ...(cost > 0 ? { cost } : undefined), statusNote: note ?? null });
|
|
151
158
|
await unblockSuccessors(resolved.run.id, resolved.opRun.operationId, userId);
|
|
@@ -76,7 +76,11 @@ async function opRunItemActions(orderKey, runNo, seqNo, opRunId, operationId, st
|
|
|
76
76
|
method: "POST",
|
|
77
77
|
title: "Skip",
|
|
78
78
|
permission: "order_manager",
|
|
79
|
-
statuses: [
|
|
79
|
+
statuses: [
|
|
80
|
+
OperationRunStatus.blocked,
|
|
81
|
+
OperationRunStatus.pending,
|
|
82
|
+
OperationRunStatus.in_progress,
|
|
83
|
+
],
|
|
80
84
|
disabledWhen: () => wcErr,
|
|
81
85
|
},
|
|
82
86
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CompleteOrderRunSchema, ErrorResponseSchema, OrderRunStatus, OrderRunTransitionSchema, } from "@naisys/erp-shared";
|
|
2
2
|
import { requirePermission } from "../auth-middleware.js";
|
|
3
|
-
import { conflict, notFound, unprocessable } from "../error-handler.js";
|
|
3
|
+
import { badRequest, conflict, notFound, unprocessable, } from "../error-handler.js";
|
|
4
4
|
import { resolveOrderRun, useFullSerializer, wantsFullResponse, } from "../route-helpers.js";
|
|
5
5
|
import { checkOpsComplete, completeOrderRun, getReopenTarget, sumOpRunCosts, transitionStatus, validateStatusFor, } from "../services/order-run-service.js";
|
|
6
6
|
import { formatRun, orderRunItemActions, RunNoParamsSchema, } from "./order-runs.js";
|
|
@@ -89,12 +89,15 @@ export default function orderRunTransitionRoutes(fastify) {
|
|
|
89
89
|
// COMPLETE (started -> closed, creates item instance)
|
|
90
90
|
app.post("/:runNo/complete", {
|
|
91
91
|
schema: {
|
|
92
|
-
description: "Complete an order run — creates an item instance and closes the run"
|
|
92
|
+
description: "Complete an order run — creates an item instance and closes the run. " +
|
|
93
|
+
"Returns 400 if any required item field is missing, or if any supplied " +
|
|
94
|
+
"fieldSeqNo doesn't exist on the item.",
|
|
93
95
|
tags: ["Order Runs"],
|
|
94
96
|
params: RunNoParamsSchema,
|
|
95
97
|
body: CompleteOrderRunSchema,
|
|
96
98
|
response: {
|
|
97
99
|
200: OrderRunTransitionSchema,
|
|
100
|
+
400: ErrorResponseSchema,
|
|
98
101
|
404: ErrorResponseSchema,
|
|
99
102
|
409: ErrorResponseSchema,
|
|
100
103
|
422: ErrorResponseSchema,
|
|
@@ -119,6 +122,9 @@ export default function orderRunTransitionRoutes(fastify) {
|
|
|
119
122
|
const userId = request.erpUser.id;
|
|
120
123
|
const result = await completeOrderRun(resolved.run.id, resolved.order.id, request.body, userId);
|
|
121
124
|
if (result.error) {
|
|
125
|
+
if (result.status === 400) {
|
|
126
|
+
return badRequest(reply, result.error);
|
|
127
|
+
}
|
|
122
128
|
return unprocessable(reply, result.error);
|
|
123
129
|
}
|
|
124
130
|
const run = result.run;
|
package/dist/routes/orders.js
CHANGED
|
@@ -4,7 +4,7 @@ import { hasPermission, requirePermission } from "../auth-middleware.js";
|
|
|
4
4
|
import { conflict, notFound } from "../error-handler.js";
|
|
5
5
|
import { API_PREFIX, collectionLink, paginationLinks, schemaLink, selfLink, } from "../hateoas.js";
|
|
6
6
|
import { formatAuditFields, mutationResult, permGate, resolveActions, } from "../route-helpers.js";
|
|
7
|
-
import { checkHasRevisions, createOrder, deleteOrder, findExisting, listOrders, resolveItemKey, updateOrder, } from "../services/order-service.js";
|
|
7
|
+
import { checkHasRevisions, createOrder, deleteOrder, findExisting, getLatestApprovedRevNo, listOrders, resolveItemKey, updateOrder, } from "../services/order-service.js";
|
|
8
8
|
function orderLinks(resource, key, schemaName) {
|
|
9
9
|
return [
|
|
10
10
|
selfLink(`/${resource}/${key}`),
|
|
@@ -64,13 +64,14 @@ const RESOURCE = "orders";
|
|
|
64
64
|
const KeyParamsSchema = z.object({
|
|
65
65
|
key: z.string(),
|
|
66
66
|
});
|
|
67
|
-
function formatOrder(order, user) {
|
|
67
|
+
function formatOrder(order, user, latestApprovedRevNo) {
|
|
68
68
|
return {
|
|
69
69
|
id: order.id,
|
|
70
70
|
key: order.key,
|
|
71
71
|
description: order.description,
|
|
72
72
|
status: order.status,
|
|
73
73
|
itemKey: order.item?.key ?? null,
|
|
74
|
+
latestApprovedRevNo,
|
|
74
75
|
...formatAuditFields(order),
|
|
75
76
|
_links: [
|
|
76
77
|
...orderLinks(RESOURCE, order.key, "Order"),
|
|
@@ -81,8 +82,8 @@ function formatOrder(order, user) {
|
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
84
|
function formatListOrder(order, user) {
|
|
84
|
-
const { _actions, ...rest } = formatOrder(order, user);
|
|
85
|
-
const { _links: _, ...withoutLinks } = rest;
|
|
85
|
+
const { _actions, ...rest } = formatOrder(order, user, null);
|
|
86
|
+
const { _links: _, latestApprovedRevNo: __, ...withoutLinks } = rest;
|
|
86
87
|
return withoutLinks;
|
|
87
88
|
}
|
|
88
89
|
export default function orderRoutes(fastify) {
|
|
@@ -159,7 +160,8 @@ export default function orderRoutes(fastify) {
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
const order = await createOrder(key, description, itemId, userId);
|
|
162
|
-
|
|
163
|
+
// Newly-created order has no revisions yet, so latestApprovedRevNo is null
|
|
164
|
+
const full = formatOrder(order, request.erpUser, null);
|
|
163
165
|
reply.status(201);
|
|
164
166
|
return mutationResult(request, reply, full, {
|
|
165
167
|
id: full.id,
|
|
@@ -186,7 +188,8 @@ export default function orderRoutes(fastify) {
|
|
|
186
188
|
if (!order) {
|
|
187
189
|
return notFound(reply, `Order '${key}' not found`);
|
|
188
190
|
}
|
|
189
|
-
|
|
191
|
+
const latestApprovedRevNo = await getLatestApprovedRevNo(order.id);
|
|
192
|
+
return formatOrder(order, request.erpUser, latestApprovedRevNo);
|
|
190
193
|
},
|
|
191
194
|
});
|
|
192
195
|
// UPDATE
|
|
@@ -225,7 +228,8 @@ export default function orderRoutes(fastify) {
|
|
|
225
228
|
}
|
|
226
229
|
}
|
|
227
230
|
const order = await updateOrder(key, dbData, userId);
|
|
228
|
-
const
|
|
231
|
+
const latestApprovedRevNo = await getLatestApprovedRevNo(order.id);
|
|
232
|
+
const full = formatOrder(order, request.erpUser, latestApprovedRevNo);
|
|
229
233
|
return mutationResult(request, reply, full, {
|
|
230
234
|
_actions: full._actions,
|
|
231
235
|
});
|
package/dist/routes/users.js
CHANGED
|
@@ -3,7 +3,7 @@ import { z } from "zod/v4";
|
|
|
3
3
|
import { hasPermission, requirePermission } from "../auth-middleware.js";
|
|
4
4
|
import { notFound } from "../error-handler.js";
|
|
5
5
|
import { API_PREFIX, collectionLink, paginationLinks, schemaLink, selfLink, } from "../hateoas.js";
|
|
6
|
-
import { formatAuditFields, mutationResult } from "../route-helpers.js";
|
|
6
|
+
import { formatAuditFields, mutationResult, permGate } from "../route-helpers.js";
|
|
7
7
|
import { assignUser, createWorkCenter, deleteWorkCenter, findExisting, listWorkCenters, removeUser, updateWorkCenter, } from "../services/work-center-service.js";
|
|
8
8
|
const RESOURCE = "work-centers";
|
|
9
9
|
const KeyParamsSchema = z.object({
|
|
@@ -21,9 +21,8 @@ function wcLinks(key) {
|
|
|
21
21
|
];
|
|
22
22
|
}
|
|
23
23
|
function wcActions(key, user) {
|
|
24
|
-
if (!hasPermission(user, "erp_admin"))
|
|
25
|
-
return [];
|
|
26
24
|
const href = `${API_PREFIX}/${RESOURCE}/${key}`;
|
|
25
|
+
const gate = permGate(hasPermission(user, "erp_admin"), "erp_admin");
|
|
27
26
|
return [
|
|
28
27
|
{
|
|
29
28
|
rel: "update",
|
|
@@ -31,12 +30,14 @@ function wcActions(key, user) {
|
|
|
31
30
|
method: "PUT",
|
|
32
31
|
title: "Update",
|
|
33
32
|
schema: `${API_PREFIX}/schemas/UpdateWorkCenter`,
|
|
33
|
+
...gate,
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
rel: "delete",
|
|
37
37
|
href,
|
|
38
38
|
method: "DELETE",
|
|
39
39
|
title: "Delete",
|
|
40
|
+
...gate,
|
|
40
41
|
},
|
|
41
42
|
{
|
|
42
43
|
rel: "assignUser",
|
|
@@ -44,11 +45,12 @@ function wcActions(key, user) {
|
|
|
44
45
|
method: "POST",
|
|
45
46
|
title: "Assign User",
|
|
46
47
|
schema: `${API_PREFIX}/schemas/AssignWorkCenterUser`,
|
|
48
|
+
...gate,
|
|
47
49
|
},
|
|
48
50
|
];
|
|
49
51
|
}
|
|
50
52
|
function formatWorkCenter(wc, user) {
|
|
51
|
-
const
|
|
53
|
+
const adminGate = permGate(hasPermission(user, "erp_admin"), "erp_admin");
|
|
52
54
|
return {
|
|
53
55
|
id: wc.id,
|
|
54
56
|
key: wc.key,
|
|
@@ -58,16 +60,15 @@ function formatWorkCenter(wc, user) {
|
|
|
58
60
|
username: a.user.username,
|
|
59
61
|
createdAt: a.createdAt.toISOString(),
|
|
60
62
|
createdBy: a.createdBy?.username ?? null,
|
|
61
|
-
_actions:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
: [],
|
|
63
|
+
_actions: [
|
|
64
|
+
{
|
|
65
|
+
rel: "remove",
|
|
66
|
+
href: `${API_PREFIX}/${RESOURCE}/${wc.key}/users/${a.user.username}`,
|
|
67
|
+
method: "DELETE",
|
|
68
|
+
title: "Remove",
|
|
69
|
+
...adminGate,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
71
72
|
})),
|
|
72
73
|
...formatAuditFields(wc),
|
|
73
74
|
_links: wcLinks(wc.key),
|
|
@@ -117,17 +118,16 @@ export default function workCenterRoutes(fastify) {
|
|
|
117
118
|
hrefTemplate: `${API_PREFIX}/work-centers/{key}`,
|
|
118
119
|
},
|
|
119
120
|
],
|
|
120
|
-
_actions:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
: [],
|
|
121
|
+
_actions: [
|
|
122
|
+
{
|
|
123
|
+
rel: "create",
|
|
124
|
+
href: `${API_PREFIX}/${RESOURCE}`,
|
|
125
|
+
method: "POST",
|
|
126
|
+
title: "Create Work Center",
|
|
127
|
+
schema: `${API_PREFIX}/schemas/CreateWorkCenter`,
|
|
128
|
+
...permGate(hasPermission(request.erpUser, "erp_admin"), "erp_admin"),
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
131
|
};
|
|
132
132
|
},
|
|
133
133
|
});
|