@operato/ops-contract 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract.d.ts +35 -12
- package/dist/domain-definition.d.ts +21 -3
- package/dist/erp.d.ts +7 -12
- package/dist/erp.js +6 -6
- package/dist-cjs/index.cjs +5 -5
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -1798,12 +1798,7 @@ export interface TaskState {
|
|
|
1798
1798
|
* **실제로 들어가고 나온 자재** — ISA-95 `JobResponse.MaterialActual`(`OpMaterialActualType`).
|
|
1799
1799
|
* 명세(계획)가 아니라 일어난 일이다. 투입 인원·설비와 같은 채널에 실어 실적을 한 곳에서 읽는다.
|
|
1800
1800
|
*/
|
|
1801
|
-
materialActual?:
|
|
1802
|
-
definitionId: string;
|
|
1803
|
-
use: 'consumed' | 'produced';
|
|
1804
|
-
quantity: number;
|
|
1805
|
-
uom?: string;
|
|
1806
|
-
}[];
|
|
1801
|
+
materialActual?: MaterialActual[];
|
|
1807
1802
|
}
|
|
1808
1803
|
/**
|
|
1809
1804
|
* 오더 하나의 관측 상태 — **ISA-95 `OperationsRequest`**(Part 4, 일정).
|
|
@@ -3102,6 +3097,39 @@ export interface QualityDelta {
|
|
|
3102
3097
|
*/
|
|
3103
3098
|
taskId?: string;
|
|
3104
3099
|
}
|
|
3100
|
+
/**
|
|
3101
|
+
* 실제로 들어가고 나온 자재 한 줄 — ISA-95 `OpMaterialActualType` 의 부분집합.
|
|
3102
|
+
*
|
|
3103
|
+
* ── 왜 이름 있는 타입인가 (2026-08-31) ────────────────────────────────────
|
|
3104
|
+
* 같은 데이터가 네 곳에 각각 인라인으로 적혀 있었다(작업 상태 둘 · 커널 · ERP). ERP 쪽은 필드명까지
|
|
3105
|
+
* 달랐다(`materialDefinitionId` · `direction`). 같은 데이터를 여러 이름으로 부르면 옮기는 코드가
|
|
3106
|
+
* 생기고, 그 코드가 언젠가 어긋난다.
|
|
3107
|
+
*
|
|
3108
|
+
* 표준 매핑은 커널이 이미 적어 두었다 — `MaterialDefinitionID` → `definitionId`,
|
|
3109
|
+
* `MaterialUse` → `use`. 그 이름을 그대로 쓴다.
|
|
3110
|
+
*/
|
|
3111
|
+
export interface MaterialActual {
|
|
3112
|
+
/** 표준 `MaterialDefinitionID`. */
|
|
3113
|
+
definitionId: string;
|
|
3114
|
+
/** 표준 `MaterialUse`. */
|
|
3115
|
+
use: 'consumed' | 'produced';
|
|
3116
|
+
quantity: number;
|
|
3117
|
+
uom?: string;
|
|
3118
|
+
/**
|
|
3119
|
+
* 어느 로트를 썼는가 — 표준 `MaterialLotID`.
|
|
3120
|
+
*
|
|
3121
|
+
* ── 값은 식별자다 (2026-08-31, MES 레인 요청) ─────────────────────────────
|
|
3122
|
+
* 로트 번호 문자열이 아니라 **식별자**를 넣는다. GS1 로 식별되는 로트는
|
|
3123
|
+
* `urn:epc:class:lgtin:<프리픽스>.<품번>.<로트>` 이고, 그렇지 않으면 원본 시스템의 로트 식별자다.
|
|
3124
|
+
*
|
|
3125
|
+
* 문자열을 기준값으로 두면 이것을 쓰는 네 곳에서 각각 식별자를 다시 조립해야 한다 — 불합격 로트의
|
|
3126
|
+
* 이동 제한, EPCIS `TransformationEvent` 의 입력·출력 목록, 현장 자재 스캔, 로트 엔티티. 조립
|
|
3127
|
+
* 규칙이 네 곳에 생기면 그중 하나가 달라지는 날 그 경로에서만 로트가 맞지 않고 오류는 발생하지 않는다.
|
|
3128
|
+
*
|
|
3129
|
+
* 사람이 읽는 로트 번호는 식별자에서 파생한다 — `ItemState.lot` 이 이미 그 방식이다.
|
|
3130
|
+
*/
|
|
3131
|
+
lotId?: string;
|
|
3132
|
+
}
|
|
3105
3133
|
export interface TaskStatusDelta {
|
|
3106
3134
|
taskId: string;
|
|
3107
3135
|
/** 착수 시각(절대 sim-clock) — 보간 앵커. TaskState 와 같은 뜻. */
|
|
@@ -3158,12 +3186,7 @@ export interface TaskStatusDelta {
|
|
|
3158
3186
|
* **실제로 들어가고 나온 자재** — ISA-95 `JobResponse.MaterialActual`(`OpMaterialActualType`).
|
|
3159
3187
|
* 명세(계획)가 아니라 일어난 일이다. 투입 인원·설비와 같은 채널에 실어 실적을 한 곳에서 읽는다.
|
|
3160
3188
|
*/
|
|
3161
|
-
materialActual?:
|
|
3162
|
-
definitionId: string;
|
|
3163
|
-
use: 'consumed' | 'produced';
|
|
3164
|
-
quantity: number;
|
|
3165
|
-
uom?: string;
|
|
3166
|
-
}[];
|
|
3189
|
+
materialActual?: MaterialActual[];
|
|
3167
3190
|
}
|
|
3168
3191
|
export interface EquipmentStatusDelta extends EffectivePeriod {
|
|
3169
3192
|
moverId: string;
|
|
@@ -239,7 +239,13 @@ export interface OpMaterialSpecification {
|
|
|
239
239
|
uom?: string;
|
|
240
240
|
}
|
|
241
241
|
/** 라우트(오퍼레이션 시퀀스) — ISA-95 ProcessSegment 연결. */
|
|
242
|
-
|
|
242
|
+
/**
|
|
243
|
+
* 공정을 지나는 순서.
|
|
244
|
+
*
|
|
245
|
+
* 유효 기간은 레시피와 같은 이유로 있다 — 라우트도 개정되고, 지난 실적이 어느 경로로 만든 것인지
|
|
246
|
+
* 잃으면 추적이 성립하지 않는다(§`RecipeDef`).
|
|
247
|
+
*/
|
|
248
|
+
export interface RouteDef extends EffectivePeriod {
|
|
243
249
|
key: string;
|
|
244
250
|
label: string;
|
|
245
251
|
/** OperationDef.key 순서. */
|
|
@@ -323,8 +329,20 @@ export interface RecipePart {
|
|
|
323
329
|
*/
|
|
324
330
|
operation?: string;
|
|
325
331
|
}
|
|
326
|
-
/**
|
|
327
|
-
|
|
332
|
+
/**
|
|
333
|
+
* BOM/레시피 — ISA-95 Material Consumed/Produced · EPCIS TransformationEvent(input→output).
|
|
334
|
+
*
|
|
335
|
+
* ── 유효 기간을 왜 여기 두나 (2026-08-31) ────────────────────────────────
|
|
336
|
+
* **레시피는 개정된다.** 그런데 이 타입에 그 자리가 없어서, 마스터가 `effectiveStart`·`effectiveEnd` 를
|
|
337
|
+
* 채워 보내도 커널이 오류 없이 지나쳤다 — 값을 넣어도 트윈에 닿지 않았다.
|
|
338
|
+
*
|
|
339
|
+
* 그것이 없으면 **지난 실적이 어느 배합으로 만든 것인지 잃는다.** 정산과 추적이 그 위에 선다.
|
|
340
|
+
* 마스터가 행을 지우지 않고 이 칸을 채우는 이유도 그것이다.
|
|
341
|
+
*
|
|
342
|
+
* 선언 적합성 하네스가 잡았다(`ops-master/test/declaration-reaches-contract.test.ts`) — 마스터가
|
|
343
|
+
* 내보내는 칸이 계약에 실재하는지 보는 시험이다.
|
|
344
|
+
*/
|
|
345
|
+
export interface RecipeDef extends EffectivePeriod {
|
|
328
346
|
key: string;
|
|
329
347
|
label: string;
|
|
330
348
|
inputs: RecipePart[];
|
package/dist/erp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ISOTime } from './contract.ts';
|
|
1
|
+
import type { ISOTime, MaterialActual } from './contract.ts';
|
|
2
2
|
/**
|
|
3
3
|
* **무엇을 얼마나 만들라** — 이 사실을 나르는 자리가 둘이고, 어휘는 하나다.
|
|
4
4
|
*
|
|
@@ -106,7 +106,12 @@ export interface ProductionPerformanceEntry {
|
|
|
106
106
|
to: ISOTime;
|
|
107
107
|
/** 누가 했나 — ERP 의 인사번호. 「누가 적었나」는 여기 오지 않는다(MES 의 감사 추적이다). */
|
|
108
108
|
personId?: string;
|
|
109
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* 쓴 자재 — 소비 문서가 된다. 없으면 자재 소비를 올리지 않는다(0 으로 채우지 않는다).
|
|
111
|
+
*
|
|
112
|
+
* 모양은 커널과 같은 `MaterialActual` 이다. 처음에는 여기에 따로 적었고 필드명이 달랐다
|
|
113
|
+
* (`materialDefinitionId` · `direction`) — 같은 데이터를 두 이름으로 부르면 옮기는 코드가 생긴다.
|
|
114
|
+
*/
|
|
110
115
|
materialActual?: MaterialActual[];
|
|
111
116
|
/**
|
|
112
117
|
* **이것이 이 지시의 마지막 실적인가** — ERP 가 이 값으로 오더를 닫는다.
|
|
@@ -117,16 +122,6 @@ export interface ProductionPerformanceEntry {
|
|
|
117
122
|
final?: boolean;
|
|
118
123
|
recordTime?: ISOTime;
|
|
119
124
|
}
|
|
120
|
-
/** 실제로 들어가고 나온 자재 한 줄. */
|
|
121
|
-
export interface MaterialActual {
|
|
122
|
-
/** 마스터의 `MaterialDefinition` 식별자 — 지시와 같은 이름을 쓴다. */
|
|
123
|
-
materialDefinitionId: string;
|
|
124
|
-
quantity: number;
|
|
125
|
-
uom: string;
|
|
126
|
-
/** `consumed` 소비 · `produced` 산출. 부호로 구별하지 않는다 — 음수는 되돌림과 섞인다. */
|
|
127
|
-
direction: 'consumed' | 'produced';
|
|
128
|
-
lot?: string;
|
|
129
|
-
}
|
|
130
125
|
/**
|
|
131
126
|
* 실적의 **멱등 키** — 같은 실적이 두 번 올라가지 않게.
|
|
132
127
|
*
|
package/dist/erp.js
CHANGED
|
@@ -88,14 +88,14 @@ export function validatePerformance(e, nowMs) {
|
|
|
88
88
|
errors.push('아직 오지 않은 시각까지의 실적이다');
|
|
89
89
|
}
|
|
90
90
|
for (const m of e?.materialActual ?? []) {
|
|
91
|
-
if (!String(m?.
|
|
92
|
-
errors.push('자재 줄에
|
|
91
|
+
if (!String(m?.definitionId ?? '').trim())
|
|
92
|
+
errors.push('자재 줄에 definitionId 가 없다');
|
|
93
93
|
if (!String(m?.uom ?? '').trim())
|
|
94
|
-
errors.push(`${m?.
|
|
94
|
+
errors.push(`${m?.definitionId}: uom 없음 — 단위는 ERP 의 사실이므로 지어내지 않는다`);
|
|
95
95
|
if (typeof m?.quantity !== 'number' || !(m.quantity > 0))
|
|
96
|
-
errors.push(`${m?.
|
|
97
|
-
if (m?.
|
|
98
|
-
errors.push(`${m?.
|
|
96
|
+
errors.push(`${m?.definitionId}: 수량이 0 이하다`);
|
|
97
|
+
if (m?.use !== 'consumed' && m?.use !== 'produced') {
|
|
98
|
+
errors.push(`${m?.definitionId}: use 가 consumed · produced 가 아니다`);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
return errors;
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -3930,11 +3930,11 @@ function validatePerformance(e, nowMs) {
|
|
|
3930
3930
|
if (to > nowMs) errors.push("\uC544\uC9C1 \uC624\uC9C0 \uC54A\uC740 \uC2DC\uAC01\uAE4C\uC9C0\uC758 \uC2E4\uC801\uC774\uB2E4");
|
|
3931
3931
|
}
|
|
3932
3932
|
for (const m of e?.materialActual ?? []) {
|
|
3933
|
-
if (!String(m?.
|
|
3934
|
-
if (!String(m?.uom ?? "").trim()) errors.push(`${m?.
|
|
3935
|
-
if (typeof m?.quantity !== "number" || !(m.quantity > 0)) errors.push(`${m?.
|
|
3936
|
-
if (m?.
|
|
3937
|
-
errors.push(`${m?.
|
|
3933
|
+
if (!String(m?.definitionId ?? "").trim()) errors.push("\uC790\uC7AC \uC904\uC5D0 definitionId \uAC00 \uC5C6\uB2E4");
|
|
3934
|
+
if (!String(m?.uom ?? "").trim()) errors.push(`${m?.definitionId}: uom \uC5C6\uC74C \u2014 \uB2E8\uC704\uB294 ERP \uC758 \uC0AC\uC2E4\uC774\uBBC0\uB85C \uC9C0\uC5B4\uB0B4\uC9C0 \uC54A\uB294\uB2E4`);
|
|
3935
|
+
if (typeof m?.quantity !== "number" || !(m.quantity > 0)) errors.push(`${m?.definitionId}: \uC218\uB7C9\uC774 0 \uC774\uD558\uB2E4`);
|
|
3936
|
+
if (m?.use !== "consumed" && m?.use !== "produced") {
|
|
3937
|
+
errors.push(`${m?.definitionId}: use \uAC00 consumed \xB7 produced \uAC00 \uC544\uB2C8\uB2E4`);
|
|
3938
3938
|
}
|
|
3939
3939
|
}
|
|
3940
3940
|
return errors;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/ops-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Operations domain contract — the standard vocabulary that producers and readers agree on (EPCIS 2.0/GS1, ISA-95, IEC 61850/ISO 50001). Types, guards, validation. No state, no engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist-cjs/index.cjs",
|