@naisys/erp-shared 3.0.0-beta.10

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.
@@ -0,0 +1,58 @@
1
+ import { z } from "zod/v4";
2
+ import { HateoasActionSchema, HateoasLinkSchema, HateoasLinkTemplateSchema, } from "./hateoas-types.js";
3
+ // Predecessor summary (included in list responses)
4
+ export const OperationPredecessorSchema = z.object({
5
+ seqNo: z.number(),
6
+ title: z.string(),
7
+ });
8
+ // Step summary (included in single operation GET responses)
9
+ export const StepSummarySchema = z.object({
10
+ seqNo: z.number(),
11
+ title: z.string(),
12
+ });
13
+ // Full operation response shape
14
+ export const OperationSchema = z.object({
15
+ id: z.number(),
16
+ orderRevId: z.number(),
17
+ seqNo: z.number(),
18
+ title: z.string(),
19
+ description: z.string(),
20
+ workCenterKey: z.string().nullable(),
21
+ stepCount: z.number().optional(),
22
+ stepSummary: z.array(StepSummarySchema).optional(),
23
+ predecessors: z.array(OperationPredecessorSchema).optional(),
24
+ createdAt: z.iso.datetime(),
25
+ createdBy: z.string(),
26
+ updatedAt: z.iso.datetime(),
27
+ updatedBy: z.string(),
28
+ _links: z.array(HateoasLinkSchema).optional(),
29
+ _actions: z.array(HateoasActionSchema).optional(),
30
+ });
31
+ // Input for creating an operation
32
+ export const CreateOperationSchema = z
33
+ .object({
34
+ seqNo: z.number().int().min(1).optional(),
35
+ title: z.string().min(1).max(200),
36
+ description: z.string().max(2000).optional(),
37
+ workCenterKey: z.string().max(100).nullable().optional(),
38
+ predecessorSeqNos: z.array(z.number().int().min(1)).optional(),
39
+ })
40
+ .strict();
41
+ // Input for updating an operation
42
+ export const UpdateOperationSchema = z
43
+ .object({
44
+ title: z.string().min(1).max(200).optional(),
45
+ description: z.string().max(2000).optional(),
46
+ workCenterKey: z.string().max(100).nullable().optional(),
47
+ seqNo: z.number().int().min(1).optional(),
48
+ })
49
+ .strict();
50
+ // List response
51
+ export const OperationListResponseSchema = z.object({
52
+ items: z.array(OperationSchema),
53
+ total: z.number(),
54
+ nextSeqNo: z.number(),
55
+ _links: z.array(HateoasLinkSchema),
56
+ _linkTemplates: z.array(HateoasLinkTemplateSchema).optional(),
57
+ _actions: z.array(HateoasActionSchema).optional(),
58
+ });
@@ -0,0 +1,61 @@
1
+ import { z } from "zod/v4";
2
+ import { HateoasActionSchema, HateoasLinkSchema, HateoasLinkTemplateSchema, } from "./hateoas-types.js";
3
+ // Operation summary embedded in revision GET responses
4
+ export const RevisionOperationSummarySchema = z.object({
5
+ seqNo: z.number(),
6
+ title: z.string(),
7
+ });
8
+ export const RevisionStatusEnum = z.enum(["draft", "approved", "obsolete"]);
9
+ export const RevisionStatus = RevisionStatusEnum.enum;
10
+ // Full revision response shape
11
+ export const OrderRevisionSchema = z.object({
12
+ id: z.number(),
13
+ orderId: z.number(),
14
+ revNo: z.number(),
15
+ status: RevisionStatusEnum,
16
+ description: z.string(),
17
+ changeSummary: z.string().nullable(),
18
+ itemKey: z.string().nullable(),
19
+ operationSummary: z.array(RevisionOperationSummarySchema).optional(),
20
+ createdAt: z.iso.datetime(),
21
+ createdBy: z.string(),
22
+ updatedAt: z.iso.datetime(),
23
+ updatedBy: z.string(),
24
+ _links: z.array(HateoasLinkSchema).optional(),
25
+ _actions: z.array(HateoasActionSchema).optional(),
26
+ });
27
+ // Input for creating a revision
28
+ export const CreateOrderRevisionSchema = z
29
+ .object({
30
+ description: z.string().max(2000).optional(),
31
+ changeSummary: z.string().max(2000).optional(),
32
+ })
33
+ .strict();
34
+ // Input for updating a revision
35
+ export const UpdateOrderRevisionSchema = z
36
+ .object({
37
+ description: z.string().max(2000).optional(),
38
+ changeSummary: z.string().max(2000).optional(),
39
+ })
40
+ .strict();
41
+ // Query params for listing revisions
42
+ export const OrderRevisionListQuerySchema = z.object({
43
+ page: z.coerce.number().int().min(1).optional().default(1),
44
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(20),
45
+ status: RevisionStatusEnum.optional(),
46
+ includeObsolete: z
47
+ .enum(["true", "false"])
48
+ .transform((v) => v === "true")
49
+ .optional()
50
+ .default(false),
51
+ });
52
+ // List response
53
+ export const OrderRevisionListResponseSchema = z.object({
54
+ items: z.array(OrderRevisionSchema),
55
+ total: z.number(),
56
+ page: z.number(),
57
+ pageSize: z.number(),
58
+ _links: z.array(HateoasLinkSchema),
59
+ _linkTemplates: z.array(HateoasLinkTemplateSchema).optional(),
60
+ _actions: z.array(HateoasActionSchema).optional(),
61
+ });
@@ -0,0 +1,141 @@
1
+ import { z } from "zod/v4";
2
+ import { HateoasActionSchema, HateoasActionTemplateSchema, HateoasLinkSchema, HateoasLinkTemplateSchema, } from "./hateoas-types.js";
3
+ import { OperationRunStatusEnum } from "./operation-run-types.js";
4
+ // Operation summary embedded in order run GET responses
5
+ export const OperationRunSummarySchema = z.object({
6
+ seqNo: z.number(),
7
+ title: z.string(),
8
+ status: OperationRunStatusEnum,
9
+ });
10
+ export const OrderRunStatusEnum = z.enum([
11
+ "released",
12
+ "started",
13
+ "closed",
14
+ "cancelled",
15
+ ]);
16
+ export const OrderRunStatus = OrderRunStatusEnum.enum;
17
+ export const OrderRunPriorityEnum = z.enum([
18
+ "low",
19
+ "medium",
20
+ "high",
21
+ "critical",
22
+ ]);
23
+ export const OrderRunPriority = OrderRunPriorityEnum.enum;
24
+ // Full order run response shape
25
+ export const OrderRunSchema = z.object({
26
+ id: z.number(),
27
+ runNo: z.number(),
28
+ orderId: z.number(),
29
+ orderKey: z.string(),
30
+ revNo: z.number(),
31
+ itemKey: z.string().nullable(),
32
+ instanceId: z.number().nullable(),
33
+ instanceKey: z.string().nullable(),
34
+ status: OrderRunStatusEnum,
35
+ priority: OrderRunPriorityEnum,
36
+ cost: z.number().nullable(),
37
+ dueAt: z.string().nullable(),
38
+ releaseNote: z.string().nullable(),
39
+ operationSummary: z.array(OperationRunSummarySchema).optional(),
40
+ createdAt: z.iso.datetime(),
41
+ createdBy: z.string(),
42
+ updatedAt: z.iso.datetime(),
43
+ updatedBy: z.string(),
44
+ _links: z.array(HateoasLinkSchema).optional(),
45
+ _actions: z.array(HateoasActionSchema).optional(),
46
+ });
47
+ // Input for creating an order run
48
+ export const CreateOrderRunSchema = z
49
+ .object({
50
+ revNo: z.number().int().min(1).optional(),
51
+ priority: OrderRunPriorityEnum.optional().default("medium"),
52
+ dueAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Must be YYYY-MM-DD"),
53
+ releaseNote: z.string().max(2000).optional(),
54
+ })
55
+ .strict();
56
+ // Input for updating an order run
57
+ export const UpdateOrderRunSchema = z
58
+ .object({
59
+ priority: OrderRunPriorityEnum.optional(),
60
+ dueAt: z
61
+ .string()
62
+ .regex(/^\d{4}-\d{2}-\d{2}$/, "Must be YYYY-MM-DD")
63
+ .nullable()
64
+ .optional(),
65
+ releaseNote: z.string().max(2000).nullable().optional(),
66
+ })
67
+ .strict();
68
+ // Input for completing an order run (creates item instance + closes run)
69
+ export const CompleteOrderRunSchema = z
70
+ .object({
71
+ instanceKey: z.string().max(200).optional(),
72
+ quantity: z.number().nullable().optional(),
73
+ fieldValues: z
74
+ .array(z.object({
75
+ fieldId: z.number().int(),
76
+ value: z.string().max(2000),
77
+ setIndex: z.number().int().min(0).optional(),
78
+ }))
79
+ .optional(),
80
+ })
81
+ .strict();
82
+ // Query params for listing order runs
83
+ export const OrderRunListQuerySchema = z.object({
84
+ page: z.coerce.number().int().min(1).optional().default(1),
85
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(20),
86
+ status: OrderRunStatusEnum.optional(),
87
+ priority: OrderRunPriorityEnum.optional(),
88
+ search: z.string().optional(),
89
+ });
90
+ // List response
91
+ export const OrderRunListResponseSchema = z.object({
92
+ items: z.array(OrderRunSchema),
93
+ total: z.number(),
94
+ page: z.number(),
95
+ pageSize: z.number(),
96
+ _links: z.array(HateoasLinkSchema),
97
+ _linkTemplates: z.array(HateoasLinkTemplateSchema).optional(),
98
+ _actions: z.array(HateoasActionSchema).optional(),
99
+ });
100
+ // Query params for dispatch view (operation runs across open orders)
101
+ export const DispatchListQuerySchema = z.object({
102
+ page: z.coerce.number().int().min(1).optional().default(1),
103
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(20),
104
+ status: OperationRunStatusEnum.optional(),
105
+ priority: OrderRunPriorityEnum.optional(),
106
+ search: z.string().optional(),
107
+ viewAs: z.string().optional(),
108
+ canWork: z
109
+ .union([z.literal("true"), z.literal("false")])
110
+ .transform((v) => v === "true")
111
+ .optional(),
112
+ clockedIn: z
113
+ .union([z.literal("true"), z.literal("false")])
114
+ .transform((v) => v === "true")
115
+ .optional(),
116
+ });
117
+ // Dispatch item = operation run with parent order/run context
118
+ export const DispatchItemSchema = z.object({
119
+ id: z.number(),
120
+ orderKey: z.string(),
121
+ revNo: z.number(),
122
+ runNo: z.number(),
123
+ seqNo: z.number(),
124
+ title: z.string(),
125
+ workCenterKey: z.string().nullable(),
126
+ canWork: z.boolean(),
127
+ status: OperationRunStatusEnum,
128
+ priority: OrderRunPriorityEnum,
129
+ assignedTo: z.string().nullable(),
130
+ dueAt: z.string().nullable(),
131
+ createdAt: z.iso.datetime(),
132
+ });
133
+ export const DispatchListResponseSchema = z.object({
134
+ items: z.array(DispatchItemSchema),
135
+ total: z.number(),
136
+ page: z.number(),
137
+ pageSize: z.number(),
138
+ _links: z.array(HateoasLinkSchema),
139
+ _linkTemplates: z.array(HateoasLinkTemplateSchema).optional(),
140
+ _actionTemplates: z.array(HateoasActionTemplateSchema).optional(),
141
+ });
@@ -0,0 +1,61 @@
1
+ import { z } from "zod/v4";
2
+ import { HateoasActionSchema, HateoasLinkSchema, HateoasLinkTemplateSchema, } from "./hateoas-types.js";
3
+ export const OrderStatusEnum = z.enum(["active", "archived"]);
4
+ export const OrderStatus = OrderStatusEnum.enum;
5
+ // Full order response shape
6
+ export const OrderSchema = z.object({
7
+ id: z.number(),
8
+ key: z.string(),
9
+ description: z.string(),
10
+ status: OrderStatusEnum,
11
+ itemKey: z.string().nullable(),
12
+ createdBy: z.string(),
13
+ createdAt: z.iso.datetime(),
14
+ updatedBy: z.string(),
15
+ updatedAt: z.iso.datetime(),
16
+ _links: z.array(HateoasLinkSchema).optional(),
17
+ _actions: z.array(HateoasActionSchema).optional(),
18
+ });
19
+ // Input for creating an order
20
+ export const CreateOrderSchema = z
21
+ .object({
22
+ key: z
23
+ .string()
24
+ .min(1)
25
+ .max(100)
26
+ .regex(/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/, "Key must be alphanumeric with hyphens"),
27
+ description: z.string().max(2000).optional().default(""),
28
+ itemKey: z.string().max(100).optional(),
29
+ })
30
+ .strict();
31
+ // Input for updating an order
32
+ export const UpdateOrderSchema = z
33
+ .object({
34
+ key: z
35
+ .string()
36
+ .min(1)
37
+ .max(100)
38
+ .regex(/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/, "Key must be alphanumeric with hyphens")
39
+ .optional(),
40
+ description: z.string().max(2000).optional(),
41
+ status: OrderStatusEnum.optional(),
42
+ itemKey: z.string().max(100).nullable().optional(),
43
+ })
44
+ .strict();
45
+ // Query params for listing orders
46
+ export const OrderListQuerySchema = z.object({
47
+ page: z.coerce.number().int().min(1).optional().default(1),
48
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(20),
49
+ status: z.enum(["active", "archived"]).optional(),
50
+ search: z.string().optional(),
51
+ });
52
+ // List response
53
+ export const OrderListResponseSchema = z.object({
54
+ items: z.array(OrderSchema),
55
+ total: z.number(),
56
+ page: z.number(),
57
+ pageSize: z.number(),
58
+ _links: z.array(HateoasLinkSchema),
59
+ _linkTemplates: z.array(HateoasLinkTemplateSchema).optional(),
60
+ _actions: z.array(HateoasActionSchema).optional(),
61
+ });
@@ -0,0 +1,55 @@
1
+ import { z } from "zod/v4";
2
+ // A single changed property
3
+ export const PropertyChangeSchema = z.object({
4
+ field: z.string(),
5
+ from: z.union([z.string(), z.number(), z.boolean(), z.null()]),
6
+ to: z.union([z.string(), z.number(), z.boolean(), z.null()]),
7
+ });
8
+ export const DiffStatusEnum = z.enum([
9
+ "added",
10
+ "removed",
11
+ "modified",
12
+ "unchanged",
13
+ ]);
14
+ // Field-level diff
15
+ export const FieldDiffSchema = z.object({
16
+ seqNo: z.number(),
17
+ label: z.string(),
18
+ status: DiffStatusEnum,
19
+ changes: z.array(PropertyChangeSchema).optional(),
20
+ });
21
+ // Step-level diff
22
+ export const StepDiffSchema = z.object({
23
+ seqNo: z.number(),
24
+ title: z.string(),
25
+ status: DiffStatusEnum,
26
+ changes: z.array(PropertyChangeSchema).optional(),
27
+ fields: z.array(FieldDiffSchema).optional(),
28
+ });
29
+ // Dependency diff
30
+ export const DependencyDiffSchema = z.object({
31
+ predecessorSeqNo: z.number(),
32
+ predecessorTitle: z.string(),
33
+ status: z.enum(["added", "removed", "unchanged"]),
34
+ });
35
+ // Operation-level diff
36
+ export const OperationDiffSchema = z.object({
37
+ seqNo: z.number(),
38
+ title: z.string(),
39
+ status: DiffStatusEnum,
40
+ changes: z.array(PropertyChangeSchema).optional(),
41
+ steps: z.array(StepDiffSchema).optional(),
42
+ dependencies: z.array(DependencyDiffSchema).optional(),
43
+ });
44
+ // Top-level diff response
45
+ export const RevisionDiffResponseSchema = z.object({
46
+ fromRevNo: z.number(),
47
+ toRevNo: z.number(),
48
+ revisionChanges: z.array(PropertyChangeSchema),
49
+ operations: z.array(OperationDiffSchema),
50
+ });
51
+ // Query params
52
+ export const RevisionDiffQuerySchema = z.object({
53
+ from: z.coerce.number().int().min(1),
54
+ to: z.coerce.number().int().min(1),
55
+ });
@@ -0,0 +1,148 @@
1
+ import { z } from "zod/v4";
2
+ import { HateoasActionSchema, HateoasActionTemplateSchema, HateoasLinkSchema, HateoasLinkTemplateSchema, } from "./hateoas-types.js";
3
+ // Validation result for a field value
4
+ export const FieldValidationSchema = z.object({
5
+ valid: z.boolean(),
6
+ error: z.string().optional(),
7
+ });
8
+ // Attachment metadata within a field value
9
+ export const FieldAttachmentSchema = z.object({
10
+ id: z.string(),
11
+ filename: z.string(),
12
+ fileSize: z.number(),
13
+ downloadHref: z.string().optional(),
14
+ });
15
+ // Field value: string for scalar fields, string[] for array fields (type ends with "[]")
16
+ // Coerce non-strings (e.g. numbers, booleans) to strings so callers don't
17
+ // get an opaque "Invalid input" error when they send 2024 instead of "2024".
18
+ const coercedString = z.coerce.string();
19
+ export const FieldValueSchema = z.union([
20
+ z.array(coercedString),
21
+ coercedString,
22
+ ]);
23
+ /** Human/AI-readable hint for the expected value format per field type. */
24
+ export const VALUE_FORMAT_HINTS = {
25
+ string: "any text",
26
+ number: 'numeric string, e.g. "42" or "3.14"',
27
+ date: 'YYYY-MM-DD, e.g. "2024-06-15"',
28
+ datetime: 'YYYY-MM-DDTHH:mm, e.g. "2024-06-15T09:30"',
29
+ yesNo: '"Yes" or "No"',
30
+ checkbox: '"checked" or "" (empty string to uncheck)',
31
+ attachment: "managed by file upload endpoints, not set directly",
32
+ };
33
+ /** Return the API-facing type string, appending "[]" for array fields. */
34
+ export function fieldTypeString(type, isArray) {
35
+ return isArray ? `${type}[]` : type;
36
+ }
37
+ /** Look up the valueFormat hint for a given field type (handles "[]" suffix). */
38
+ export function getValueFormatHint(type) {
39
+ const isArray = type.endsWith("[]");
40
+ const baseType = isArray ? type.slice(0, -2) : type;
41
+ const hint = VALUE_FORMAT_HINTS[baseType] ?? "any text";
42
+ if (isArray) {
43
+ return `value must be a native JSON array (not a string), e.g. ["value1", "value2"]. Each element: ${hint}`;
44
+ }
45
+ return hint;
46
+ }
47
+ // A single field value entry (API response shape)
48
+ export const FieldValueEntrySchema = z.object({
49
+ fieldId: z.number(),
50
+ fieldSeqNo: z.number(),
51
+ label: z.string(),
52
+ type: z.string(),
53
+ valueFormat: z.string(),
54
+ required: z.boolean(),
55
+ setIndex: z.number(),
56
+ value: FieldValueSchema,
57
+ attachments: z.array(FieldAttachmentSchema).optional(),
58
+ validation: FieldValidationSchema,
59
+ });
60
+ // Upload attachment response
61
+ export const UploadAttachmentResponseSchema = z.object({
62
+ attachmentId: z.string(),
63
+ filename: z.string(),
64
+ fileSize: z.number(),
65
+ });
66
+ // Full step run response shape
67
+ export const StepRunSchema = z.object({
68
+ id: z.number(),
69
+ operationRunId: z.number(),
70
+ stepId: z.number(),
71
+ seqNo: z.number(),
72
+ title: z.string(),
73
+ instructions: z.string(),
74
+ multiSet: z.boolean(),
75
+ completed: z.boolean(),
76
+ note: z.string().nullable(),
77
+ fieldCount: z.number().optional(),
78
+ fieldValues: z.array(FieldValueEntrySchema).optional(),
79
+ createdAt: z.iso.datetime(),
80
+ createdBy: z.string(),
81
+ updatedAt: z.iso.datetime(),
82
+ updatedBy: z.string(),
83
+ _links: z.array(HateoasLinkSchema).optional(),
84
+ _actions: z.array(HateoasActionSchema).optional(),
85
+ _actionTemplates: z.array(HateoasActionTemplateSchema).optional(),
86
+ });
87
+ // Single field value update (setIndex is specified via URL path, not body)
88
+ const coercedStringMax = z.coerce.string().max(2000);
89
+ export const UpdateFieldValueSchema = z
90
+ .object({
91
+ value: z.union([z.array(coercedStringMax), coercedStringMax]),
92
+ })
93
+ .strict();
94
+ // Batch field value update (setIndex is specified via URL path, not body)
95
+ export const BatchUpdateFieldValuesSchema = z
96
+ .object({
97
+ fieldValues: z.array(z.object({
98
+ fieldSeqNo: z.number().int(),
99
+ value: z.union([z.array(coercedStringMax), coercedStringMax]),
100
+ })),
101
+ })
102
+ .strict();
103
+ // Batch field value response
104
+ export const BatchFieldValueResponseSchema = z.object({
105
+ items: z.array(FieldValueEntrySchema),
106
+ total: z.number(),
107
+ });
108
+ // Response for single field value update — field entry + step-level actions
109
+ export const FieldValueUpdateResponseSchema = FieldValueEntrySchema.extend({
110
+ _actions: z.array(HateoasActionSchema).optional(),
111
+ _actionTemplates: z.array(HateoasActionTemplateSchema).optional(),
112
+ });
113
+ // Response for batch field value update — field entries + step-level actions
114
+ export const BatchFieldValueUpdateResponseSchema = BatchFieldValueResponseSchema.extend({
115
+ _actions: z.array(HateoasActionSchema).optional(),
116
+ _actionTemplates: z.array(HateoasActionTemplateSchema).optional(),
117
+ });
118
+ // Response for deleting a field value set
119
+ export const DeleteSetResponseSchema = z.object({
120
+ setCount: z.number(),
121
+ _actions: z.array(HateoasActionSchema).optional(),
122
+ _actionTemplates: z.array(HateoasActionTemplateSchema).optional(),
123
+ });
124
+ // Slim transition response (complete/reopen)
125
+ export const StepRunTransitionSchema = z.object({
126
+ id: z.number(),
127
+ completed: z.boolean(),
128
+ note: z.string().nullable(),
129
+ updatedAt: z.iso.datetime(),
130
+ updatedBy: z.string(),
131
+ _actions: z.array(HateoasActionSchema).optional(),
132
+ _actionTemplates: z.array(HateoasActionTemplateSchema).optional(),
133
+ });
134
+ // Query params for listing step runs
135
+ export const StepRunListQuerySchema = z.object({
136
+ includeFields: z
137
+ .union([z.literal("true"), z.literal("false")])
138
+ .transform((v) => v === "true")
139
+ .optional(),
140
+ });
141
+ // List response
142
+ export const StepRunListResponseSchema = z.object({
143
+ items: z.array(StepRunSchema),
144
+ total: z.number(),
145
+ _links: z.array(HateoasLinkSchema),
146
+ _linkTemplates: z.array(HateoasLinkTemplateSchema).optional(),
147
+ _actions: z.array(HateoasActionSchema).optional(),
148
+ });
@@ -0,0 +1,51 @@
1
+ import { z } from "zod/v4";
2
+ import { FieldListResponseSchema } from "./field-types.js";
3
+ import { HateoasActionSchema, HateoasLinkSchema, HateoasLinkTemplateSchema, } from "./hateoas-types.js";
4
+ // Full step response shape
5
+ export const StepSchema = z.object({
6
+ id: z.number(),
7
+ operationId: z.number(),
8
+ seqNo: z.number(),
9
+ title: z.string(),
10
+ instructions: z.string(),
11
+ multiSet: z.boolean(),
12
+ fieldCount: z.number().optional(),
13
+ createdAt: z.iso.datetime(),
14
+ createdBy: z.string(),
15
+ updatedAt: z.iso.datetime(),
16
+ updatedBy: z.string(),
17
+ fields: FieldListResponseSchema,
18
+ _links: z.array(HateoasLinkSchema).optional(),
19
+ _actions: z.array(HateoasActionSchema).optional(),
20
+ });
21
+ // Input for creating a step
22
+ export const CreateStepSchema = z
23
+ .object({
24
+ seqNo: z.number().int().min(1).optional(),
25
+ title: z.string().max(200).optional(),
26
+ instructions: z.string().max(10000).optional(),
27
+ multiSet: z.boolean().optional(),
28
+ })
29
+ .strict();
30
+ // Input for batch creating steps
31
+ export const BatchCreateStepSchema = z.object({
32
+ items: z.array(CreateStepSchema).min(1).max(100),
33
+ });
34
+ // Input for updating a step
35
+ export const UpdateStepSchema = z
36
+ .object({
37
+ seqNo: z.number().int().min(1).optional(),
38
+ title: z.string().max(200).optional(),
39
+ instructions: z.string().max(10000).optional(),
40
+ multiSet: z.boolean().optional(),
41
+ })
42
+ .strict();
43
+ // List response
44
+ export const StepListResponseSchema = z.object({
45
+ items: z.array(StepSchema),
46
+ total: z.number(),
47
+ nextSeqNo: z.number(),
48
+ _links: z.array(HateoasLinkSchema),
49
+ _linkTemplates: z.array(HateoasLinkTemplateSchema).optional(),
50
+ _actions: z.array(HateoasActionSchema).optional(),
51
+ });
@@ -0,0 +1,79 @@
1
+ import { z } from "zod/v4";
2
+ export const ErpPermissionEnum = z.enum([
3
+ "erp_admin",
4
+ "order_planner",
5
+ "order_executor",
6
+ "order_manager",
7
+ "item_manager",
8
+ ]);
9
+ export const ErpPermission = ErpPermissionEnum.enum;
10
+ const urlSafeUsername = z
11
+ .string()
12
+ .min(1)
13
+ .max(64)
14
+ .regex(/^[a-zA-Z0-9_-]+$/, "Must contain only letters, numbers, hyphens, and underscores");
15
+ export const CreateUserSchema = z
16
+ .object({
17
+ username: urlSafeUsername,
18
+ password: z.string().min(6),
19
+ })
20
+ .strict();
21
+ export const UpdateUserSchema = z
22
+ .object({
23
+ username: urlSafeUsername.optional(),
24
+ password: z.string().min(6).optional(),
25
+ })
26
+ .strict();
27
+ export const GrantPermissionSchema = z
28
+ .object({
29
+ permission: ErpPermissionEnum,
30
+ })
31
+ .strict();
32
+ export const CreateAgentUserSchema = z
33
+ .object({
34
+ agentId: z.number().int(),
35
+ })
36
+ .strict();
37
+ export const ChangePasswordSchema = z
38
+ .object({
39
+ password: z.string().min(6),
40
+ })
41
+ .strict();
42
+ export const UserPermissionSchema = z.object({
43
+ permission: ErpPermissionEnum,
44
+ grantedAt: z.string(),
45
+ grantedBy: z.number().nullable(),
46
+ _actions: z.array(z.any()).optional(),
47
+ });
48
+ export const UserSchema = z.object({
49
+ id: z.number(),
50
+ username: z.string(),
51
+ isAgent: z.boolean(),
52
+ createdAt: z.string(),
53
+ updatedAt: z.string(),
54
+ apiKey: z.string().nullable().optional(),
55
+ permissions: z.array(UserPermissionSchema),
56
+ _links: z.array(z.any()).optional(),
57
+ _actions: z.array(z.any()).optional(),
58
+ });
59
+ export const UserListItemSchema = z.object({
60
+ id: z.number(),
61
+ username: z.string(),
62
+ isAgent: z.boolean(),
63
+ createdAt: z.string(),
64
+ permissionCount: z.number(),
65
+ });
66
+ export const UserListResponseSchema = z.object({
67
+ items: z.array(UserListItemSchema),
68
+ total: z.number(),
69
+ page: z.number(),
70
+ pageSize: z.number(),
71
+ _links: z.array(z.any()).optional(),
72
+ _linkTemplates: z.array(z.any()).optional(),
73
+ _actions: z.array(z.any()).optional(),
74
+ });
75
+ export const UserListQuerySchema = z.object({
76
+ page: z.coerce.number().int().min(1).default(1),
77
+ pageSize: z.coerce.number().int().min(1).max(100).default(20),
78
+ search: z.string().optional(),
79
+ });