@happyvertical/smrt-manufacturing 0.53.4 → 0.53.5
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 +45 -6
- package/README.md +161 -3
- package/dist/collections/AssemblyCollection.d.ts +15 -0
- package/dist/collections/AssemblyCollection.d.ts.map +1 -0
- package/dist/collections/OperationCollection.d.ts +20 -0
- package/dist/collections/OperationCollection.d.ts.map +1 -0
- package/dist/collections/RoutingStepCollection.d.ts +15 -0
- package/dist/collections/RoutingStepCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +3 -0
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +756 -29
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1248 -128
- package/dist/models/Assembly.d.ts +62 -0
- package/dist/models/Assembly.d.ts.map +1 -0
- package/dist/models/BillOfMaterials.d.ts +12 -4
- package/dist/models/BillOfMaterials.d.ts.map +1 -1
- package/dist/models/BomLine.d.ts +11 -5
- package/dist/models/BomLine.d.ts.map +1 -1
- package/dist/models/Operation.d.ts +59 -0
- package/dist/models/Operation.d.ts.map +1 -0
- package/dist/models/RoutingStep.d.ts +46 -0
- package/dist/models/RoutingStep.d.ts.map +1 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/operation-types.d.ts +99 -0
- package/dist/operation-types.d.ts.map +1 -0
- package/dist/services/AssemblyService.d.ts +217 -0
- package/dist/services/AssemblyService.d.ts.map +1 -0
- package/dist/services/BomService.d.ts +25 -1
- package/dist/services/BomService.d.ts.map +1 -1
- package/dist/services/OperationService.d.ts +48 -0
- package/dist/services/OperationService.d.ts.map +1 -0
- package/dist/services/RoutingService.d.ts +50 -0
- package/dist/services/RoutingService.d.ts.map +1 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +1138 -81
- package/dist/svelte/components/AssemblyForm.svelte +69 -0
- package/dist/svelte/components/AssemblyForm.svelte.d.ts +38 -0
- package/dist/svelte/components/AssemblyForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/AssemblyList.svelte +229 -0
- package/dist/svelte/components/AssemblyList.svelte.d.ts +28 -0
- package/dist/svelte/components/AssemblyList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/BomEditor.svelte +575 -0
- package/dist/svelte/components/BomEditor.svelte.d.ts +29 -0
- package/dist/svelte/components/BomEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/BomStructureTree.svelte +164 -0
- package/dist/svelte/components/BomStructureTree.svelte.d.ts +16 -0
- package/dist/svelte/components/BomStructureTree.svelte.d.ts.map +1 -0
- package/dist/svelte/components/OperationForm.svelte +258 -0
- package/dist/svelte/components/OperationForm.svelte.d.ts +17 -0
- package/dist/svelte/components/OperationForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/OperationList.svelte +170 -0
- package/dist/svelte/components/OperationList.svelte.d.ts +17 -0
- package/dist/svelte/components/OperationList.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +87 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +87 -0
- package/dist/svelte/index.d.ts +28 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +29 -0
- package/dist/svelte/types.d.ts +170 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +126 -0
- package/dist/ui.d.ts +17 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +72 -0
- package/dist/ui.js.map +1 -0
- package/package.json +39 -10
package/AGENTS.md
CHANGED
|
@@ -1,19 +1,49 @@
|
|
|
1
1
|
# @happyvertical/smrt-manufacturing
|
|
2
2
|
|
|
3
|
-
Bills of materials, cost rollup, and production-order
|
|
3
|
+
Bills of materials, operations and routing, cost and labour rollup, and production-order stock movement. Strictly industry-neutral — the same primitives serve apparel, furniture, automotive, CPG, electronics, food production, custom hardware, and any vertical that builds finished goods from a recipe.
|
|
4
4
|
|
|
5
|
-
Sits on top of `@happyvertical/smrt-inventory` (stock) and works alongside the `ProductionOrder` Contract STI subtype already shipped in `@happyvertical/smrt-commerce`.
|
|
5
|
+
Sits on top of `@happyvertical/smrt-inventory` (stock) and `@happyvertical/smrt-products` (catalog: `Product`, `Material`, `Sku`), and works alongside the `ProductionOrder` Contract STI subtype already shipped in `@happyvertical/smrt-commerce`.
|
|
6
6
|
|
|
7
7
|
## Models
|
|
8
8
|
|
|
9
9
|
| Model | Purpose |
|
|
10
10
|
|---|---|
|
|
11
|
+
| `Assembly` | The product that is **made**, counterpart to `Material`. Cross-package STI subtype of `smrt-products` `Product` (`productType: 'assembly'`, `_meta_type: '@happyvertical/smrt-manufacturing:Assembly'`) in the shared `products` table. Own columns: `estimatedLabourMinutes` (integer, per unit; a save refuses anything but a whole number of minutes of zero or more), `defaultOperationId` (`@foreignKey(Operation)`, nullable, RESTRICT like `RoutingStep.operationId`: the operation work on it is booked to by default), `partReference` (the organization's drawing/part number). Name, price, tags come from `Product`; part number and stock from its `Sku` and inventory; structure from its bill; documents from `ProductAsset`. Exposure matches `Product` (REST list/get/create/update, MCP list/get), as the issue asks: unlike `Operation`, the assembly row has no service-owned invariant that generated writes could bypass. |
|
|
11
12
|
| `BillOfMaterials` | Recipe for a finished product. `productId` (plain string) references the upstream `Product` or any STI subtype. Multiple revisions per product via `version` + `status` (`draft` / `active` / `superseded`). `conflictColumns: ['product_id', 'version', 'tenant_id']`. |
|
|
12
13
|
| `BomLine` | One component on a BOM. `bomId` (FK), `componentSkuId` (plain string ref — the `Sku` model lives in `@happyvertical/smrt-products`; inventory tracks stock motion against the id), `qtyPerUnit`, `uom` (open-ended — `yards`, `each`, `grams`, `kg`, ...), `wastePercent`, `notes`. `conflictColumns: ['bom_id', 'component_sku_id', 'tenant_id']`. |
|
|
14
|
+
| `Operation` | A kind of work (cut, weld, inspect), a managed list per tenant. `code` (unique per tenant; `conflictColumns: ['code', 'tenant_id']`, fixed once defined), `name`, `category`, `isActive` (retire/reinstate; never deleted, `delete()` throws), `requiredQualificationId` (nullable `@crossPackageRef('@happyvertical/smrt-human-resources:Qualification')`: metadata only, no dependency, unvalidated; an application reads it to gate who may start the operation). |
|
|
15
|
+
| `RoutingStep` | One operation in a BOM's optional routing. `bomId` (`@foreignKey(BillOfMaterials)`, CASCADE: a routing belongs to its bill), `operationId` (`@foreignKey(Operation)`, RESTRICT: operations are retired, never deleted), `sequence` (1..n, unique per bom), `estimatedMinutes` (decimal, per produced unit), `notes`. `conflictColumns: ['bom_id', 'sequence', 'tenant_id']`. |
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
All models are `@TenantScoped({ mode: 'optional' })` with a nullable `tenantId` so they can be used either tenant-scoped or globally.
|
|
18
|
+
|
|
19
|
+
## Operations, routing and labour estimate
|
|
20
|
+
|
|
21
|
+
- `OperationService` maintains the list (`define` rejects a duplicate code, including a retired one's; `rename`, `update`, `retire`, `reinstate`). `RoutingService.replaceRouting(bomId, steps)` swaps a bill's whole routing in one transaction and numbers it 1..n; a retired operation may stay on a routing that has it but cannot be newly added.
|
|
22
|
+
- `BomService.computeLabourEstimate(bomId)` returns minutes and, only when the caller supplies `rateResolver` (hourly rate per `Operation`, mirroring `costResolver`), cost. No resolver means minutes only with `rateUnavailable` on each step. A bill with no routing returns an empty estimate; `computeMaterialCost` is unchanged.
|
|
23
|
+
- `Operation` and `RoutingStep` expose generated list/get only; writes go through the services (generated create/update would upsert on a duplicate code or rename the code). `replaceRouting` saves the bill row inside its transaction (row lock) and retries on a revision conflict, so concurrent replacements serialize.
|
|
24
|
+
- Deliberately absent: a default labour rate class (nothing here would use it; the resolver receives the whole `Operation` and can key on `code`/`category`), setup time, scheduling and work-centre capacity, and time recorded against an operation (that stays in `smrt-timesheets` or the application).
|
|
25
|
+
- `./svelte` exports `OperationList` and `OperationForm` (props-driven; hosts persist through `OperationService`). Messages live in `src/svelte/i18n.ts`; component tests use the shared jsdom harness with axe assertions.
|
|
26
|
+
- Tests: `operations-suite.ts` runs on SQLite (`operations.test.ts`) and PostgreSQL (`operations.optional.test.ts`, `pnpm test:postgres`; each test runs in its own tenant because PostgreSQL test data persists).
|
|
27
|
+
|
|
28
|
+
## Assemblies — nesting lives in the bill
|
|
29
|
+
|
|
30
|
+
A sub-assembly is a `BomLine` whose component SKU belongs to an `Assembly`; there is no children list on the model. `AssemblyService` is the one link from a line to what it names:
|
|
31
|
+
|
|
32
|
+
| Method | Behavior |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `resolveComponent(skuId, { tenantId? })` | `{ kind, sku, product, assembly, activeBom, buildable }`. `kind` is `assembly`, `material` (`Material`), `bought` (any other `Product`), or `missing` (unknown SKU, SKU with no product, or a non-UUID id; never throws). `activeBom` is the assembly's highest active bill, `null` when it has none; `buildable` is `assembly && activeBom`. Multi-level explosion (#3444) recurses on `activeBom` and stops at every other kind. |
|
|
35
|
+
| `isAssembly(skuId)` | `resolveComponent(skuId).kind === 'assembly'`. |
|
|
36
|
+
| `findActiveBom(productId, { tenantId? })` | Highest active bill. With `tenantId` (including `null`), only that tenant's bills count, and `resolveComponent` reports another tenant's SKU, product or bill line as `missing` (global rows stay visible), whatever the tenant context; the cycle walk and `getBillStructure` pass the bill's own tenant, so a shared assembly resolves to the right tenant's bill on writes made without `withTenant()`. |
|
|
37
|
+
| `getBillStructure(bomId)` | One level: the bill plus each line with its resolved component. Throws `BomNotFoundError`. |
|
|
38
|
+
| `findCycle(productId, componentSkuIds)` | The path by which those components would make `productId` contain itself, following sub-assemblies' **active** bills only; `null` when none. |
|
|
39
|
+
| `assertLineAcyclic(line)` / `assertBillAcyclic(bom)` | Throw `BomCycleError` (a `ValidationError`, code `MANUFACTURING_BOM_CYCLE`, `path` from the product back to itself). |
|
|
40
|
+
|
|
41
|
+
**Cycle refusal runs in the models.** `BomLine.validateBeforeSave` checks every line save (draft bills too, so a loop is refused when written); `BillOfMaterials.validateBeforeSave` checks every save of an `active` bill (activation), including the lines of a stored bill with the same `(productId, version, tenantId)` that the save would upsert onto. The generated REST routes go through `save()`, so they refuse cycles as well. Not covered: re-pointing a `Sku.productId` (owned by `smrt-products`) and two concurrent saves that each close half of a loop.
|
|
42
|
+
|
|
43
|
+
## Svelte components (`./svelte`)
|
|
44
|
+
|
|
45
|
+
Props-driven; the host loads and persists. `AssemblyList` (field policy drops hidden columns via `policyToVisibleColumnIds`), `AssemblyForm` (smrt-fields `ObjectForm` for `@happyvertical/smrt-manufacturing:Assembly`, so price and every other field follow the consumer's field policy), `BomEditor` (add/edit/remove lines through host handlers; a sub-assembly shows whether it has its own bill and expands read-only through `loadBill`), `BomStructureTree` (the read-only nested view). Adapters: `toAssemblyView`, `toBomEditorLines`. Strings are under `manufacturing.` in `src/svelte/i18n.ts`; the package is strict for raw primitives, hardcoded strings and JSDoc.
|
|
15
46
|
|
|
16
|
-
`RoutingStep` (labor cost) is intentionally out of scope for v1 — see issue [#1245](https://github.com/happyvertical/smrt/issues/1245) for the planned follow-up.
|
|
17
47
|
|
|
18
48
|
## BomService — planning helpers
|
|
19
49
|
|
|
@@ -123,6 +153,10 @@ This subscribes to:
|
|
|
123
153
|
|
|
124
154
|
The companion handlers for `contract:created` (reserve) and `fulfillment:shipped` (fulfil) live in `@happyvertical/smrt-inventory` — wire both packages' installers from `smrt.ts` to get the full lifecycle.
|
|
125
155
|
|
|
156
|
+
## Tests
|
|
157
|
+
|
|
158
|
+
`src/__tests__/assembly-suite.ts` runs on SQLite (`assembly.test.ts`) and on PostgreSQL (`assembly.optional.test.ts`, skipped without `DATABASE_URL`; `pnpm test:postgres` and the PostgreSQL CI lane run it on a transaction-scoped handle). Component tests under `src/svelte/__tests__` use jsdom and assert axe.
|
|
159
|
+
|
|
126
160
|
## Gotchas
|
|
127
161
|
|
|
128
162
|
- **`computeMaterialCost` without a resolver returns `$0`.** That is deliberate — manufacturing does not assume any particular cost source. A real wiring will plug in `@happyvertical/smrt-products` `Material.costPerUnit`, or a rolling average from purchase-order history, or a vendor price book. The `costUnavailable` flag tells UIs to surface "unknown cost" rather than silently rolling up zeros.
|
|
@@ -131,9 +165,11 @@ The companion handlers for `contract:created` (reserve) and `fulfillment:shipped
|
|
|
131
165
|
- **`consumeMaterials` propagates `InsufficientStockError`.** If a line would drive `available` below zero, the underlying `StockService.adjust` throws. Pre-flight with `canProduce` before posting if you want to avoid partial-failure mid-run.
|
|
132
166
|
- **`consumeMaterials` is atomic across BOM lines.** All per-line deductions and their audit rows run inside a single `stockService.withTransaction(...)` scope (powered by `@happyvertical/sql >= 0.74.0`'s native `db.transaction()`). An `InsufficientStockError` on line N+1 rolls back lines 1..N so production-order posting never leaves materials half-consumed. The recommended pre-flight (`BomService.canProduce(orderId, qty)`) is still useful when you'd rather know upfront than discover the shortfall mid-run, but a missed pre-flight no longer corrupts state.
|
|
133
167
|
- **`consumeMaterials` + `produceFinishedGoods` are NOT jointly atomic.** Each opens its own transaction. A failure on the produce leg leaves materials deducted with no finished SKU receipt to balance it. Use `runProduction(order, { consume, produce })` when you need both legs to commit or roll back together.
|
|
134
|
-
- **Cross-package references are plain strings.** `productId`, `componentSkuId`, `bomId` (within this package) — all plain string ids, never `@foreignKey()`.
|
|
168
|
+
- **Cross-package references are plain strings.** `productId`, `componentSkuId`, `bomId` (within this package) — all plain string ids, never `@foreignKey()`. The package does depend on `smrt-products` (for `Assembly` and the resolve helper), but the bill schema does not couple to the catalog's table layout.
|
|
169
|
+
- **`Assembly` fields are columns, not `Meta<T>`.** Unlike `Material`, its own fields are ordinary columns this package adds to `products` (filterable over REST). The PostgreSQL suite asserts they land there.
|
|
170
|
+
- **`AssemblyCollection` lists exact `Assembly` rows.** An application subtype (e.g. one adding a customer) has its own `_meta_type`; read it through its own collection or `ProductCollection` (`resolveComponent` uses `instanceof Assembly`, so subtypes resolve as assemblies).
|
|
135
171
|
- **`conflictColumns` include `tenant_id`** on both models. NULL-matching semantics are handled by `@happyvertical/sql >= 0.74.0`; two saves with the same `(product_id, version, NULL)` tuple merge in place.
|
|
136
|
-
- **Lazy table creation.** Like everything else in SMRT, the `manufacturing_boms` and `manufacturing_bom_lines` tables are created on first DB op via `syncSchema`. Safe for SSR.
|
|
172
|
+
- **Lazy table creation.** Like everything else in SMRT, the `manufacturing_boms` and `manufacturing_bom_lines` tables are created on first DB op via `syncSchema`. Safe for SSR. A `BomLine` save now reads `product_skus` and `products` for the cycle check.
|
|
137
173
|
- **Cross-industry constraint.** This package's vocabulary stays generic. Apparel-specific concepts (`Style`, `Makeup`, `Colorway`, `tech-pack`, fashion `Season`) and their analogues in furniture / automotive / CPG live in the relevant template package, never here. PRs that introduce industry vocabulary should be rejected.
|
|
138
174
|
|
|
139
175
|
## Source attribution
|
|
@@ -153,5 +189,8 @@ These join cleanly with the standard inventory reason codes (`receipt`, `reserva
|
|
|
153
189
|
|---|---|
|
|
154
190
|
| `@happyvertical/smrt-core` | SmrtObject / SmrtCollection / DispatchBus |
|
|
155
191
|
| `@happyvertical/smrt-inventory` | StockService, stock levels, movements (the `Sku` model itself lives in `@happyvertical/smrt-products`; inventory tracks stock motion against the id) |
|
|
192
|
+
| `@happyvertical/smrt-products` | `Product` (the `Assembly` STI base), `Material`, `Sku` for the resolve helper and cycle check |
|
|
193
|
+
| `@happyvertical/smrt-fields` | `ObjectForm` and field-policy column visibility for the Svelte components |
|
|
194
|
+
| `@happyvertical/smrt-ui` / `smrt-types` | Svelte controls, i18n, module UI slots |
|
|
156
195
|
| `@happyvertical/smrt-tenancy` | Optional tenant scoping |
|
|
157
196
|
| `@happyvertical/sql` | Database adapter |
|
package/README.md
CHANGED
|
@@ -8,10 +8,68 @@ Bills of materials, cost rollup, and production-order operations for the s-m-r-t
|
|
|
8
8
|
pnpm add @happyvertical/smrt-manufacturing
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
This package depends on `@happyvertical/smrt-inventory`
|
|
11
|
+
This package depends on `@happyvertical/smrt-inventory` for stock operations and on `@happyvertical/smrt-products` for the catalog: `Assembly` is a `Product` subtype, and bill lines point at `Sku` ids.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
+
### Define an assembly and its bill
|
|
16
|
+
|
|
17
|
+
An `Assembly` is a product that is made, the counterpart to `Material`. It lives in the shared `products` table (`productType: 'assembly'`) and adds three fields of its own: `estimatedLabourMinutes`, `defaultOperationId` and `partReference`. Its part number and stock come from its `Sku`; what it is made of comes from its bill. A sub-assembly is simply a bill line whose SKU belongs to another assembly.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { SkuCollection } from '@happyvertical/smrt-products/collections';
|
|
21
|
+
import {
|
|
22
|
+
AssemblyCollection,
|
|
23
|
+
BillOfMaterialsCollection,
|
|
24
|
+
BomLineCollection,
|
|
25
|
+
} from '@happyvertical/smrt-manufacturing';
|
|
26
|
+
|
|
27
|
+
const assemblies = await AssemblyCollection.create({ db });
|
|
28
|
+
const skus = await SkuCollection.create({ db });
|
|
29
|
+
const boms = await BillOfMaterialsCollection.create({ db });
|
|
30
|
+
const lines = await BomLineCollection.create({ db });
|
|
31
|
+
|
|
32
|
+
const panel = await assemblies.create({ name: 'Side panel', partReference: 'DWG-200' });
|
|
33
|
+
const panelSku = await skus.create({ productId: panel.id!, code: 'SP-100' });
|
|
34
|
+
|
|
35
|
+
const frame = await assemblies.create({
|
|
36
|
+
name: 'Frame',
|
|
37
|
+
partReference: 'DWG-100',
|
|
38
|
+
estimatedLabourMinutes: 95,
|
|
39
|
+
});
|
|
40
|
+
const frameBom = await boms.create({ productId: frame.id!, version: 1, status: 'active' });
|
|
41
|
+
|
|
42
|
+
// Two side panels per frame: a sub-assembly line.
|
|
43
|
+
await lines.create({ bomId: frameBom.id!, componentSkuId: panelSku.id!, qtyPerUnit: 2 });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Resolve a component, and refused cycles
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { AssemblyService, BomCycleError } from '@happyvertical/smrt-manufacturing';
|
|
50
|
+
|
|
51
|
+
const assemblyService = await AssemblyService.create({ db });
|
|
52
|
+
|
|
53
|
+
const component = await assemblyService.resolveComponent(panelSku.id!);
|
|
54
|
+
// component.kind: 'assembly' | 'material' | 'bought' | 'missing'
|
|
55
|
+
// component.activeBom: the sub-assembly's active bill, or null when it has none
|
|
56
|
+
// component.buildable: true only for an assembly with an active bill
|
|
57
|
+
|
|
58
|
+
const frameSku = await skus.create({ productId: frame.id!, code: 'FR-100' });
|
|
59
|
+
const panelBom = await boms.create({ productId: panel.id!, version: 1, status: 'active' });
|
|
60
|
+
try {
|
|
61
|
+
// The frame contains the panel, so the panel may not contain the frame.
|
|
62
|
+
await lines.create({ bomId: panelBom.id!, componentSkuId: frameSku.id!, qtyPerUnit: 1 });
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof BomCycleError) {
|
|
65
|
+
console.log(error.message);
|
|
66
|
+
// Refused: "Side panel" would contain itself: Side panel → Frame → Side panel
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every `BomLine` save, and every save of an `active` bill, runs this check, so the generated REST routes refuse a cycle too. Only active bills of sub-assemblies count; a draft or superseded bill is not part of the structure.
|
|
72
|
+
|
|
15
73
|
### Define a BOM with components
|
|
16
74
|
|
|
17
75
|
```typescript
|
|
@@ -71,6 +129,62 @@ console.log(rollup.totalCost, rollup.currency);
|
|
|
71
129
|
// Walks lines, applies waste, surfaces a per-line breakdown.
|
|
72
130
|
```
|
|
73
131
|
|
|
132
|
+
### Operations, routing and estimated labour
|
|
133
|
+
|
|
134
|
+
An `Operation` is a kind of work (cut, weld, inspect) kept as a managed list per
|
|
135
|
+
tenant. A bill of materials may carry a routing: an ordered list of operations,
|
|
136
|
+
each with an estimated duration in minutes for one unit. A bill with no routing
|
|
137
|
+
works exactly as before.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import {
|
|
141
|
+
BomService,
|
|
142
|
+
OperationService,
|
|
143
|
+
RoutingService,
|
|
144
|
+
} from '@happyvertical/smrt-manufacturing';
|
|
145
|
+
|
|
146
|
+
const operations = await OperationService.create({ db });
|
|
147
|
+
const cut = await operations.define({ code: 'CUT', name: 'Cutting', category: 'fabrication' });
|
|
148
|
+
const weld = await operations.define({
|
|
149
|
+
code: 'WELD',
|
|
150
|
+
name: 'Welding',
|
|
151
|
+
// Plain id of a smrt-human-resources Qualification; an application reads it
|
|
152
|
+
// to decide who may start the operation. Not validated here.
|
|
153
|
+
requiredQualificationId: qualificationId,
|
|
154
|
+
});
|
|
155
|
+
await operations.rename(cut.id!, 'Plasma cutting');
|
|
156
|
+
await operations.retire(weld.id!); // leaves pickers, stays on history
|
|
157
|
+
await operations.reinstate(weld.id!);
|
|
158
|
+
|
|
159
|
+
const routing = await RoutingService.create({ db });
|
|
160
|
+
await routing.replaceRouting(bom.id!, [
|
|
161
|
+
{ operationId: cut.id!, estimatedMinutes: 30 },
|
|
162
|
+
{ operationId: weld.id!, estimatedMinutes: 90, notes: 'tack first' },
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
const service = await BomService.create({
|
|
166
|
+
db,
|
|
167
|
+
// Optional, like costResolver: hourly rate for an operation, or null.
|
|
168
|
+
rateResolver: (operation) => (operation.code === 'WELD' ? 80 : 60),
|
|
169
|
+
});
|
|
170
|
+
const labour = await service.computeLabourEstimate(bom.id!);
|
|
171
|
+
console.log(labour.totalMinutes, labour.totalCost, labour.steps);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`replaceRouting` swaps the whole routing in one transaction (steps are numbered
|
|
175
|
+
1..n in the order given; an empty list removes it). A retired operation can stay
|
|
176
|
+
on a routing that already has it but cannot be newly added. Without a
|
|
177
|
+
`rateResolver` the estimate reports minutes only and flags each step
|
|
178
|
+
`rateUnavailable`.
|
|
179
|
+
|
|
180
|
+
### Operation list and form
|
|
181
|
+
|
|
182
|
+
`@happyvertical/smrt-manufacturing/svelte` exports props-driven `OperationList`
|
|
183
|
+
(with optional edit, retire and reinstate actions) and `OperationForm` (add, or
|
|
184
|
+
edit name, category and required qualification; the code is fixed once set).
|
|
185
|
+
They register with `ModuleUIRegistry` on import. Hosts load rows through
|
|
186
|
+
`OperationService` and perform the actions themselves.
|
|
187
|
+
|
|
74
188
|
### Plan a production run
|
|
75
189
|
|
|
76
190
|
```typescript
|
|
@@ -124,7 +238,7 @@ Every emitted `StockMovement` is stamped with `sourceType: 'ProductionOrder'` pl
|
|
|
124
238
|
|
|
125
239
|
### Multi-tenancy
|
|
126
240
|
|
|
127
|
-
|
|
241
|
+
`Assembly`, `BillOfMaterials` and `BomLine` use `@TenantScoped({ mode: 'optional' })` with a nullable `tenantId`. Wrap mutations in `withTenant()` from `@happyvertical/smrt-tenancy` to scope queries automatically.
|
|
128
242
|
|
|
129
243
|
```typescript
|
|
130
244
|
import { withTenant } from '@happyvertical/smrt-tenancy';
|
|
@@ -170,28 +284,62 @@ await bus.emit('production_order:posted', {
|
|
|
170
284
|
|
|
171
285
|
Per-handler toggles (`installProductionPosted`, `installProductionCompleted`) let consumers pick exactly the legs they want. The companion `contract:created` and `fulfillment:shipped` handlers live in `@happyvertical/smrt-inventory`.
|
|
172
286
|
|
|
287
|
+
### Svelte components
|
|
288
|
+
|
|
289
|
+
`@happyvertical/smrt-manufacturing/svelte` ships props-driven components; the host loads data and saves it.
|
|
290
|
+
|
|
291
|
+
```svelte
|
|
292
|
+
<script lang="ts">
|
|
293
|
+
import {
|
|
294
|
+
AssemblyForm,
|
|
295
|
+
AssemblyList,
|
|
296
|
+
BomEditor,
|
|
297
|
+
} from '@happyvertical/smrt-manufacturing/svelte';
|
|
298
|
+
|
|
299
|
+
let { assemblies, policy, fields, lines, components } = $props();
|
|
300
|
+
</script>
|
|
301
|
+
|
|
302
|
+
<AssemblyList {assemblies} {policy} onselect={(id) => goto(`/assemblies/${id}`)} />
|
|
303
|
+
<AssemblyForm {fields} {policy} onsubmit={save} />
|
|
304
|
+
<BomEditor {lines} {components} onadd={addLine} loadBill={loadLines} error={saveError} />
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`AssemblyList` and `AssemblyForm` follow the consumer's field policy for `@happyvertical/smrt-manufacturing:Assembly` (a hidden price is not shown). Build rows with `toAssemblyView` and editor lines with `toBomEditorLines(await assemblyService.getBillStructure(bomId))`. A sub-assembly line in `BomEditor` says whether it has its own bill and expands read-only through `loadBill`.
|
|
308
|
+
|
|
173
309
|
## API
|
|
174
310
|
|
|
175
311
|
### Models
|
|
176
312
|
|
|
177
313
|
| Export | Description |
|
|
178
314
|
|---|---|
|
|
315
|
+
| `Assembly` | A product that is made: `Product` STI subtype with `estimatedLabourMinutes`, `defaultOperationId`, `partReference`. |
|
|
179
316
|
| `BillOfMaterials` | Recipe for one finished product. Versioned with a `draft` / `active` / `superseded` lifecycle. |
|
|
180
317
|
| `BomLine` | One component on a BOM. `effectiveQtyPerUnit()` returns the qty including waste. |
|
|
318
|
+
| `Operation` | A kind of work: `code` (unique per tenant), `name`, `category`, `isActive` (retired operations stay on history, never deleted), optional `requiredQualificationId` (plain string id of a `smrt-human-resources` qualification). |
|
|
319
|
+
| `RoutingStep` | One operation in a BOM's optional routing: `bomId`, `operationId`, `sequence` (1..n), `estimatedMinutes`, `notes`. |
|
|
181
320
|
|
|
182
321
|
### Collections
|
|
183
322
|
|
|
184
323
|
| Export | Description |
|
|
185
324
|
|---|---|
|
|
325
|
+
| `AssemblyCollection` | Assembly rows of the shared `products` table. |
|
|
186
326
|
| `BillOfMaterialsCollection` | `findByProduct`, `findActiveForProduct`, `findByStatus` |
|
|
187
327
|
| `BomLineCollection` | `findByBom`, `findByComponent` |
|
|
328
|
+
| `OperationCollection` | `findByCode`, `listOperations({ includeRetired? })` |
|
|
329
|
+
| `RoutingStepCollection` | `findByBom` (in step order), `findByOperation` |
|
|
188
330
|
|
|
189
331
|
### Services
|
|
190
332
|
|
|
191
333
|
| Export | Description |
|
|
192
334
|
|---|---|
|
|
335
|
+
| `AssemblyService` | `resolveComponent`, `findActiveBom`, `isAssembly`, `getBillStructure`, `findCycle`, `assertLineAcyclic`, `assertBillAcyclic`. Pass `{ tenantId }` to read one tenant's structure. |
|
|
336
|
+
| `createAssemblyService({ db })` | Convenience factory. |
|
|
337
|
+
| `BomCycleError` | A `ValidationError` naming the `path` by which a save would make a product contain itself. |
|
|
193
338
|
| `BomService` | Cost rollup, requirements explosion, can-produce check. |
|
|
194
|
-
| `createBomService({ db, costResolver? })` | Convenience factory. |
|
|
339
|
+
| `createBomService({ db, costResolver?, rateResolver? })` | Convenience factory. `computeLabourEstimate(bomId)` rolls up routing minutes and, with a `rateResolver`, cost. |
|
|
340
|
+
| `OperationService` / `createOperationService({ db })` | `define`, `get`, `list`, `rename`, `update`, `retire`, `reinstate`. |
|
|
341
|
+
| `RoutingService` / `createRoutingService({ db })` | `list(bomId)`, `replaceRouting(bomId, steps)`. |
|
|
342
|
+
| `OperationNotFoundError`, `DuplicateOperationCodeError`, `OperationRetiredError`, `InvalidOperationInputError` | Operation and routing errors. |
|
|
195
343
|
| `ProductionService` | Operational consume / produce against a production order. |
|
|
196
344
|
| `createProductionService({ db })` | Convenience factory. |
|
|
197
345
|
| `installManufacturingDispatchHandlers({ dispatchBus, db })` | Opt-in bus wiring. |
|
|
@@ -203,12 +351,18 @@ Per-handler toggles (`installProductionPosted`, `installProductionCompleted`) le
|
|
|
203
351
|
| Export | Description |
|
|
204
352
|
|---|---|
|
|
205
353
|
| `BomStatus` | `'draft' \| 'active' \| 'superseded'` |
|
|
354
|
+
| `ComponentKind` | `'assembly' \| 'material' \| 'bought' \| 'missing'` |
|
|
355
|
+
| `ResolvedComponent` | Return shape of `resolveComponent`. |
|
|
356
|
+
| `BillStructure` | Return shape of `getBillStructure`. |
|
|
206
357
|
| `BomCostRollup` | Return shape of `computeMaterialCost`. |
|
|
207
358
|
| `BomLineCost` | Per-line entry inside a `BomCostRollup`. |
|
|
208
359
|
| `MaterialRequirement` | Entry returned by `explodeRequirements`. |
|
|
209
360
|
| `MaterialShortage` | Entry returned by `canProduce` when stock is insufficient. |
|
|
210
361
|
| `CanProduceResult` | `{ ok: true; shortages: [] } \| { ok: false; shortages: [...] }` |
|
|
211
362
|
| `ComponentCostResolver` | Async (or sync) callback returning unit cost or `null`. |
|
|
363
|
+
| `OperationRateResolver` | Async (or sync) callback returning an operation's hourly rate or `null`. |
|
|
364
|
+
| `LabourEstimate`, `RoutingStepEstimate` | Return shape of `computeLabourEstimate`. |
|
|
365
|
+
| `DefineOperationInput`, `RoutingStepInput` | Inputs to `define` and `replaceRouting`. |
|
|
212
366
|
|
|
213
367
|
## Dependencies
|
|
214
368
|
|
|
@@ -216,7 +370,11 @@ Per-handler toggles (`installProductionPosted`, `installProductionCompleted`) le
|
|
|
216
370
|
|---|---|
|
|
217
371
|
| `@happyvertical/smrt-core` | SmrtObject / SmrtCollection / DispatchBus |
|
|
218
372
|
| `@happyvertical/smrt-inventory` | StockService (consume / produce target) |
|
|
373
|
+
| `@happyvertical/smrt-products` | `Product` (base of `Assembly`), `Material`, `Sku` |
|
|
374
|
+
| `@happyvertical/smrt-fields` | `ObjectForm` and field-policy columns for the Svelte components |
|
|
375
|
+
| `@happyvertical/smrt-ui` | Svelte controls and i18n |
|
|
219
376
|
| `@happyvertical/smrt-tenancy` | Optional tenant scoping |
|
|
377
|
+
| `@happyvertical/smrt-ui` / `@happyvertical/smrt-types` | Operation list and form (`./svelte`), module slots (`./ui`); `svelte` is an optional peer |
|
|
220
378
|
| `@happyvertical/sql` | Database adapter |
|
|
221
379
|
|
|
222
380
|
## License
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AssemblyCollection — collection for {@link Assembly} rows.
|
|
3
|
+
*
|
|
4
|
+
* Assemblies share the `products` table with every other `Product` subtype;
|
|
5
|
+
* the STI framework filters this collection to assembly rows by
|
|
6
|
+
* `_meta_type`, so `list()` and `get()` only ever return assemblies.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import { ProductCollection } from '@happyvertical/smrt-products/collections';
|
|
11
|
+
import { Assembly } from '../models/Assembly.js';
|
|
12
|
+
export declare class AssemblyCollection extends ProductCollection {
|
|
13
|
+
static readonly _itemClass: typeof Assembly;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=AssemblyCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssemblyCollection.d.ts","sourceRoot":"","sources":["../../src/collections/AssemblyCollection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,qBAAa,kBAAmB,SAAQ,iBAAiB;IACvD,gBAAyB,UAAU,kBAAY;CAChD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OperationCollection — query helpers for {@link Operation} rows.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
7
|
+
import { Operation } from '../models/Operation.js';
|
|
8
|
+
export declare class OperationCollection extends SmrtCollection<Operation> {
|
|
9
|
+
static readonly _itemClass: typeof Operation;
|
|
10
|
+
/** Return the operation with the given code, or `null`. */
|
|
11
|
+
findByCode(code: string): Promise<Operation | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Return operations ordered by category then code. Retired operations are
|
|
14
|
+
* left out unless `includeRetired` is set, so the default is a picker list.
|
|
15
|
+
*/
|
|
16
|
+
listOperations(options?: {
|
|
17
|
+
includeRetired?: boolean;
|
|
18
|
+
}): Promise<Operation[]>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=OperationCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OperationCollection.d.ts","sourceRoot":"","sources":["../../src/collections/OperationCollection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,qBAAa,mBAAoB,SAAQ,cAAc,CAAC,SAAS,CAAC;IAChE,MAAM,CAAC,QAAQ,CAAC,UAAU,mBAAa;IAEvC,2DAA2D;IACrD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAKzD;;;OAGG;IACG,cAAc,CAClB,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAO,GACzC,OAAO,CAAC,SAAS,EAAE,CAAC;CAMxB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoutingStepCollection — query helpers for {@link RoutingStep} rows.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
7
|
+
import { RoutingStep } from '../models/RoutingStep.js';
|
|
8
|
+
export declare class RoutingStepCollection extends SmrtCollection<RoutingStep> {
|
|
9
|
+
static readonly _itemClass: typeof RoutingStep;
|
|
10
|
+
/** Return the routing of a bill in step order; empty when it has none. */
|
|
11
|
+
findByBom(bomId: string): Promise<RoutingStep[]>;
|
|
12
|
+
/** Return every step that uses the given operation, across bills. */
|
|
13
|
+
findByOperation(operationId: string): Promise<RoutingStep[]>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=RoutingStepCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoutingStepCollection.d.ts","sourceRoot":"","sources":["../../src/collections/RoutingStepCollection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,qBAAa,qBAAsB,SAAQ,cAAc,CAAC,WAAW,CAAC;IACpE,MAAM,CAAC,QAAQ,CAAC,UAAU,qBAAe;IAEzC,0EAA0E;IACpE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAItD,qEAAqE;IAC/D,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAGnE"}
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* @packageDocumentation
|
|
10
10
|
*/
|
|
11
11
|
import '../__smrt-register__.js';
|
|
12
|
+
export { AssemblyCollection } from './AssemblyCollection.js';
|
|
12
13
|
export { BillOfMaterialsCollection } from './BillOfMaterialsCollection.js';
|
|
13
14
|
export { BomLineCollection } from './BomLineCollection.js';
|
|
15
|
+
export { OperationCollection } from './OperationCollection.js';
|
|
16
|
+
export { RoutingStepCollection } from './RoutingStepCollection.js';
|
|
14
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,yBAAyB,CAAC;AAEjC,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,yBAAyB,CAAC;AAEjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,13 +9,22 @@
|
|
|
9
9
|
*
|
|
10
10
|
* **Model hierarchy**
|
|
11
11
|
*
|
|
12
|
+
* - {@link Assembly} — the product that is made: a `Product` STI subtype
|
|
13
|
+
* (`productType: 'assembly'`) in the shared `products` table. Nesting
|
|
14
|
+
* lives in its bill: a line whose component SKU belongs to another
|
|
15
|
+
* assembly is a sub-assembly.
|
|
12
16
|
* - {@link BillOfMaterials} — recipe for one finished product. Multiple
|
|
13
17
|
* revisions per product via `version` + `status` lifecycle.
|
|
14
18
|
* - {@link BomLine} — one component on a BOM with `qtyPerUnit`, `uom`,
|
|
15
19
|
* and optional `wastePercent`.
|
|
20
|
+
* - {@link Operation} — a kind of work (cut, weld, inspect); a managed list.
|
|
21
|
+
* - {@link RoutingStep} — an operation in a BOM's optional routing, with an
|
|
22
|
+
* estimated duration.
|
|
16
23
|
*
|
|
17
24
|
* **Services**
|
|
18
25
|
*
|
|
26
|
+
* - {@link AssemblyService} — resolve a component SKU to an assembly (and
|
|
27
|
+
* its active bill), a material, or a bought item; refuse cycles.
|
|
19
28
|
* - {@link BomService} — cost rollup, requirements explosion,
|
|
20
29
|
* "can we make this?" stock availability check.
|
|
21
30
|
* - {@link ProductionService} — operational bridge from a production
|
|
@@ -27,8 +36,9 @@
|
|
|
27
36
|
* @packageDocumentation
|
|
28
37
|
*/
|
|
29
38
|
import './__smrt-register__.js';
|
|
30
|
-
export { BillOfMaterialsCollection, BomLineCollection, } from './collections/index.js';
|
|
31
|
-
export { BillOfMaterials, type BillOfMaterialsOptions, BomLine, type BomLineOptions, } from './models/index.js';
|
|
32
|
-
export {
|
|
39
|
+
export { AssemblyCollection, BillOfMaterialsCollection, BomLineCollection, OperationCollection, RoutingStepCollection, } from './collections/index.js';
|
|
40
|
+
export { Assembly, type AssemblyOptions, BillOfMaterials, type BillOfMaterialsOptions, BomLine, type BomLineOptions, Operation, type OperationOptions, RoutingStep, type RoutingStepOptions, } from './models/index.js';
|
|
41
|
+
export { type DefineOperationInput, DuplicateOperationCodeError, InvalidOperationInputError, type LabourEstimate, OperationNotFoundError, type OperationRateResolver, OperationRetiredError, type RoutingStepEstimate, type RoutingStepInput, } from './operation-types.js';
|
|
42
|
+
export { AssemblyService, type AssemblyServiceOptions, type BillStructure, type BillStructureLine, BomCycleError, type BomCyclePathEntry, BomService, type BomServiceOptions, type ComponentCostResolver, type ComponentKind, type ConsumeMaterialsOptions, type ConsumeResult, createAssemblyService, createBomService, createOperationService, createProductionService, createRoutingService, type InstalledManufacturingDispatchHandlers, type InstallManufacturingDispatchHandlersOptions, installManufacturingDispatchHandlers, OperationService, type OperationServiceOptions, type ProduceFinishedGoodsOptions, type ProduceResult, type ProductionOrderCompletedPayload, type ProductionOrderPostedPayload, type ProductionOrderRef, ProductionService, type ProductionServiceOptions, type ResolveComponentOptions, type ResolvedComponent, RoutingService, type RoutingServiceOptions, } from './services/index.js';
|
|
33
43
|
export { type BomCostRollup, type BomLineCost, BomNotFoundError, type BomStatus, type CanProduceResult, type MaterialRequirement, type MaterialShortage, NoActiveBomForProductError, } from './types.js';
|
|
34
44
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAMH,OAAO,wBAAwB,CAAC;AAKhC,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EACL,QAAQ,EACR,KAAK,eAAe,EACpB,eAAe,EACf,KAAK,sBAAsB,EAC3B,OAAO,EACP,KAAK,cAAc,EACnB,SAAS,EACT,KAAK,gBAAgB,EACrB,WAAW,EACX,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EACL,KAAK,oBAAoB,EACzB,2BAA2B,EAC3B,0BAA0B,EAC1B,KAAK,cAAc,EACnB,sBAAsB,EACtB,KAAK,qBAAqB,EAC1B,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,aAAa,EACb,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,sCAAsC,EAC3C,KAAK,2CAA2C,EAChD,oCAAoC,EACpC,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,aAAa,EAClB,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,cAAc,EACd,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,gBAAgB,EAChB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,0BAA0B,GAC3B,MAAM,YAAY,CAAC"}
|