@happyvertical/smrt-manufacturing 0.54.3 → 0.54.4
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/AGENTS.md +1 -1
- package/README.md +12 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/smrt-knowledge.json +7 -6
- package/dist/svelte/components/AssemblyForm.svelte +93 -12
- package/dist/svelte/components/AssemblyForm.svelte.d.ts +14 -3
- package/dist/svelte/components/AssemblyForm.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ProductionRunList.svelte +197 -1
- package/dist/svelte/components/ProductionRunList.svelte.d.ts +15 -0
- package/dist/svelte/components/ProductionRunList.svelte.d.ts.map +1 -1
- package/dist/svelte/i18n.d.ts +23 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +23 -0
- package/dist/svelte/index.d.ts +1 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -1
- package/dist/svelte/types.d.ts +40 -87
- package/dist/svelte/types.d.ts.map +1 -1
- package/dist/svelte/types.js +29 -85
- package/dist/views.d.ts +95 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/views.js +72 -0
- package/dist/views.js.map +1 -0
- package/package.json +12 -8
package/dist/svelte/types.js
CHANGED
|
@@ -75,6 +75,7 @@ export function assemblyFormDraft(initial, exponent = 2) {
|
|
|
75
75
|
estimatedLabourMinutes: String(initial?.estimatedLabourMinutes ?? 0),
|
|
76
76
|
defaultOperationId: initial?.defaultOperationId ?? '',
|
|
77
77
|
tags: (initial?.tags ?? []).join(', '),
|
|
78
|
+
...(initial?.skuCode === undefined ? {} : { skuCode: initial.skuCode }),
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
@@ -95,6 +96,12 @@ export function keepProtectedFields(values, initial, protectedFields) {
|
|
|
95
96
|
tags: [...(initial?.tags ?? [])],
|
|
96
97
|
};
|
|
97
98
|
for (const field of protectedFields) {
|
|
99
|
+
// The part number is optional: it is kept only when the form carried it.
|
|
100
|
+
if (field === 'skuCode') {
|
|
101
|
+
if (kept.skuCode !== undefined)
|
|
102
|
+
kept.skuCode = initial?.skuCode ?? '';
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
98
105
|
kept[field] = original[field];
|
|
99
106
|
}
|
|
100
107
|
return kept;
|
|
@@ -113,7 +120,8 @@ function parseTags(raw) {
|
|
|
113
120
|
* required, the labour estimate is a whole number of minutes of zero or more
|
|
114
121
|
* (empty counts as zero), and the price is an amount of zero or more with at
|
|
115
122
|
* most `exponent` decimals (empty counts as zero; 2 unless the currency says
|
|
116
|
-
* otherwise).
|
|
123
|
+
* otherwise). A part number, when the draft carries one, must not be empty.
|
|
124
|
+
* Fields in `skip` (hidden or
|
|
117
125
|
* read-only in the form) are not checked.
|
|
118
126
|
*/
|
|
119
127
|
export function validateAssemblyForm(draft, skip = [], exponent = 2) {
|
|
@@ -142,11 +150,15 @@ export function validateAssemblyForm(draft, skip = [], exponent = 2) {
|
|
|
142
150
|
if (!Number.isSafeInteger(estimatedLabourMinutes) &&
|
|
143
151
|
!skip.includes('estimatedLabourMinutes'))
|
|
144
152
|
invalid.push('estimatedLabourMinutes');
|
|
153
|
+
const skuCode = draft.skuCode === undefined ? undefined : textOf(draft.skuCode);
|
|
154
|
+
if (skuCode === '' && !skip.includes('skuCode'))
|
|
155
|
+
invalid.push('skuCode');
|
|
145
156
|
if (invalid.length > 0)
|
|
146
157
|
return { ok: false, invalid };
|
|
147
158
|
return {
|
|
148
159
|
ok: true,
|
|
149
160
|
values: {
|
|
161
|
+
...(skuCode === undefined ? {} : { skuCode }),
|
|
150
162
|
name,
|
|
151
163
|
description: textOf(draft.description),
|
|
152
164
|
category: textOf(draft.category),
|
|
@@ -160,6 +172,7 @@ export function validateAssemblyForm(draft, skip = [], exponent = 2) {
|
|
|
160
172
|
},
|
|
161
173
|
};
|
|
162
174
|
}
|
|
175
|
+
export { toBomEditorLine, toBomEditorLines, toRequirementTotals, toRequirementTree, } from '../views.js';
|
|
163
176
|
/**
|
|
164
177
|
* Build an {@link AssemblyView} from an `Assembly` plus the facts the list
|
|
165
178
|
* shows that live on its neighbours: its SKU codes and its active bill.
|
|
@@ -175,30 +188,6 @@ export function toAssemblyView(assembly, related = {}) {
|
|
|
175
188
|
activeBomVersion: related.activeBomVersion ?? null,
|
|
176
189
|
};
|
|
177
190
|
}
|
|
178
|
-
/** Adapt one resolved bill line for {@link BomEditor}. */
|
|
179
|
-
export function toBomEditorLine(entry) {
|
|
180
|
-
const { line, component } = entry;
|
|
181
|
-
const skuCode = component.sku?.code ?? '';
|
|
182
|
-
return {
|
|
183
|
-
id: line.id ?? '',
|
|
184
|
-
componentSkuId: line.componentSkuId,
|
|
185
|
-
componentName: component.product?.name || skuCode || line.componentSkuId,
|
|
186
|
-
skuCode,
|
|
187
|
-
kind: component.kind,
|
|
188
|
-
qtyPerUnit: Number(line.qtyPerUnit ?? 0),
|
|
189
|
-
uom: line.uom,
|
|
190
|
-
wastePercent: Number(line.wastePercent ?? 0),
|
|
191
|
-
notes: line.notes ?? '',
|
|
192
|
-
subBomId: component.activeBom?.id ?? null,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Adapt a whole bill from `AssemblyService.getBillStructure` for
|
|
197
|
-
* {@link BomEditor}, keeping its line order.
|
|
198
|
-
*/
|
|
199
|
-
export function toBomEditorLines(structure) {
|
|
200
|
-
return structure.lines.map(toBomEditorLine);
|
|
201
|
-
}
|
|
202
191
|
function toNumber(raw) {
|
|
203
192
|
if (typeof raw === 'number')
|
|
204
193
|
return raw;
|
|
@@ -311,66 +300,21 @@ export function validateCompletionQty(raw, run) {
|
|
|
311
300
|
return { ok: false, reason: 'too_many' };
|
|
312
301
|
return { ok: true, qty: rounded };
|
|
313
302
|
}
|
|
314
|
-
function isPlanned(line) {
|
|
315
|
-
return 'short' in line;
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Nest the depth-first lines of an {@link Explosion} or a
|
|
319
|
-
* {@link RequirementsPlan} for `RequirementsTree`.
|
|
320
|
-
*/
|
|
321
|
-
export function toRequirementTree(source) {
|
|
322
|
-
const roots = [];
|
|
323
|
-
const open = [];
|
|
324
|
-
for (const line of source.lines) {
|
|
325
|
-
const view = {
|
|
326
|
-
key: [...line.path.map((entry) => entry.bomId), line.lineId].join('/'),
|
|
327
|
-
level: line.level,
|
|
328
|
-
name: line.name,
|
|
329
|
-
skuCode: line.skuCode,
|
|
330
|
-
kind: line.kind,
|
|
331
|
-
required: line.totalQty,
|
|
332
|
-
uom: line.uom,
|
|
333
|
-
available: isPlanned(line) ? line.available : null,
|
|
334
|
-
short: isPlanned(line) ? line.short : null,
|
|
335
|
-
buildable: line.buildable,
|
|
336
|
-
children: [],
|
|
337
|
-
};
|
|
338
|
-
open.length = Math.min(open.length, line.level - 1);
|
|
339
|
-
const parent = open[line.level - 2];
|
|
340
|
-
if (line.level > 1 && parent)
|
|
341
|
-
parent.children.push(view);
|
|
342
|
-
else
|
|
343
|
-
roots.push(view);
|
|
344
|
-
open[line.level - 1] = view;
|
|
345
|
-
}
|
|
346
|
-
return roots;
|
|
347
|
-
}
|
|
348
303
|
/**
|
|
349
|
-
*
|
|
350
|
-
* not
|
|
304
|
+
* Check a typed target quantity: greater than zero, within the quantity
|
|
305
|
+
* limits, and not below what the run has already completed (the service
|
|
306
|
+
* checks again under its lock).
|
|
351
307
|
*/
|
|
352
|
-
export function
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
else {
|
|
365
|
-
totals.set(line.componentSkuId, {
|
|
366
|
-
componentSkuId: line.componentSkuId,
|
|
367
|
-
name: line.name,
|
|
368
|
-
skuCode: line.skuCode,
|
|
369
|
-
required: line.totalQty,
|
|
370
|
-
uom: line.uom,
|
|
371
|
-
short,
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
return Array.from(totals.values());
|
|
308
|
+
export function validateTargetQty(raw, run) {
|
|
309
|
+
const text = raw.trim();
|
|
310
|
+
const qty = text === '' ? Number.NaN : Number(text);
|
|
311
|
+
if (!Number.isFinite(qty) || qty <= 0)
|
|
312
|
+
return { ok: false, reason: 'invalid' };
|
|
313
|
+
const millionths = (value) => Number(value.toFixed(6).replace('.', ''));
|
|
314
|
+
const rounded = Number(qty.toFixed(6));
|
|
315
|
+
if (rounded <= 0 || rounded > 999_999_999)
|
|
316
|
+
return { ok: false, reason: 'invalid' };
|
|
317
|
+
if (millionths(rounded) < millionths(run.completedQty))
|
|
318
|
+
return { ok: false, reason: 'below_done' };
|
|
319
|
+
return { ok: true, qty: rounded };
|
|
376
320
|
}
|
package/dist/views.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component-free view adapters for the manufacturing components.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions and plain view types, importable from
|
|
5
|
+
* `@happyvertical/smrt-manufacturing/views` without compiling any `.svelte`
|
|
6
|
+
* file (server loads adapt service results here). `./svelte` re-exports
|
|
7
|
+
* everything in this module unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import type { BillStructure, BillStructureLine, ComponentKind } from './services/AssemblyService.js';
|
|
10
|
+
import type { Explosion, RequirementsPlan } from './types.js';
|
|
11
|
+
export type { ComponentKind };
|
|
12
|
+
/** One line of {@link BomEditor}. */
|
|
13
|
+
export interface BomEditorLine {
|
|
14
|
+
/** The bill line id. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** The component SKU the line consumes. */
|
|
17
|
+
componentSkuId: string;
|
|
18
|
+
/** The component's product name, or its SKU code when that is unknown. */
|
|
19
|
+
componentName: string;
|
|
20
|
+
/** The component's SKU code; empty when the SKU is missing. */
|
|
21
|
+
skuCode: string;
|
|
22
|
+
/** What the component is. */
|
|
23
|
+
kind: ComponentKind;
|
|
24
|
+
/** Quantity per produced unit, before waste. */
|
|
25
|
+
qtyPerUnit: number;
|
|
26
|
+
/** Unit of measure. */
|
|
27
|
+
uom: string;
|
|
28
|
+
/** Expected waste, in percent. */
|
|
29
|
+
wastePercent: number;
|
|
30
|
+
/** Free-form notes. */
|
|
31
|
+
notes: string;
|
|
32
|
+
/**
|
|
33
|
+
* For a sub-assembly, the id of its own active bill; `null` when it has
|
|
34
|
+
* none, and for every other kind.
|
|
35
|
+
*/
|
|
36
|
+
subBomId: string | null;
|
|
37
|
+
}
|
|
38
|
+
/** Adapt one resolved bill line for {@link BomEditor}. */
|
|
39
|
+
export declare function toBomEditorLine(entry: BillStructureLine): BomEditorLine;
|
|
40
|
+
/**
|
|
41
|
+
* Adapt a whole bill from `AssemblyService.getBillStructure` for
|
|
42
|
+
* {@link BomEditor}, keeping its line order.
|
|
43
|
+
*/
|
|
44
|
+
export declare function toBomEditorLines(structure: BillStructure): BomEditorLine[];
|
|
45
|
+
/** One line of `RequirementsTree`, with the lines of its bill when opened. */
|
|
46
|
+
export interface RequirementLineView {
|
|
47
|
+
/** Unique within the tree: the bills above it plus the line id. */
|
|
48
|
+
key: string;
|
|
49
|
+
/** 1 for the top bill's lines. */
|
|
50
|
+
level: number;
|
|
51
|
+
/** Component name; empty when it cannot be resolved. */
|
|
52
|
+
name: string;
|
|
53
|
+
/** Component SKU code; empty when unknown. */
|
|
54
|
+
skuCode: string;
|
|
55
|
+
/** What the component is. */
|
|
56
|
+
kind: ComponentKind;
|
|
57
|
+
/** Quantity required for the run (net of stock in a plan). */
|
|
58
|
+
required: number;
|
|
59
|
+
/** The line's unit. */
|
|
60
|
+
uom: string;
|
|
61
|
+
/** Unallocated available stock; `null` for a gross explosion. */
|
|
62
|
+
available: number | null;
|
|
63
|
+
/** How much is short; `null` for a gross explosion. */
|
|
64
|
+
short: number | null;
|
|
65
|
+
/** A sub-assembly with an active bill. */
|
|
66
|
+
buildable: boolean;
|
|
67
|
+
/** The lines of this sub-assembly's bill that were walked. */
|
|
68
|
+
children: RequirementLineView[];
|
|
69
|
+
}
|
|
70
|
+
/** One row of the totals table of `RequirementsTree`. */
|
|
71
|
+
export interface RequirementTotalView {
|
|
72
|
+
/** The component SKU. */
|
|
73
|
+
componentSkuId: string;
|
|
74
|
+
/** Component name; empty when it cannot be resolved. */
|
|
75
|
+
name: string;
|
|
76
|
+
/** Component SKU code; empty when unknown. */
|
|
77
|
+
skuCode: string;
|
|
78
|
+
/** Total required, summed over the lines not opened. */
|
|
79
|
+
required: number;
|
|
80
|
+
/** The first line's unit. */
|
|
81
|
+
uom: string;
|
|
82
|
+
/** Total short; `null` for a gross explosion. */
|
|
83
|
+
short: number | null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Nest the depth-first lines of an {@link Explosion} or a
|
|
87
|
+
* {@link RequirementsPlan} for `RequirementsTree`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function toRequirementTree(source: Pick<Explosion | RequirementsPlan, 'lines'>): RequirementLineView[];
|
|
90
|
+
/**
|
|
91
|
+
* The totals of an {@link Explosion} or a {@link RequirementsPlan}: every line
|
|
92
|
+
* not opened, summed per component SKU (first unit kept).
|
|
93
|
+
*/
|
|
94
|
+
export declare function toRequirementTotals(source: Pick<Explosion | RequirementsPlan, 'lines'>): RequirementTotalView[];
|
|
95
|
+
//# sourceMappingURL=views.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"views.d.ts","sourceRoot":"","sources":["../src/views.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,aAAa,EACd,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAEV,SAAS,EAET,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,aAAa,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,KAAK,EAAE,iBAAiB,GAAG,aAAa,CAevE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,aAAa,GAAG,aAAa,EAAE,CAE1E;AAMD,8EAA8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uDAAuD;IACvD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,0CAA0C;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,yBAAyB;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,IAAI,CAAC,SAAS,GAAG,gBAAgB,EAAE,OAAO,CAAC,GAClD,mBAAmB,EAAE,CAwBvB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,IAAI,CAAC,SAAS,GAAG,gBAAgB,EAAE,OAAO,CAAC,GAClD,oBAAoB,EAAE,CAqBxB"}
|
package/dist/views.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/views.ts
|
|
2
|
+
function toBomEditorLine(entry) {
|
|
3
|
+
const { line, component } = entry;
|
|
4
|
+
const skuCode = component.sku?.code ?? "";
|
|
5
|
+
return {
|
|
6
|
+
id: line.id ?? "",
|
|
7
|
+
componentSkuId: line.componentSkuId,
|
|
8
|
+
componentName: component.product?.name || skuCode || line.componentSkuId,
|
|
9
|
+
skuCode,
|
|
10
|
+
kind: component.kind,
|
|
11
|
+
qtyPerUnit: Number(line.qtyPerUnit ?? 0),
|
|
12
|
+
uom: line.uom,
|
|
13
|
+
wastePercent: Number(line.wastePercent ?? 0),
|
|
14
|
+
notes: line.notes ?? "",
|
|
15
|
+
subBomId: component.activeBom?.id ?? null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function toBomEditorLines(structure) {
|
|
19
|
+
return structure.lines.map(toBomEditorLine);
|
|
20
|
+
}
|
|
21
|
+
function isPlanned(line) {
|
|
22
|
+
return "short" in line;
|
|
23
|
+
}
|
|
24
|
+
function toRequirementTree(source) {
|
|
25
|
+
const roots = [];
|
|
26
|
+
const open = [];
|
|
27
|
+
for (const line of source.lines) {
|
|
28
|
+
const view = {
|
|
29
|
+
key: [...line.path.map((entry) => entry.bomId), line.lineId].join("/"),
|
|
30
|
+
level: line.level,
|
|
31
|
+
name: line.name,
|
|
32
|
+
skuCode: line.skuCode,
|
|
33
|
+
kind: line.kind,
|
|
34
|
+
required: line.totalQty,
|
|
35
|
+
uom: line.uom,
|
|
36
|
+
available: isPlanned(line) ? line.available : null,
|
|
37
|
+
short: isPlanned(line) ? line.short : null,
|
|
38
|
+
buildable: line.buildable,
|
|
39
|
+
children: []
|
|
40
|
+
};
|
|
41
|
+
open.length = Math.min(open.length, line.level - 1);
|
|
42
|
+
const parent = open[line.level - 2];
|
|
43
|
+
if (line.level > 1 && parent) parent.children.push(view);
|
|
44
|
+
else roots.push(view);
|
|
45
|
+
open[line.level - 1] = view;
|
|
46
|
+
}
|
|
47
|
+
return roots;
|
|
48
|
+
}
|
|
49
|
+
function toRequirementTotals(source) {
|
|
50
|
+
const totals = /* @__PURE__ */ new Map();
|
|
51
|
+
for (const line of source.lines) {
|
|
52
|
+
if (line.expanded) continue;
|
|
53
|
+
const short = isPlanned(line) ? line.short : null;
|
|
54
|
+
const existing = totals.get(line.componentSkuId);
|
|
55
|
+
if (existing) {
|
|
56
|
+
existing.required += line.totalQty;
|
|
57
|
+
if (existing.short !== null && short !== null) existing.short += short;
|
|
58
|
+
} else totals.set(line.componentSkuId, {
|
|
59
|
+
componentSkuId: line.componentSkuId,
|
|
60
|
+
name: line.name,
|
|
61
|
+
skuCode: line.skuCode,
|
|
62
|
+
required: line.totalQty,
|
|
63
|
+
uom: line.uom,
|
|
64
|
+
short
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return Array.from(totals.values());
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { toBomEditorLine, toBomEditorLines, toRequirementTotals, toRequirementTree };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=views.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"views.js","names":[],"sources":["../src/views.ts"],"sourcesContent":["/**\n * Component-free view adapters for the manufacturing components.\n *\n * Pure functions and plain view types, importable from\n * `@happyvertical/smrt-manufacturing/views` without compiling any `.svelte`\n * file (server loads adapt service results here). `./svelte` re-exports\n * everything in this module unchanged.\n */\n\nimport type {\n BillStructure,\n BillStructureLine,\n ComponentKind,\n} from './services/AssemblyService.js';\nimport type {\n ExplodedLine,\n Explosion,\n PlannedLine,\n RequirementsPlan,\n} from './types.js';\n\nexport type { ComponentKind };\n\n/** One line of {@link BomEditor}. */\nexport interface BomEditorLine {\n /** The bill line id. */\n id: string;\n /** The component SKU the line consumes. */\n componentSkuId: string;\n /** The component's product name, or its SKU code when that is unknown. */\n componentName: string;\n /** The component's SKU code; empty when the SKU is missing. */\n skuCode: string;\n /** What the component is. */\n kind: ComponentKind;\n /** Quantity per produced unit, before waste. */\n qtyPerUnit: number;\n /** Unit of measure. */\n uom: string;\n /** Expected waste, in percent. */\n wastePercent: number;\n /** Free-form notes. */\n notes: string;\n /**\n * For a sub-assembly, the id of its own active bill; `null` when it has\n * none, and for every other kind.\n */\n subBomId: string | null;\n}\n\n/** Adapt one resolved bill line for {@link BomEditor}. */\nexport function toBomEditorLine(entry: BillStructureLine): BomEditorLine {\n const { line, component } = entry;\n const skuCode = component.sku?.code ?? '';\n return {\n id: line.id ?? '',\n componentSkuId: line.componentSkuId,\n componentName: component.product?.name || skuCode || line.componentSkuId,\n skuCode,\n kind: component.kind,\n qtyPerUnit: Number(line.qtyPerUnit ?? 0),\n uom: line.uom,\n wastePercent: Number(line.wastePercent ?? 0),\n notes: line.notes ?? '',\n subBomId: component.activeBom?.id ?? null,\n };\n}\n\n/**\n * Adapt a whole bill from `AssemblyService.getBillStructure` for\n * {@link BomEditor}, keeping its line order.\n */\nexport function toBomEditorLines(structure: BillStructure): BomEditorLine[] {\n return structure.lines.map(toBomEditorLine);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Exploded requirements\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** One line of `RequirementsTree`, with the lines of its bill when opened. */\nexport interface RequirementLineView {\n /** Unique within the tree: the bills above it plus the line id. */\n key: string;\n /** 1 for the top bill's lines. */\n level: number;\n /** Component name; empty when it cannot be resolved. */\n name: string;\n /** Component SKU code; empty when unknown. */\n skuCode: string;\n /** What the component is. */\n kind: ComponentKind;\n /** Quantity required for the run (net of stock in a plan). */\n required: number;\n /** The line's unit. */\n uom: string;\n /** Unallocated available stock; `null` for a gross explosion. */\n available: number | null;\n /** How much is short; `null` for a gross explosion. */\n short: number | null;\n /** A sub-assembly with an active bill. */\n buildable: boolean;\n /** The lines of this sub-assembly's bill that were walked. */\n children: RequirementLineView[];\n}\n\n/** One row of the totals table of `RequirementsTree`. */\nexport interface RequirementTotalView {\n /** The component SKU. */\n componentSkuId: string;\n /** Component name; empty when it cannot be resolved. */\n name: string;\n /** Component SKU code; empty when unknown. */\n skuCode: string;\n /** Total required, summed over the lines not opened. */\n required: number;\n /** The first line's unit. */\n uom: string;\n /** Total short; `null` for a gross explosion. */\n short: number | null;\n}\n\nfunction isPlanned(line: ExplodedLine): line is PlannedLine {\n return 'short' in line;\n}\n\n/**\n * Nest the depth-first lines of an {@link Explosion} or a\n * {@link RequirementsPlan} for `RequirementsTree`.\n */\nexport function toRequirementTree(\n source: Pick<Explosion | RequirementsPlan, 'lines'>,\n): RequirementLineView[] {\n const roots: RequirementLineView[] = [];\n const open: RequirementLineView[] = [];\n for (const line of source.lines as ExplodedLine[]) {\n const view: RequirementLineView = {\n key: [...line.path.map((entry) => entry.bomId), line.lineId].join('/'),\n level: line.level,\n name: line.name,\n skuCode: line.skuCode,\n kind: line.kind,\n required: line.totalQty,\n uom: line.uom,\n available: isPlanned(line) ? line.available : null,\n short: isPlanned(line) ? line.short : null,\n buildable: line.buildable,\n children: [],\n };\n open.length = Math.min(open.length, line.level - 1);\n const parent = open[line.level - 2];\n if (line.level > 1 && parent) parent.children.push(view);\n else roots.push(view);\n open[line.level - 1] = view;\n }\n return roots;\n}\n\n/**\n * The totals of an {@link Explosion} or a {@link RequirementsPlan}: every line\n * not opened, summed per component SKU (first unit kept).\n */\nexport function toRequirementTotals(\n source: Pick<Explosion | RequirementsPlan, 'lines'>,\n): RequirementTotalView[] {\n const totals = new Map<string, RequirementTotalView>();\n for (const line of source.lines as ExplodedLine[]) {\n if (line.expanded) continue;\n const short = isPlanned(line) ? line.short : null;\n const existing = totals.get(line.componentSkuId);\n if (existing) {\n existing.required += line.totalQty;\n if (existing.short !== null && short !== null) existing.short += short;\n } else {\n totals.set(line.componentSkuId, {\n componentSkuId: line.componentSkuId,\n name: line.name,\n skuCode: line.skuCode,\n required: line.totalQty,\n uom: line.uom,\n short,\n });\n }\n }\n return Array.from(totals.values());\n}\n"],"mappings":";AAmDO,SAAS,gBAAgB,OAAyC;CACvE,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,UAAU,UAAU,KAAK,QAAQ;CACvC,OAAO;EACL,IAAI,KAAK,MAAM;EACf,gBAAgB,KAAK;EACrB,eAAe,UAAU,SAAS,QAAQ,WAAW,KAAK;EAC1D;EACA,MAAM,UAAU;EAChB,YAAY,OAAO,KAAK,cAAc,CAAC;EACvC,KAAK,KAAK;EACV,cAAc,OAAO,KAAK,gBAAgB,CAAC;EAC3C,OAAO,KAAK,SAAS;EACrB,UAAU,UAAU,WAAW,MAAM;CACvC;AACF;AAMO,SAAS,iBAAiB,WAA2C;CAC1E,OAAO,UAAU,MAAM,IAAI,eAAe;AAC5C;AAgDA,SAAS,UAAU,MAAyC;CAC1D,OAAO,WAAW;AACpB;AAMO,SAAS,kBACd,QACuB;CACvB,MAAM,QAA+B,CAAC;CACtC,MAAM,OAA8B,CAAC;CACrC,KAAA,MAAW,QAAQ,OAAO,OAAyB;EACjD,MAAM,OAA4B;GAChC,KAAK,CAAC,GAAG,KAAK,KAAK,KAAK,UAAU,MAAM,KAAK,GAAG,KAAK,MAAM,CAAA,CAAE,KAAK,GAAG;GACrE,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,SAAS,KAAK;GACd,MAAM,KAAK;GACX,UAAU,KAAK;GACf,KAAK,KAAK;GACV,WAAW,UAAU,IAAI,IAAI,KAAK,YAAY;GAC9C,OAAO,UAAU,IAAI,IAAI,KAAK,QAAQ;GACtC,WAAW,KAAK;GAChB,UAAU,CAAC;EACb;EACA,KAAK,SAAS,KAAK,IAAI,KAAK,QAAQ,KAAK,QAAQ,CAAC;EAClD,MAAM,SAAS,KAAK,KAAK,QAAQ;EACjC,IAAI,KAAK,QAAQ,KAAK,QAAQ,OAAO,SAAS,KAAK,IAAI;OAClD,MAAM,KAAK,IAAI;EACpB,KAAK,KAAK,QAAQ,KAAK;CACzB;CACA,OAAO;AACT;AAMO,SAAS,oBACd,QACwB;CACxB,MAAM,yBAAS,IAAI,IAAkC;CACrD,KAAA,MAAW,QAAQ,OAAO,OAAyB;EACjD,IAAI,KAAK,UAAU;EACnB,MAAM,QAAQ,UAAU,IAAI,IAAI,KAAK,QAAQ;EAC7C,MAAM,WAAW,OAAO,IAAI,KAAK,cAAc;EAC/C,IAAI,UAAU;GACZ,SAAS,YAAY,KAAK;GAC1B,IAAI,SAAS,UAAU,QAAQ,UAAU,MAAM,SAAS,SAAS;EACnE,OACE,OAAO,IAAI,KAAK,gBAAgB;GAC9B,gBAAgB,KAAK;GACrB,MAAM,KAAK;GACX,SAAS,KAAK;GACd,UAAU,KAAK;GACf,KAAK,KAAK;GACV;EACF,CAAC;CAEL;CACA,OAAO,MAAM,KAAK,OAAO,OAAO,CAAC;AACnC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-manufacturing",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.4",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Bills of materials, cost rollup, and production-order operations for the SMRT framework — strictly industry-neutral",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"types": "./dist/ui.d.ts",
|
|
23
23
|
"import": "./dist/ui.js"
|
|
24
24
|
},
|
|
25
|
+
"./views": {
|
|
26
|
+
"types": "./dist/views.d.ts",
|
|
27
|
+
"import": "./dist/views.js"
|
|
28
|
+
},
|
|
25
29
|
"./svelte": {
|
|
26
30
|
"types": "./dist/svelte/index.d.ts",
|
|
27
31
|
"svelte": "./dist/svelte/index.js",
|
|
@@ -32,12 +36,12 @@
|
|
|
32
36
|
},
|
|
33
37
|
"dependencies": {
|
|
34
38
|
"@happyvertical/logger": "^0.102.1",
|
|
35
|
-
"@happyvertical/smrt-core": "0.54.
|
|
36
|
-
"@happyvertical/smrt-inventory": "0.54.
|
|
37
|
-
"@happyvertical/smrt-products": "0.54.
|
|
38
|
-
"@happyvertical/smrt-tenancy": "0.54.
|
|
39
|
-
"@happyvertical/smrt-types": "0.54.
|
|
40
|
-
"@happyvertical/smrt-ui": "0.54.
|
|
39
|
+
"@happyvertical/smrt-core": "0.54.4",
|
|
40
|
+
"@happyvertical/smrt-inventory": "0.54.4",
|
|
41
|
+
"@happyvertical/smrt-products": "0.54.4",
|
|
42
|
+
"@happyvertical/smrt-tenancy": "0.54.4",
|
|
43
|
+
"@happyvertical/smrt-types": "0.54.4",
|
|
44
|
+
"@happyvertical/smrt-ui": "0.54.4",
|
|
41
45
|
"@happyvertical/sql": "^0.102.1"
|
|
42
46
|
},
|
|
43
47
|
"peerDependencies": {
|
|
@@ -49,7 +53,7 @@
|
|
|
49
53
|
}
|
|
50
54
|
},
|
|
51
55
|
"devDependencies": {
|
|
52
|
-
"@happyvertical/smrt-vitest": "0.54.
|
|
56
|
+
"@happyvertical/smrt-vitest": "0.54.4",
|
|
53
57
|
"@sveltejs/package": "^2.5.8",
|
|
54
58
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
55
59
|
"@types/node": "24.13.6",
|