@things-factory/ops-master 10.0.18 → 10.1.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-client/bootstrap.d.ts +18 -0
- package/dist-client/bootstrap.js +36 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/index.d.ts +15 -1
- package/dist-client/index.js +18 -1
- package/dist-client/index.js.map +1 -1
- package/dist-client/pages/master/equipment-class.d.ts +12 -0
- package/dist-client/pages/master/equipment-class.js +42 -0
- package/dist-client/pages/master/equipment-class.js.map +1 -0
- package/dist-client/pages/master/equipment.d.ts +12 -0
- package/dist-client/pages/master/equipment.js +57 -0
- package/dist-client/pages/master/equipment.js.map +1 -0
- package/dist-client/pages/master/grist-iso-duration.d.ts +20 -0
- package/dist-client/pages/master/grist-iso-duration.js +70 -0
- package/dist-client/pages/master/grist-iso-duration.js.map +1 -0
- package/dist-client/pages/master/grist-reference.d.ts +91 -0
- package/dist-client/pages/master/grist-reference.js +419 -0
- package/dist-client/pages/master/grist-reference.js.map +1 -0
- package/dist-client/pages/master/import-rows.d.ts +35 -0
- package/dist-client/pages/master/import-rows.js +66 -0
- package/dist-client/pages/master/import-rows.js.map +1 -0
- package/dist-client/pages/master/iso-duration.d.ts +34 -0
- package/dist-client/pages/master/iso-duration.js +76 -0
- package/dist-client/pages/master/iso-duration.js.map +1 -0
- package/dist-client/pages/master/master-options.d.ts +33 -0
- package/dist-client/pages/master/master-options.js +47 -0
- package/dist-client/pages/master/master-options.js.map +1 -0
- package/dist-client/pages/master/master-page-base.d.ts +357 -0
- package/dist-client/pages/master/master-page-base.js +626 -0
- package/dist-client/pages/master/master-page-base.js.map +1 -0
- package/dist-client/pages/master/material-class.d.ts +12 -0
- package/dist-client/pages/master/material-class.js +42 -0
- package/dist-client/pages/master/material-class.js.map +1 -0
- package/dist-client/pages/master/material-definition.d.ts +12 -0
- package/dist-client/pages/master/material-definition.js +62 -0
- package/dist-client/pages/master/material-definition.js.map +1 -0
- package/dist-client/pages/master/operation-definition.d.ts +12 -0
- package/dist-client/pages/master/operation-definition.js +49 -0
- package/dist-client/pages/master/operation-definition.js.map +1 -0
- package/dist-client/pages/master/operational-location.d.ts +84 -0
- package/dist-client/pages/master/operational-location.js +426 -0
- package/dist-client/pages/master/operational-location.js.map +1 -0
- package/dist-client/pages/master/person.d.ts +12 -0
- package/dist-client/pages/master/person.js +41 -0
- package/dist-client/pages/master/person.js.map +1 -0
- package/dist-client/pages/master/personnel-class.d.ts +12 -0
- package/dist-client/pages/master/personnel-class.js +43 -0
- package/dist-client/pages/master/personnel-class.js.map +1 -0
- package/dist-client/pages/master/physical-asset-class.d.ts +12 -0
- package/dist-client/pages/master/physical-asset-class.js +40 -0
- package/dist-client/pages/master/physical-asset-class.js.map +1 -0
- package/dist-client/pages/master/physical-asset.d.ts +12 -0
- package/dist-client/pages/master/physical-asset.js +41 -0
- package/dist-client/pages/master/physical-asset.js.map +1 -0
- package/dist-client/pages/master/recipe-definition.d.ts +136 -0
- package/dist-client/pages/master/recipe-definition.js +404 -0
- package/dist-client/pages/master/recipe-definition.js.map +1 -0
- package/dist-client/pages/master/reference-targets.d.ts +42 -0
- package/dist-client/pages/master/reference-targets.js +82 -0
- package/dist-client/pages/master/reference-targets.js.map +1 -0
- package/dist-client/pages/master/route-definition.d.ts +19 -0
- package/dist-client/pages/master/route-definition.js +40 -0
- package/dist-client/pages/master/route-definition.js.map +1 -0
- package/dist-client/pages/master/stop-reason.d.ts +12 -0
- package/dist-client/pages/master/stop-reason.js +48 -0
- package/dist-client/pages/master/stop-reason.js.map +1 -0
- package/dist-client/pages/master/test-specification.d.ts +93 -0
- package/dist-client/pages/master/test-specification.js +449 -0
- package/dist-client/pages/master/test-specification.js.map +1 -0
- package/dist-client/pages/master-axes.d.ts +20 -29
- package/dist-client/pages/master-axes.js +32 -169
- package/dist-client/pages/master-axes.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +76 -3
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/asset/asset-lifecycle.d.ts +30 -7
- package/dist-server/service/asset/asset-lifecycle.js +33 -6
- package/dist-server/service/asset/asset-lifecycle.js.map +1 -1
- package/dist-server/service/asset/asset-master-resolver.d.ts +9 -0
- package/dist-server/service/asset/asset-master-resolver.js +41 -0
- package/dist-server/service/asset/asset-master-resolver.js.map +1 -1
- package/dist-server/service/asset/asset-master.js +1 -1
- package/dist-server/service/asset/asset-master.js.map +1 -1
- package/dist-server/service/asset/asset-query.d.ts +9 -1
- package/dist-server/service/asset/asset-query.js +28 -6
- package/dist-server/service/asset/asset-query.js.map +1 -1
- package/dist-server/service/asset/physical-asset-class.js +1 -1
- package/dist-server/service/asset/physical-asset-class.js.map +1 -1
- package/dist-server/service/asset/physical-asset.d.ts +17 -4
- package/dist-server/service/asset/physical-asset.js +6 -6
- package/dist-server/service/asset/physical-asset.js.map +1 -1
- package/dist-server/service/equipment/equipment-class.js +1 -1
- package/dist-server/service/equipment/equipment-class.js.map +1 -1
- package/dist-server/service/equipment/equipment-master-resolver.d.ts +9 -0
- package/dist-server/service/equipment/equipment-master-resolver.js +58 -0
- package/dist-server/service/equipment/equipment-master-resolver.js.map +1 -1
- package/dist-server/service/equipment/equipment-master.d.ts +3 -2
- package/dist-server/service/equipment/equipment-master.js +21 -4
- package/dist-server/service/equipment/equipment-master.js.map +1 -1
- package/dist-server/service/equipment/equipment-query.d.ts +9 -1
- package/dist-server/service/equipment/equipment-query.js +28 -6
- package/dist-server/service/equipment/equipment-query.js.map +1 -1
- package/dist-server/service/equipment/equipment.d.ts +27 -1
- package/dist-server/service/equipment/equipment.js +8 -3
- package/dist-server/service/equipment/equipment.js.map +1 -1
- package/dist-server/service/equipment/state-period-rules.d.ts +19 -0
- package/dist-server/service/equipment/state-period-rules.js +35 -1
- package/dist-server/service/equipment/state-period-rules.js.map +1 -1
- package/dist-server/service/index.d.ts +12 -2
- package/dist-server/service/index.js +21 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/location/location-master-resolver.d.ts +29 -0
- package/dist-server/service/location/location-master-resolver.js +228 -1
- package/dist-server/service/location/location-master-resolver.js.map +1 -1
- package/dist-server/service/location/location-query.d.ts +9 -1
- package/dist-server/service/location/location-query.js +24 -9
- package/dist-server/service/location/location-query.js.map +1 -1
- package/dist-server/service/location/operational-location.d.ts +37 -0
- package/dist-server/service/location/operational-location.js +22 -2
- package/dist-server/service/location/operational-location.js.map +1 -1
- package/dist-server/service/master-axis.d.ts +1 -1
- package/dist-server/service/master-axis.js +16 -1
- package/dist-server/service/master-axis.js.map +1 -1
- package/dist-server/service/master-conformance-resolver.d.ts +34 -0
- package/dist-server/service/master-conformance-resolver.js +143 -0
- package/dist-server/service/master-conformance-resolver.js.map +1 -0
- package/dist-server/service/master-conformance.d.ts +80 -0
- package/dist-server/service/master-conformance.js +266 -0
- package/dist-server/service/master-conformance.js.map +1 -0
- package/dist-server/service/master-page-query.d.ts +33 -0
- package/dist-server/service/master-page-query.js +55 -0
- package/dist-server/service/master-page-query.js.map +1 -0
- package/dist-server/service/master-page.d.ts +85 -0
- package/dist-server/service/master-page.js +104 -0
- package/dist-server/service/master-page.js.map +1 -0
- package/dist-server/service/material/material-class.js +1 -1
- package/dist-server/service/material/material-class.js.map +1 -1
- package/dist-server/service/material/material-definition.js +2 -2
- package/dist-server/service/material/material-definition.js.map +1 -1
- package/dist-server/service/material/material-master-resolver.d.ts +62 -0
- package/dist-server/service/material/material-master-resolver.js +213 -0
- package/dist-server/service/material/material-master-resolver.js.map +1 -1
- package/dist-server/service/material/material-query.d.ts +9 -1
- package/dist-server/service/material/material-query.js +28 -6
- package/dist-server/service/material/material-query.js.map +1 -1
- package/dist-server/service/material/recipe-index.d.ts +23 -0
- package/dist-server/service/material/recipe-index.js +44 -0
- package/dist-server/service/material/recipe-index.js.map +1 -0
- package/dist-server/service/operation/index.d.ts +2 -1
- package/dist-server/service/operation/index.js +2 -1
- package/dist-server/service/operation/index.js.map +1 -1
- package/dist-server/service/operation/operation-definition.d.ts +1 -1
- package/dist-server/service/operation/operation-definition.js +4 -4
- package/dist-server/service/operation/operation-definition.js.map +1 -1
- package/dist-server/service/operation/operation-master-resolver.d.ts +24 -0
- package/dist-server/service/operation/operation-master-resolver.js +107 -0
- package/dist-server/service/operation/operation-master-resolver.js.map +1 -1
- package/dist-server/service/operation/operation-query.d.ts +10 -2
- package/dist-server/service/operation/operation-query.js +25 -10
- package/dist-server/service/operation/operation-query.js.map +1 -1
- package/dist-server/service/operation/operation-readiness-resolver.d.ts +39 -0
- package/dist-server/service/operation/operation-readiness-resolver.js +111 -0
- package/dist-server/service/operation/operation-readiness-resolver.js.map +1 -0
- package/dist-server/service/operation/operation-readiness.d.ts +34 -0
- package/dist-server/service/operation/operation-readiness.js +133 -0
- package/dist-server/service/operation/operation-readiness.js.map +1 -0
- package/dist-server/service/personnel/person.js +3 -3
- package/dist-server/service/personnel/person.js.map +1 -1
- package/dist-server/service/personnel/personnel-class-readiness.d.ts +22 -0
- package/dist-server/service/personnel/personnel-class-readiness.js +98 -0
- package/dist-server/service/personnel/personnel-class-readiness.js.map +1 -0
- package/dist-server/service/personnel/personnel-class.d.ts +1 -1
- package/dist-server/service/personnel/personnel-class.js +2 -2
- package/dist-server/service/personnel/personnel-class.js.map +1 -1
- package/dist-server/service/personnel/personnel-master-resolver.d.ts +9 -0
- package/dist-server/service/personnel/personnel-master-resolver.js +43 -0
- package/dist-server/service/personnel/personnel-master-resolver.js.map +1 -1
- package/dist-server/service/personnel/personnel-master.js +1 -1
- package/dist-server/service/personnel/personnel-master.js.map +1 -1
- package/dist-server/service/personnel/personnel-query.d.ts +9 -1
- package/dist-server/service/personnel/personnel-query.js +28 -6
- package/dist-server/service/personnel/personnel-query.js.map +1 -1
- package/dist-server/service/personnel/qualification.d.ts +7 -0
- package/dist-server/service/personnel/qualification.js +25 -0
- package/dist-server/service/personnel/qualification.js.map +1 -1
- package/dist-server/service/recipe/index.d.ts +9 -0
- package/dist-server/service/recipe/index.js +14 -0
- package/dist-server/service/recipe/index.js.map +1 -0
- package/dist-server/service/recipe/recipe-definition.d.ts +71 -0
- package/dist-server/service/recipe/recipe-definition.js +130 -0
- package/dist-server/service/recipe/recipe-definition.js.map +1 -0
- package/dist-server/service/recipe/recipe-master-resolver.d.ts +28 -0
- package/dist-server/service/recipe/recipe-master-resolver.js +124 -0
- package/dist-server/service/recipe/recipe-master-resolver.js.map +1 -0
- package/dist-server/service/recipe/recipe-master.d.ts +37 -0
- package/dist-server/service/recipe/recipe-master.js +47 -0
- package/dist-server/service/recipe/recipe-master.js.map +1 -0
- package/dist-server/service/recipe/recipe-query.d.ts +21 -0
- package/dist-server/service/recipe/recipe-query.js +75 -0
- package/dist-server/service/recipe/recipe-query.js.map +1 -0
- package/dist-server/service/recipe/route-definition.d.ts +51 -0
- package/dist-server/service/recipe/route-definition.js +113 -0
- package/dist-server/service/recipe/route-definition.js.map +1 -0
- package/dist-server/service/resource/class-lineage.d.ts +46 -0
- package/dist-server/service/resource/class-lineage.js +80 -0
- package/dist-server/service/resource/class-lineage.js.map +1 -0
- package/dist-server/service/resource/class-members.d.ts +36 -0
- package/dist-server/service/resource/class-members.js +50 -0
- package/dist-server/service/resource/class-members.js.map +1 -0
- package/dist-server/service/resource/referrers.d.ts +54 -0
- package/dist-server/service/resource/referrers.js +99 -0
- package/dist-server/service/resource/referrers.js.map +1 -0
- package/dist-server/service/status/master-status.d.ts +0 -7
- package/dist-server/service/status/master-status.js +5 -6
- package/dist-server/service/status/master-status.js.map +1 -1
- package/dist-server/service/test-spec/criteria-check.d.ts +53 -0
- package/dist-server/service/test-spec/criteria-check.js +142 -0
- package/dist-server/service/test-spec/criteria-check.js.map +1 -0
- package/dist-server/service/test-spec/criteria-rows.d.ts +89 -0
- package/dist-server/service/test-spec/criteria-rows.js +139 -0
- package/dist-server/service/test-spec/criteria-rows.js.map +1 -0
- package/dist-server/service/test-spec/test-spec-master-resolver.d.ts +33 -7
- package/dist-server/service/test-spec/test-spec-master-resolver.js +137 -2
- package/dist-server/service/test-spec/test-spec-master-resolver.js.map +1 -1
- package/dist-server/service/test-spec/test-spec-query.d.ts +9 -1
- package/dist-server/service/test-spec/test-spec-query.js +23 -9
- package/dist-server/service/test-spec/test-spec-query.js.map +1 -1
- package/dist-server/service/test-spec/test-specification.js +1 -1
- package/dist-server/service/test-spec/test-specification.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/helps/ops-master/equipment-class.ko.md +11 -0
- package/helps/ops-master/equipment-class.md +11 -0
- package/helps/ops-master/equipment.ko.md +11 -0
- package/helps/ops-master/equipment.md +11 -0
- package/helps/ops-master/material-class.ko.md +11 -0
- package/helps/ops-master/material-class.md +11 -0
- package/helps/ops-master/material-definition.ko.md +11 -0
- package/helps/ops-master/material-definition.md +11 -0
- package/helps/ops-master/operation-definition.ko.md +11 -0
- package/helps/ops-master/operation-definition.md +11 -0
- package/helps/ops-master/operational-location.ko.md +11 -0
- package/helps/ops-master/operational-location.md +11 -0
- package/helps/ops-master/person.ko.md +11 -0
- package/helps/ops-master/person.md +11 -0
- package/helps/ops-master/personnel-class.ko.md +11 -0
- package/helps/ops-master/personnel-class.md +11 -0
- package/helps/ops-master/physical-asset-class.ko.md +11 -0
- package/helps/ops-master/physical-asset-class.md +11 -0
- package/helps/ops-master/physical-asset.ko.md +11 -0
- package/helps/ops-master/physical-asset.md +11 -0
- package/helps/ops-master/recipe-definition.ko.md +11 -0
- package/helps/ops-master/recipe-definition.md +11 -0
- package/helps/ops-master/route-definition.ko.md +11 -0
- package/helps/ops-master/route-definition.md +11 -0
- package/helps/ops-master/stop-reason.ko.md +11 -0
- package/helps/ops-master/stop-reason.md +11 -0
- package/helps/ops-master/test-specification.ko.md +11 -0
- package/helps/ops-master/test-specification.md +11 -0
- package/package.json +5 -5
- package/things-factory.config.js +22 -2
- package/translations/en.json +89 -6
- package/translations/ja.json +96 -1
- package/translations/ko.json +96 -13
- package/translations/ms.json +96 -1
- package/translations/zh.json +96 -1
- package/dist-client/pages/master-list.d.ts +0 -68
- package/dist-client/pages/master-list.js +0 -267
- package/dist-client/pages/master-list.js.map +0 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 검사 기준이 **판정에 쓸 수 있는 모양인가** — 순수 판단.
|
|
4
|
+
*
|
|
5
|
+
* ── 왜 저장하기 전에 보나 ──────────────────────────────────────────────────
|
|
6
|
+
* 기준을 저장하는 자리와 그 기준으로 판정하는 자리가 다르다. 판정하는 쪽(`measurement-judge`)은
|
|
7
|
+
* **모르는 것을 통과로 읽지 않도록** 만들어져 있어서, 망가진 기준이 들어와도 오류를 내지 않고 조용히
|
|
8
|
+
* 「판정하지 않음」이 된다. 그 조용함이 여기서는 위험하다 — 기준을 적은 사람은 적었다고 믿고,
|
|
9
|
+
* 검사원은 통과했다고 읽는다.
|
|
10
|
+
*
|
|
11
|
+
* 세 가지가 특히 조용하다.
|
|
12
|
+
*
|
|
13
|
+
* 한계가 숫자가 아니다 그리드 칸에서 온 `"39.7"` 은 글자다. `값 < "39.7"` 은 견주기가 되긴 하지만
|
|
14
|
+
* `"약 40"` 같은 것이 들어오면 어느 쪽 비교도 참이 아니어서 **모든 값이
|
|
15
|
+
* 한계 안**으로 읽힌다. 그 기준은 아무것도 막지 않는다.
|
|
16
|
+
* 한계에 단위가 없다 관측이 단위를 말해도 견줄 단위가 없으면 단위 검사를 건너뛴다. 화씨 관측이
|
|
17
|
+
* 섭씨 한계와 견주어지고 아무도 모른다.
|
|
18
|
+
* 기준 이름이 비었다 잰 값은 이름으로 기준을 찾는다. 이름이 없으면 영원히 「안 쟀음」이 된다.
|
|
19
|
+
*
|
|
20
|
+
* ── 막지 않는 것 ───────────────────────────────────────────────────────────
|
|
21
|
+
* **숫자 한계가 없는 기준은 정상이다.** 「표면에 흠이 없을 것」처럼 사람이 보고 판단하는 기준이 있고,
|
|
22
|
+
* 그때는 `expression` 만 있다. 그것까지 막으면 현장의 사실을 담을 수 없다.
|
|
23
|
+
*
|
|
24
|
+
* 다만 `limit` 을 **적다 만 것**과 애초에 안 적은 것은 다르다. 앞은 막고 뒤는 통과시킨다.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.judgeCriteria = judgeCriteria;
|
|
28
|
+
exports.criteriaRejected = criteriaRejected;
|
|
29
|
+
exports.readCriteria = readCriteria;
|
|
30
|
+
/** 이름을 붙일 자리 — 오류 문구가 몇 번째 기준인지 말해야 고칠 수 있다. */
|
|
31
|
+
function nameOf(criterion, index) {
|
|
32
|
+
const id = typeof criterion.id === 'string' ? criterion.id.trim() : '';
|
|
33
|
+
return id ? `"${id}"` : `${index + 1}번째 기준`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 한계값 하나를 본다.
|
|
37
|
+
*
|
|
38
|
+
* `undefined` 와 `null` 은 「없다」이고 정상이다. 그 밖의 것은 **숫자여야 한다** — 글자를 그대로 두면
|
|
39
|
+
* 판정이 조용히 무너진다.
|
|
40
|
+
*/
|
|
41
|
+
function limitNumberProblem(where, field, value) {
|
|
42
|
+
if (value === undefined || value === null || value === '') {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === 'number') {
|
|
46
|
+
return Number.isFinite(value)
|
|
47
|
+
? undefined
|
|
48
|
+
: `${where}의 ${field} 이(가) 숫자가 아닙니다. 견줄 수 없는 값입니다`;
|
|
49
|
+
}
|
|
50
|
+
return (`${where}의 ${field} 에 "${String(value)}" 이(가) 들어 있습니다 — 숫자여야 합니다. ` +
|
|
51
|
+
'글자로 두면 견주기가 늘 참이 되어 모든 값이 한계 안으로 읽힙니다');
|
|
52
|
+
}
|
|
53
|
+
function judgeCriteria(criteria) {
|
|
54
|
+
const problems = [];
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
criteria.forEach((criterion, index) => {
|
|
57
|
+
const where = nameOf(criterion, index);
|
|
58
|
+
const id = typeof criterion.id === 'string' ? criterion.id.trim() : '';
|
|
59
|
+
if (!id) {
|
|
60
|
+
problems.push(`${where}에 이름이 없습니다. 잰 값은 이름으로 기준을 찾으므로, ` +
|
|
61
|
+
'이름이 없으면 무엇을 재도 「안 쟀음」으로 남습니다');
|
|
62
|
+
}
|
|
63
|
+
else if (seen.has(id)) {
|
|
64
|
+
problems.push(`기준 이름 "${id}" 이(가) 두 번 나옵니다. 잰 값 하나가 어느 쪽 것인지 정할 수 없습니다`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
seen.add(id);
|
|
68
|
+
}
|
|
69
|
+
const limit = criterion.limit;
|
|
70
|
+
if (limit === undefined || limit === null) {
|
|
71
|
+
/* 숫자 한계 없이 사람이 보고 판단하는 기준이다. 정상이다. */
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (typeof limit !== 'object' || Array.isArray(limit)) {
|
|
75
|
+
problems.push(`${where}의 한계가 { minimum, maximum, uom } 모양이 아닙니다`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const minimumProblem = limitNumberProblem(where, 'minimum', limit.minimum);
|
|
79
|
+
const maximumProblem = limitNumberProblem(where, 'maximum', limit.maximum);
|
|
80
|
+
if (minimumProblem)
|
|
81
|
+
problems.push(minimumProblem);
|
|
82
|
+
if (maximumProblem)
|
|
83
|
+
problems.push(maximumProblem);
|
|
84
|
+
const hasMinimum = typeof limit.minimum === 'number' && Number.isFinite(limit.minimum);
|
|
85
|
+
const hasMaximum = typeof limit.maximum === 'number' && Number.isFinite(limit.maximum);
|
|
86
|
+
if (!hasMinimum && !hasMaximum && !minimumProblem && !maximumProblem) {
|
|
87
|
+
/*
|
|
88
|
+
* `limit` 을 적기 시작했는데 상한도 하한도 없다. 안 적은 것과 다르다 — 적으려다 만 것이고,
|
|
89
|
+
* 그대로 두면 「한계를 정했다」고 믿은 채 아무 값이나 지나간다.
|
|
90
|
+
*/
|
|
91
|
+
problems.push(`${where}에 한계가 열려 있으나 상한도 하한도 없습니다. ` +
|
|
92
|
+
'값을 채우거나, 사람이 보고 판단하는 기준이면 한계를 비우십시오');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (hasMinimum && hasMaximum && limit.minimum > limit.maximum) {
|
|
96
|
+
problems.push(`${where}의 하한 ${limit.minimum} 이(가) 상한 ${limit.maximum} 보다 큽니다. ` +
|
|
97
|
+
'어떤 값도 이 한계 안에 들어올 수 없습니다');
|
|
98
|
+
}
|
|
99
|
+
const uom = limit.uom;
|
|
100
|
+
if (uom === undefined || uom === null || String(uom).trim() === '') {
|
|
101
|
+
/*
|
|
102
|
+
* **단위 없는 한계는 막는다.** 판정하는 쪽은 한계에 단위가 없으면 단위 검사를 건너뛴다 —
|
|
103
|
+
* 화씨로 잰 값이 섭씨 한계와 견주어지고 아무도 모른다. 여기가 그것을 막을 수 있는 유일한 자리다.
|
|
104
|
+
*/
|
|
105
|
+
problems.push(`${where}에 단위가 없습니다. 단위가 없으면 다른 단위로 잰 값이 그대로 견주어져 ` +
|
|
106
|
+
'틀린 판정이 오류 보고 없이 나옵니다');
|
|
107
|
+
}
|
|
108
|
+
else if (typeof uom !== 'string') {
|
|
109
|
+
problems.push(`${where}의 단위가 글자가 아닙니다`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return { ok: problems.length === 0, problems };
|
|
113
|
+
}
|
|
114
|
+
function criteriaRejected(verdict) {
|
|
115
|
+
return !verdict.ok;
|
|
116
|
+
}
|
|
117
|
+
function readCriteria(value) {
|
|
118
|
+
if (value === undefined || value === null || value === '') {
|
|
119
|
+
return { read: 'ok', criteria: [] };
|
|
120
|
+
}
|
|
121
|
+
let parsed = value;
|
|
122
|
+
if (typeof value === 'string') {
|
|
123
|
+
try {
|
|
124
|
+
parsed = JSON.parse(value);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return {
|
|
128
|
+
read: 'failed',
|
|
129
|
+
detail: `기준을 읽지 못했습니다 — JSON 이 아닙니다: ${error.message}`
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!Array.isArray(parsed)) {
|
|
134
|
+
return { read: 'failed', detail: '기준은 목록이어야 합니다. 기준 하나여도 [ ] 로 감쌉니다' };
|
|
135
|
+
}
|
|
136
|
+
const notObjects = parsed.filter(one => typeof one !== 'object' || one === null || Array.isArray(one));
|
|
137
|
+
if (notObjects.length) {
|
|
138
|
+
return { read: 'failed', detail: '기준 목록 안에 { } 모양이 아닌 것이 있습니다' };
|
|
139
|
+
}
|
|
140
|
+
return { read: 'ok', criteria: parsed };
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=criteria-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"criteria-check.js","sourceRoot":"","sources":["../../../server/service/test-spec/criteria-check.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 검사 기준이 **판정에 쓸 수 있는 모양인가** — 순수 판단.\n *\n * ── 왜 저장하기 전에 보나 ──────────────────────────────────────────────────\n * 기준을 저장하는 자리와 그 기준으로 판정하는 자리가 다르다. 판정하는 쪽(`measurement-judge`)은\n * **모르는 것을 통과로 읽지 않도록** 만들어져 있어서, 망가진 기준이 들어와도 오류를 내지 않고 조용히\n * 「판정하지 않음」이 된다. 그 조용함이 여기서는 위험하다 — 기준을 적은 사람은 적었다고 믿고,\n * 검사원은 통과했다고 읽는다.\n *\n * 세 가지가 특히 조용하다.\n *\n * 한계가 숫자가 아니다 그리드 칸에서 온 `\"39.7\"` 은 글자다. `값 < \"39.7\"` 은 견주기가 되긴 하지만\n * `\"약 40\"` 같은 것이 들어오면 어느 쪽 비교도 참이 아니어서 **모든 값이\n * 한계 안**으로 읽힌다. 그 기준은 아무것도 막지 않는다.\n * 한계에 단위가 없다 관측이 단위를 말해도 견줄 단위가 없으면 단위 검사를 건너뛴다. 화씨 관측이\n * 섭씨 한계와 견주어지고 아무도 모른다.\n * 기준 이름이 비었다 잰 값은 이름으로 기준을 찾는다. 이름이 없으면 영원히 「안 쟀음」이 된다.\n *\n * ── 막지 않는 것 ───────────────────────────────────────────────────────────\n * **숫자 한계가 없는 기준은 정상이다.** 「표면에 흠이 없을 것」처럼 사람이 보고 판단하는 기준이 있고,\n * 그때는 `expression` 만 있다. 그것까지 막으면 현장의 사실을 담을 수 없다.\n *\n * 다만 `limit` 을 **적다 만 것**과 애초에 안 적은 것은 다르다. 앞은 막고 뒤는 통과시킨다.\n */\n\n/** 저장하려는 기준 하나 — 무엇이 들어올지 모르므로 열어 둔다. */\nexport type CriterionInput = Record<string, unknown>\n\nexport interface CriteriaVerdict {\n ok: boolean\n /** 무엇이 왜 막혔는지. 한 줄에 하나씩, 고칠 자리를 가리킨다. */\n problems: string[]\n}\n\n/** 이름을 붙일 자리 — 오류 문구가 몇 번째 기준인지 말해야 고칠 수 있다. */\nfunction nameOf(criterion: CriterionInput, index: number): string {\n const id = typeof criterion.id === 'string' ? criterion.id.trim() : ''\n\n return id ? `\"${id}\"` : `${index + 1}번째 기준`\n}\n\n/**\n * 한계값 하나를 본다.\n *\n * `undefined` 와 `null` 은 「없다」이고 정상이다. 그 밖의 것은 **숫자여야 한다** — 글자를 그대로 두면\n * 판정이 조용히 무너진다.\n */\nfunction limitNumberProblem(where: string, field: string, value: unknown): string | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined\n }\n\n if (typeof value === 'number') {\n return Number.isFinite(value)\n ? undefined\n : `${where}의 ${field} 이(가) 숫자가 아닙니다. 견줄 수 없는 값입니다`\n }\n\n return (\n `${where}의 ${field} 에 \"${String(value)}\" 이(가) 들어 있습니다 — 숫자여야 합니다. ` +\n '글자로 두면 견주기가 늘 참이 되어 모든 값이 한계 안으로 읽힙니다'\n )\n}\n\nexport function judgeCriteria(criteria: readonly CriterionInput[]): CriteriaVerdict {\n const problems: string[] = []\n const seen = new Set<string>()\n\n criteria.forEach((criterion, index) => {\n const where = nameOf(criterion, index)\n const id = typeof criterion.id === 'string' ? criterion.id.trim() : ''\n\n if (!id) {\n problems.push(\n `${where}에 이름이 없습니다. 잰 값은 이름으로 기준을 찾으므로, ` +\n '이름이 없으면 무엇을 재도 「안 쟀음」으로 남습니다'\n )\n } else if (seen.has(id)) {\n problems.push(`기준 이름 \"${id}\" 이(가) 두 번 나옵니다. 잰 값 하나가 어느 쪽 것인지 정할 수 없습니다`)\n } else {\n seen.add(id)\n }\n\n const limit = criterion.limit as Record<string, unknown> | undefined\n\n if (limit === undefined || limit === null) {\n /* 숫자 한계 없이 사람이 보고 판단하는 기준이다. 정상이다. */\n return\n }\n\n if (typeof limit !== 'object' || Array.isArray(limit)) {\n problems.push(`${where}의 한계가 { minimum, maximum, uom } 모양이 아닙니다`)\n return\n }\n\n const minimumProblem = limitNumberProblem(where, 'minimum', limit.minimum)\n const maximumProblem = limitNumberProblem(where, 'maximum', limit.maximum)\n\n if (minimumProblem) problems.push(minimumProblem)\n if (maximumProblem) problems.push(maximumProblem)\n\n const hasMinimum = typeof limit.minimum === 'number' && Number.isFinite(limit.minimum)\n const hasMaximum = typeof limit.maximum === 'number' && Number.isFinite(limit.maximum)\n\n if (!hasMinimum && !hasMaximum && !minimumProblem && !maximumProblem) {\n /*\n * `limit` 을 적기 시작했는데 상한도 하한도 없다. 안 적은 것과 다르다 — 적으려다 만 것이고,\n * 그대로 두면 「한계를 정했다」고 믿은 채 아무 값이나 지나간다.\n */\n problems.push(\n `${where}에 한계가 열려 있으나 상한도 하한도 없습니다. ` +\n '값을 채우거나, 사람이 보고 판단하는 기준이면 한계를 비우십시오'\n )\n return\n }\n\n if (hasMinimum && hasMaximum && (limit.minimum as number) > (limit.maximum as number)) {\n problems.push(\n `${where}의 하한 ${limit.minimum} 이(가) 상한 ${limit.maximum} 보다 큽니다. ` +\n '어떤 값도 이 한계 안에 들어올 수 없습니다'\n )\n }\n\n const uom = limit.uom\n\n if (uom === undefined || uom === null || String(uom).trim() === '') {\n /*\n * **단위 없는 한계는 막는다.** 판정하는 쪽은 한계에 단위가 없으면 단위 검사를 건너뛴다 —\n * 화씨로 잰 값이 섭씨 한계와 견주어지고 아무도 모른다. 여기가 그것을 막을 수 있는 유일한 자리다.\n */\n problems.push(\n `${where}에 단위가 없습니다. 단위가 없으면 다른 단위로 잰 값이 그대로 견주어져 ` +\n '틀린 판정이 오류 보고 없이 나옵니다'\n )\n } else if (typeof uom !== 'string') {\n problems.push(`${where}의 단위가 글자가 아닙니다`)\n }\n })\n\n return { ok: problems.length === 0, problems }\n}\n\nexport function criteriaRejected(verdict: CriteriaVerdict): boolean {\n return !verdict.ok\n}\n\n/**\n * 그리드가 보낸 값을 기준 목록으로 읽는다.\n *\n * 화면은 JSON 글자로 보내고 저장 칸은 배열이다. **읽지 못한 것을 빈 목록으로 바꾸지 않는다** —\n * 그러면 사람이 적은 기준이 통째로 사라지고 명세는 기준 없는 명세가 된다.\n */\n/**\n * 읽음 · 못 읽음을 **글자로** 가른다.\n *\n * `ok: boolean` 으로 두면 이 저장소의 설정(`strict: false`)에서 `if (!read.ok)` 가 갈래를 좁히지 못해\n * 못 읽은 쪽의 칸을 꺼낼 수 없다. 글자 표식은 어느 설정에서나 좁혀진다.\n */\nexport type CriteriaRead =\n | { read: 'ok'; criteria: CriterionInput[] }\n | { read: 'failed'; detail: string }\n\nexport function readCriteria(value: unknown): CriteriaRead {\n if (value === undefined || value === null || value === '') {\n return { read: 'ok', criteria: [] }\n }\n\n let parsed: unknown = value\n\n if (typeof value === 'string') {\n try {\n parsed = JSON.parse(value)\n } catch (error) {\n return {\n read: 'failed',\n detail: `기준을 읽지 못했습니다 — JSON 이 아닙니다: ${(error as Error).message}`\n }\n }\n }\n\n if (!Array.isArray(parsed)) {\n return { read: 'failed', detail: '기준은 목록이어야 합니다. 기준 하나여도 [ ] 로 감쌉니다' }\n }\n\n const notObjects = parsed.filter(one => typeof one !== 'object' || one === null || Array.isArray(one))\n\n if (notObjects.length) {\n return { read: 'failed', detail: '기준 목록 안에 { } 모양이 아닌 것이 있습니다' }\n }\n\n return { read: 'ok', criteria: parsed as CriterionInput[] }\n}\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { CriterionInput } from './criteria-check';
|
|
2
|
+
/**
|
|
3
|
+
* 기준을 **행으로** 오가게 한다 — 순수 옮김.
|
|
4
|
+
*
|
|
5
|
+
* ── 왜 이 자리가 있나 ──────────────────────────────────────────────────────
|
|
6
|
+
* 기준 하나는 이렇게 생겼다.
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* { id: "length", description: "길이", evaluatedPropertyId: "length",
|
|
10
|
+
* limit: { minimum: 39.7, maximum: 40.3, uom: "MMT" }, expression: "40 ± 0.3 mm" }
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* 화면에 이 JSON 원문을 보이면 품질팀장이 상한을 40.5 로 고치려고 중괄호를 세게 된다. 그것은 기준
|
|
14
|
+
* 정보를 다루는 화면이 아니라 개발자가 JSON 을 손보는 화면이다.
|
|
15
|
+
*
|
|
16
|
+
* 그래서 **여기서 행으로 편다.** 화면은 이름 · 설명 · 하한 · 상한 · 단위 · 표현식이 각자 칸인 표를
|
|
17
|
+
* 그리고, JSON 을 아예 모른다.
|
|
18
|
+
*
|
|
19
|
+
* ── 저장이 표가 되어도 화면은 안 바뀐다 ────────────────────────────────────
|
|
20
|
+
* 지금 기준은 `simple-json` 칸 안에 있다. 그것이 옳은지는 아키텍트 안건에 올라가 있고, 표로 내려가면
|
|
21
|
+
* **이 파일만 바뀐다.** 화면은 계속 행을 받는다.
|
|
22
|
+
*
|
|
23
|
+
* 그 자유를 위해 옮김을 한 곳에 둔다. 화면이 JSON 을 풀면 저장이 바뀌는 날 그 코드가 통째로 버려진다.
|
|
24
|
+
*/
|
|
25
|
+
/** 화면이 받는 한 줄. 한계를 **펴서** 낸다 — 칸 옆에 그대로 적을 값이다. */
|
|
26
|
+
export interface CriterionRow {
|
|
27
|
+
/** 기준 이름 — 잰 값이 이것으로 기준을 찾는다. */
|
|
28
|
+
id: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
/** 어느 속성을 재는가 — 표준 `EvaluatedPropertyID`. */
|
|
31
|
+
evaluatedPropertyId?: string;
|
|
32
|
+
minimum?: number;
|
|
33
|
+
maximum?: number;
|
|
34
|
+
uom?: string;
|
|
35
|
+
/** 그 현장의 표기 그대로. 우리는 읽지 않는다. */
|
|
36
|
+
expression?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 저장된 기준 하나를 행으로 편다.
|
|
40
|
+
*
|
|
41
|
+
* **모르는 칸을 버리지 않는다.** 표준에는 우리 화면이 아직 안 그리는 칸이 있고, 편 것을 다시 담을 때
|
|
42
|
+
* 그 칸이 사라지면 사람이 적은 것이 조용히 없어진다. 그래서 원본을 `keptFields` 에 함께 들고 다닌다.
|
|
43
|
+
*
|
|
44
|
+
* 이름 앞에 `__` 를 붙이지 않는다 — GraphQL 이 그 접두사를 introspection 용으로 예약해 두어서,
|
|
45
|
+
* 필드 이름에 쓰면 **스키마가 만들어지지 않고 서버가 기동에서 죽는다**(2026-09-02에 실제로 그랬다).
|
|
46
|
+
*/
|
|
47
|
+
export declare function toCriterionRow(criterion: CriterionInput): CriterionRow & {
|
|
48
|
+
keptFields: CriterionInput;
|
|
49
|
+
};
|
|
50
|
+
export declare function toCriterionRows(criteria?: CriterionInput[]): (CriterionRow & {
|
|
51
|
+
keptFields: CriterionInput;
|
|
52
|
+
})[];
|
|
53
|
+
/**
|
|
54
|
+
* 행 하나를 저장할 모양으로 담는다.
|
|
55
|
+
*
|
|
56
|
+
* 한계가 셋 다 비면 `limit` 을 **아예 두지 않는다.** 빈 `{}` 를 두면 「한계를 정하려다 만 것」이 되어
|
|
57
|
+
* 검증이 막는다 — 사람이 보고 판단하는 기준(표현식만 있는 것)은 정상이므로 그것을 막으면 안 된다.
|
|
58
|
+
*/
|
|
59
|
+
export declare function fromCriterionRow(row: Partial<CriterionRow> & {
|
|
60
|
+
keptFields?: CriterionInput;
|
|
61
|
+
}): CriterionInput;
|
|
62
|
+
/** 그리드가 보내는 표식. `+` 새 줄 · `M` 고친 줄 · `-` 지운 줄. */
|
|
63
|
+
export type RowFlag = '+' | 'M' | '-';
|
|
64
|
+
export interface CriterionPatch extends Partial<CriterionRow> {
|
|
65
|
+
cuFlag?: string;
|
|
66
|
+
keptFields?: CriterionInput;
|
|
67
|
+
/** 고치거나 지울 때 **어느 줄인가.** 이름을 고칠 수 있으므로 이름으로 찾을 수 없다. */
|
|
68
|
+
rowIndex?: number;
|
|
69
|
+
}
|
|
70
|
+
export type CriteriaApplied = {
|
|
71
|
+
applied: 'ok';
|
|
72
|
+
criteria: CriterionInput[];
|
|
73
|
+
} | {
|
|
74
|
+
applied: 'rejected';
|
|
75
|
+
problems: string[];
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* 지금 저장된 기준에 화면의 변경을 얹는다.
|
|
79
|
+
*
|
|
80
|
+
* ── 왜 통째로 받지 않나 ────────────────────────────────────────────────────
|
|
81
|
+
* 화면이 목록 전체를 보내면, 두 사람이 같은 명세를 열어 각자 한 줄씩 고쳤을 때 **나중에 저장한 쪽이
|
|
82
|
+
* 앞사람의 줄을 지운다.** 바뀐 줄만 받으면 서로 다른 줄을 고친 경우는 둘 다 남는다.
|
|
83
|
+
*
|
|
84
|
+
* ── 지운 줄은 정말 지운다 ──────────────────────────────────────────────────
|
|
85
|
+
* 마스터의 다른 축은 지우지 않고 유효 기간의 끝을 채운다 — 지난 기록이 그 행을 가리키기 때문이다.
|
|
86
|
+
* 기준은 다르다. 기준은 명세 안에 있고, **명세가 유효 기간을 갖는다.** 기준 하나에 또 기간을 두면
|
|
87
|
+
* 「명세는 유효한데 기준은 지났다」는 상태가 생기고, 그때 무엇으로 판정하는지 아무도 모른다.
|
|
88
|
+
*/
|
|
89
|
+
export declare function applyCriterionPatches(current: CriterionInput[], patches: CriterionPatch[]): CriteriaApplied;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toCriterionRow = toCriterionRow;
|
|
4
|
+
exports.toCriterionRows = toCriterionRows;
|
|
5
|
+
exports.fromCriterionRow = fromCriterionRow;
|
|
6
|
+
exports.applyCriterionPatches = applyCriterionPatches;
|
|
7
|
+
const criteria_check_1 = require("./criteria-check");
|
|
8
|
+
function text(value) {
|
|
9
|
+
if (value === undefined || value === null) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const trimmed = String(value).trim();
|
|
13
|
+
return trimmed === '' ? undefined : trimmed;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 저장된 기준 하나를 행으로 편다.
|
|
17
|
+
*
|
|
18
|
+
* **모르는 칸을 버리지 않는다.** 표준에는 우리 화면이 아직 안 그리는 칸이 있고, 편 것을 다시 담을 때
|
|
19
|
+
* 그 칸이 사라지면 사람이 적은 것이 조용히 없어진다. 그래서 원본을 `keptFields` 에 함께 들고 다닌다.
|
|
20
|
+
*
|
|
21
|
+
* 이름 앞에 `__` 를 붙이지 않는다 — GraphQL 이 그 접두사를 introspection 용으로 예약해 두어서,
|
|
22
|
+
* 필드 이름에 쓰면 **스키마가 만들어지지 않고 서버가 기동에서 죽는다**(2026-09-02에 실제로 그랬다).
|
|
23
|
+
*/
|
|
24
|
+
function toCriterionRow(criterion) {
|
|
25
|
+
const { id, description, evaluatedPropertyId, limit, expression, ...rest } = criterion;
|
|
26
|
+
const lim = (limit ?? {});
|
|
27
|
+
return {
|
|
28
|
+
id: String(id ?? ''),
|
|
29
|
+
description: text(description),
|
|
30
|
+
evaluatedPropertyId: text(evaluatedPropertyId),
|
|
31
|
+
minimum: typeof lim.minimum === 'number' ? lim.minimum : undefined,
|
|
32
|
+
maximum: typeof lim.maximum === 'number' ? lim.maximum : undefined,
|
|
33
|
+
uom: text(lim.uom),
|
|
34
|
+
expression: text(expression),
|
|
35
|
+
keptFields: rest
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function toCriterionRows(criteria = []) {
|
|
39
|
+
return criteria.map(toCriterionRow);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 행 하나를 저장할 모양으로 담는다.
|
|
43
|
+
*
|
|
44
|
+
* 한계가 셋 다 비면 `limit` 을 **아예 두지 않는다.** 빈 `{}` 를 두면 「한계를 정하려다 만 것」이 되어
|
|
45
|
+
* 검증이 막는다 — 사람이 보고 판단하는 기준(표현식만 있는 것)은 정상이므로 그것을 막으면 안 된다.
|
|
46
|
+
*/
|
|
47
|
+
function fromCriterionRow(row) {
|
|
48
|
+
const hasLimit = row.minimum !== undefined || row.maximum !== undefined || text(row.uom) !== undefined;
|
|
49
|
+
const out = {
|
|
50
|
+
/* 화면이 모르는 칸을 먼저 깔고 아는 칸으로 덮는다 — 모르는 것을 잃지 않는다. */
|
|
51
|
+
...(row.keptFields ?? {}),
|
|
52
|
+
id: String(row.id ?? '').trim()
|
|
53
|
+
};
|
|
54
|
+
const description = text(row.description);
|
|
55
|
+
const evaluatedPropertyId = text(row.evaluatedPropertyId);
|
|
56
|
+
const expression = text(row.expression);
|
|
57
|
+
if (description !== undefined)
|
|
58
|
+
out.description = description;
|
|
59
|
+
else
|
|
60
|
+
delete out.description;
|
|
61
|
+
if (evaluatedPropertyId !== undefined)
|
|
62
|
+
out.evaluatedPropertyId = evaluatedPropertyId;
|
|
63
|
+
else
|
|
64
|
+
delete out.evaluatedPropertyId;
|
|
65
|
+
if (expression !== undefined)
|
|
66
|
+
out.expression = expression;
|
|
67
|
+
else
|
|
68
|
+
delete out.expression;
|
|
69
|
+
if (hasLimit) {
|
|
70
|
+
const limit = {};
|
|
71
|
+
if (row.minimum !== undefined && row.minimum !== null)
|
|
72
|
+
limit.minimum = row.minimum;
|
|
73
|
+
if (row.maximum !== undefined && row.maximum !== null)
|
|
74
|
+
limit.maximum = row.maximum;
|
|
75
|
+
const uom = text(row.uom);
|
|
76
|
+
if (uom !== undefined)
|
|
77
|
+
limit.uom = uom;
|
|
78
|
+
out.limit = limit;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
delete out.limit;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 지금 저장된 기준에 화면의 변경을 얹는다.
|
|
87
|
+
*
|
|
88
|
+
* ── 왜 통째로 받지 않나 ────────────────────────────────────────────────────
|
|
89
|
+
* 화면이 목록 전체를 보내면, 두 사람이 같은 명세를 열어 각자 한 줄씩 고쳤을 때 **나중에 저장한 쪽이
|
|
90
|
+
* 앞사람의 줄을 지운다.** 바뀐 줄만 받으면 서로 다른 줄을 고친 경우는 둘 다 남는다.
|
|
91
|
+
*
|
|
92
|
+
* ── 지운 줄은 정말 지운다 ──────────────────────────────────────────────────
|
|
93
|
+
* 마스터의 다른 축은 지우지 않고 유효 기간의 끝을 채운다 — 지난 기록이 그 행을 가리키기 때문이다.
|
|
94
|
+
* 기준은 다르다. 기준은 명세 안에 있고, **명세가 유효 기간을 갖는다.** 기준 하나에 또 기간을 두면
|
|
95
|
+
* 「명세는 유효한데 기준은 지났다」는 상태가 생기고, 그때 무엇으로 판정하는지 아무도 모른다.
|
|
96
|
+
*/
|
|
97
|
+
function applyCriterionPatches(current, patches) {
|
|
98
|
+
const rows = [...current];
|
|
99
|
+
const problems = [];
|
|
100
|
+
/* 지우기를 뒤에서부터 한다 — 앞에서 지우면 뒤 줄의 번호가 밀린다. */
|
|
101
|
+
const removals = patches
|
|
102
|
+
.filter(patch => String(patch.cuFlag ?? '').toUpperCase() === '-')
|
|
103
|
+
.map(patch => patch.rowIndex)
|
|
104
|
+
.filter((index) => typeof index === 'number')
|
|
105
|
+
.sort((a, b) => b - a);
|
|
106
|
+
for (const index of removals) {
|
|
107
|
+
if (index < 0 || index >= rows.length) {
|
|
108
|
+
problems.push(`지우려는 줄(${index + 1}번째)이 지금 목록에 없습니다. 화면을 새로 읽고 다시 하십시오`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
rows.splice(index, 1);
|
|
112
|
+
}
|
|
113
|
+
for (const patch of patches) {
|
|
114
|
+
const flag = String(patch.cuFlag ?? '').toUpperCase();
|
|
115
|
+
if (flag === '-') {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (flag === '+') {
|
|
119
|
+
rows.push(fromCriterionRow(patch));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const index = patch.rowIndex;
|
|
123
|
+
if (typeof index !== 'number' || index < 0 || index >= rows.length) {
|
|
124
|
+
problems.push(`고치려는 줄을 찾지 못했습니다. 화면을 새로 읽고 다시 하십시오`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
rows[index] = fromCriterionRow({ ...toCriterionRow(rows[index]), ...patch });
|
|
128
|
+
}
|
|
129
|
+
if (problems.length) {
|
|
130
|
+
return { applied: 'rejected', problems };
|
|
131
|
+
}
|
|
132
|
+
/* 담고 나서 본다 — 담는 과정에서 생긴 어긋남(빈 이름·겹친 이름)까지 여기서 걸린다. */
|
|
133
|
+
const verdict = (0, criteria_check_1.judgeCriteria)(rows);
|
|
134
|
+
if (!verdict.ok) {
|
|
135
|
+
return { applied: 'rejected', problems: verdict.problems };
|
|
136
|
+
}
|
|
137
|
+
return { applied: 'ok', criteria: rows };
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=criteria-rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"criteria-rows.js","sourceRoot":"","sources":["../../../server/service/test-spec/criteria-rows.ts"],"names":[],"mappings":";;AA4DA,wCAcC;AAED,0CAEC;AAQD,4CAsCC;AA4BD,sDAyDC;AAjND,qDAAgD;AAyChD,SAAS,IAAI,CAAC,KAAc;IAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;IAEpC,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAA;AAC7C,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,cAAc,CAAC,SAAyB;IACtD,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,SAAgB,CAAA;IAC7F,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAA;IAEpD,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;QACpB,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC;QAC9B,mBAAmB,EAAE,IAAI,CAAC,mBAAmB,CAAC;QAC9C,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAClE,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAClE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAClB,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC;QAC5B,UAAU,EAAE,IAAI;KACjB,CAAA;AACH,CAAC;AAED,SAAgB,eAAe,CAAC,WAA6B,EAAE;IAC7D,OAAO,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;AACrC,CAAC;AAED;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,GAA4D;IAC3F,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS,CAAA;IAEtG,MAAM,GAAG,GAAmB;QAC1B,kDAAkD;QAClD,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;QACzB,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;KAChC,CAAA;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IACzC,MAAM,mBAAmB,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACzD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAEvC,IAAI,WAAW,KAAK,SAAS;QAAE,GAAG,CAAC,WAAW,GAAG,WAAW,CAAA;;QACvD,OAAQ,GAAW,CAAC,WAAW,CAAA;IAEpC,IAAI,mBAAmB,KAAK,SAAS;QAAE,GAAG,CAAC,mBAAmB,GAAG,mBAAmB,CAAA;;QAC/E,OAAQ,GAAW,CAAC,mBAAmB,CAAA;IAE5C,IAAI,UAAU,KAAK,SAAS;QAAE,GAAG,CAAC,UAAU,GAAG,UAAU,CAAA;;QACpD,OAAQ,GAAW,CAAC,UAAU,CAAA;IAEnC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAA4B,EAAE,CAAA;QAEzC,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;YAAE,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;QAClF,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;YAAE,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;QAElF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEzB,IAAI,GAAG,KAAK,SAAS;YAAE,KAAK,CAAC,GAAG,GAAG,GAAG,CAAA;QAEtC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAA;IACnB,CAAC;SAAM,CAAC;QACN,OAAQ,GAAW,CAAC,KAAK,CAAA;IAC3B,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAgBD;;;;;;;;;;;GAWG;AACH,SAAgB,qBAAqB,CACnC,OAAyB,EACzB,OAAyB;IAEzB,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,CAAA;IACzB,MAAM,QAAQ,GAAa,EAAE,CAAA;IAE7B,2CAA2C;IAC3C,MAAM,QAAQ,GAAG,OAAO;SACrB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC;SACjE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC;SAC5B,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC;SAC7D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAExB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACtC,QAAQ,CAAC,IAAI,CAAC,UAAU,KAAK,GAAG,CAAC,qCAAqC,CAAC,CAAA;YACvE,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IACvB,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;QAErD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,SAAQ;QACV,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAA;YAClC,SAAQ;QACV,CAAC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAA;QAE5B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACnE,QAAQ,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAA;YACpD,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,gBAAgB,CAAC,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAA;IAC9E,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAA;IAC1C,CAAC;IAED,sDAAsD;IACtD,MAAM,OAAO,GAAG,IAAA,8BAAa,EAAC,IAAI,CAAC,CAAA;IAEnC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAA;IAC5D,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AAC1C,CAAC","sourcesContent":["import { judgeCriteria } from './criteria-check'\nimport type { CriterionInput } from './criteria-check'\n\n/**\n * 기준을 **행으로** 오가게 한다 — 순수 옮김.\n *\n * ── 왜 이 자리가 있나 ──────────────────────────────────────────────────────\n * 기준 하나는 이렇게 생겼다.\n *\n * ```\n * { id: \"length\", description: \"길이\", evaluatedPropertyId: \"length\",\n * limit: { minimum: 39.7, maximum: 40.3, uom: \"MMT\" }, expression: \"40 ± 0.3 mm\" }\n * ```\n *\n * 화면에 이 JSON 원문을 보이면 품질팀장이 상한을 40.5 로 고치려고 중괄호를 세게 된다. 그것은 기준\n * 정보를 다루는 화면이 아니라 개발자가 JSON 을 손보는 화면이다.\n *\n * 그래서 **여기서 행으로 편다.** 화면은 이름 · 설명 · 하한 · 상한 · 단위 · 표현식이 각자 칸인 표를\n * 그리고, JSON 을 아예 모른다.\n *\n * ── 저장이 표가 되어도 화면은 안 바뀐다 ────────────────────────────────────\n * 지금 기준은 `simple-json` 칸 안에 있다. 그것이 옳은지는 아키텍트 안건에 올라가 있고, 표로 내려가면\n * **이 파일만 바뀐다.** 화면은 계속 행을 받는다.\n *\n * 그 자유를 위해 옮김을 한 곳에 둔다. 화면이 JSON 을 풀면 저장이 바뀌는 날 그 코드가 통째로 버려진다.\n */\n\n/** 화면이 받는 한 줄. 한계를 **펴서** 낸다 — 칸 옆에 그대로 적을 값이다. */\nexport interface CriterionRow {\n /** 기준 이름 — 잰 값이 이것으로 기준을 찾는다. */\n id: string\n description?: string\n /** 어느 속성을 재는가 — 표준 `EvaluatedPropertyID`. */\n evaluatedPropertyId?: string\n minimum?: number\n maximum?: number\n uom?: string\n /** 그 현장의 표기 그대로. 우리는 읽지 않는다. */\n expression?: string\n}\n\nfunction text(value: unknown): string | undefined {\n if (value === undefined || value === null) {\n return undefined\n }\n\n const trimmed = String(value).trim()\n\n return trimmed === '' ? undefined : trimmed\n}\n\n/**\n * 저장된 기준 하나를 행으로 편다.\n *\n * **모르는 칸을 버리지 않는다.** 표준에는 우리 화면이 아직 안 그리는 칸이 있고, 편 것을 다시 담을 때\n * 그 칸이 사라지면 사람이 적은 것이 조용히 없어진다. 그래서 원본을 `keptFields` 에 함께 들고 다닌다.\n *\n * 이름 앞에 `__` 를 붙이지 않는다 — GraphQL 이 그 접두사를 introspection 용으로 예약해 두어서,\n * 필드 이름에 쓰면 **스키마가 만들어지지 않고 서버가 기동에서 죽는다**(2026-09-02에 실제로 그랬다).\n */\nexport function toCriterionRow(criterion: CriterionInput): CriterionRow & { keptFields: CriterionInput } {\n const { id, description, evaluatedPropertyId, limit, expression, ...rest } = criterion as any\n const lim = (limit ?? {}) as Record<string, unknown>\n\n return {\n id: String(id ?? ''),\n description: text(description),\n evaluatedPropertyId: text(evaluatedPropertyId),\n minimum: typeof lim.minimum === 'number' ? lim.minimum : undefined,\n maximum: typeof lim.maximum === 'number' ? lim.maximum : undefined,\n uom: text(lim.uom),\n expression: text(expression),\n keptFields: rest\n }\n}\n\nexport function toCriterionRows(criteria: CriterionInput[] = []): (CriterionRow & { keptFields: CriterionInput })[] {\n return criteria.map(toCriterionRow)\n}\n\n/**\n * 행 하나를 저장할 모양으로 담는다.\n *\n * 한계가 셋 다 비면 `limit` 을 **아예 두지 않는다.** 빈 `{}` 를 두면 「한계를 정하려다 만 것」이 되어\n * 검증이 막는다 — 사람이 보고 판단하는 기준(표현식만 있는 것)은 정상이므로 그것을 막으면 안 된다.\n */\nexport function fromCriterionRow(row: Partial<CriterionRow> & { keptFields?: CriterionInput }): CriterionInput {\n const hasLimit = row.minimum !== undefined || row.maximum !== undefined || text(row.uom) !== undefined\n\n const out: CriterionInput = {\n /* 화면이 모르는 칸을 먼저 깔고 아는 칸으로 덮는다 — 모르는 것을 잃지 않는다. */\n ...(row.keptFields ?? {}),\n id: String(row.id ?? '').trim()\n }\n\n const description = text(row.description)\n const evaluatedPropertyId = text(row.evaluatedPropertyId)\n const expression = text(row.expression)\n\n if (description !== undefined) out.description = description\n else delete (out as any).description\n\n if (evaluatedPropertyId !== undefined) out.evaluatedPropertyId = evaluatedPropertyId\n else delete (out as any).evaluatedPropertyId\n\n if (expression !== undefined) out.expression = expression\n else delete (out as any).expression\n\n if (hasLimit) {\n const limit: Record<string, unknown> = {}\n\n if (row.minimum !== undefined && row.minimum !== null) limit.minimum = row.minimum\n if (row.maximum !== undefined && row.maximum !== null) limit.maximum = row.maximum\n\n const uom = text(row.uom)\n\n if (uom !== undefined) limit.uom = uom\n\n out.limit = limit\n } else {\n delete (out as any).limit\n }\n\n return out\n}\n\n/** 그리드가 보내는 표식. `+` 새 줄 · `M` 고친 줄 · `-` 지운 줄. */\nexport type RowFlag = '+' | 'M' | '-'\n\nexport interface CriterionPatch extends Partial<CriterionRow> {\n cuFlag?: string\n keptFields?: CriterionInput\n /** 고치거나 지울 때 **어느 줄인가.** 이름을 고칠 수 있으므로 이름으로 찾을 수 없다. */\n rowIndex?: number\n}\n\nexport type CriteriaApplied =\n | { applied: 'ok'; criteria: CriterionInput[] }\n | { applied: 'rejected'; problems: string[] }\n\n/**\n * 지금 저장된 기준에 화면의 변경을 얹는다.\n *\n * ── 왜 통째로 받지 않나 ────────────────────────────────────────────────────\n * 화면이 목록 전체를 보내면, 두 사람이 같은 명세를 열어 각자 한 줄씩 고쳤을 때 **나중에 저장한 쪽이\n * 앞사람의 줄을 지운다.** 바뀐 줄만 받으면 서로 다른 줄을 고친 경우는 둘 다 남는다.\n *\n * ── 지운 줄은 정말 지운다 ──────────────────────────────────────────────────\n * 마스터의 다른 축은 지우지 않고 유효 기간의 끝을 채운다 — 지난 기록이 그 행을 가리키기 때문이다.\n * 기준은 다르다. 기준은 명세 안에 있고, **명세가 유효 기간을 갖는다.** 기준 하나에 또 기간을 두면\n * 「명세는 유효한데 기준은 지났다」는 상태가 생기고, 그때 무엇으로 판정하는지 아무도 모른다.\n */\nexport function applyCriterionPatches(\n current: CriterionInput[],\n patches: CriterionPatch[]\n): CriteriaApplied {\n const rows = [...current]\n const problems: string[] = []\n\n /* 지우기를 뒤에서부터 한다 — 앞에서 지우면 뒤 줄의 번호가 밀린다. */\n const removals = patches\n .filter(patch => String(patch.cuFlag ?? '').toUpperCase() === '-')\n .map(patch => patch.rowIndex)\n .filter((index): index is number => typeof index === 'number')\n .sort((a, b) => b - a)\n\n for (const index of removals) {\n if (index < 0 || index >= rows.length) {\n problems.push(`지우려는 줄(${index + 1}번째)이 지금 목록에 없습니다. 화면을 새로 읽고 다시 하십시오`)\n continue\n }\n\n rows.splice(index, 1)\n }\n\n for (const patch of patches) {\n const flag = String(patch.cuFlag ?? '').toUpperCase()\n\n if (flag === '-') {\n continue\n }\n\n if (flag === '+') {\n rows.push(fromCriterionRow(patch))\n continue\n }\n\n const index = patch.rowIndex\n\n if (typeof index !== 'number' || index < 0 || index >= rows.length) {\n problems.push(`고치려는 줄을 찾지 못했습니다. 화면을 새로 읽고 다시 하십시오`)\n continue\n }\n\n rows[index] = fromCriterionRow({ ...toCriterionRow(rows[index]), ...patch })\n }\n\n if (problems.length) {\n return { applied: 'rejected', problems }\n }\n\n /* 담고 나서 본다 — 담는 과정에서 생긴 어긋남(빈 이름·겹친 이름)까지 여기서 걸린다. */\n const verdict = judgeCriteria(rows)\n\n if (!verdict.ok) {\n return { applied: 'rejected', problems: verdict.problems }\n }\n\n return { applied: 'ok', criteria: rows }\n}\n"]}
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { ListParam } from '@things-factory/shell';
|
|
2
2
|
import { MasterPatch } from '../master-crud';
|
|
3
|
+
import { CriterionPatch } from './criteria-rows';
|
|
3
4
|
import { TestSpecification } from './test-specification';
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* 기준 한 줄 — **화면이 받는 모양.**
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* 규정이고, 규정은 바뀐다. 없앨 때 행을 지우지 않고 유효 기간의 끝을 채우는 것이 그래서다 —
|
|
10
|
-
* 지우면 그것을 가리키는 지난 기록이 가리킬 것을 잃는다.
|
|
11
|
-
*
|
|
12
|
-
* 규칙은 `master-crud.ts` 한 곳에 있다. 여기는 축의 이름만 붙인다.
|
|
8
|
+
* 한계를 펴서 낸다. 화면은 이름 · 설명 · 하한 · 상한 · 단위 · 표현식이 각자 칸인 표를 그리고,
|
|
9
|
+
* JSON 을 아예 모른다. 옮김은 `criteria-rows.ts` 한 곳이 든다 — 저장이 표로 내려가도 그 파일만 바뀐다.
|
|
13
10
|
*/
|
|
11
|
+
export declare class TestSpecificationCriterionRow {
|
|
12
|
+
id: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
evaluatedPropertyId?: string;
|
|
15
|
+
minimum?: number;
|
|
16
|
+
maximum?: number;
|
|
17
|
+
uom?: string;
|
|
18
|
+
expression?: string;
|
|
19
|
+
/**
|
|
20
|
+
* 화면이 아직 안 그리는 표준 칸들.
|
|
21
|
+
*
|
|
22
|
+
* **버리지 않는다.** 편 것을 다시 담을 때 이 칸들이 사라지면 사람이 적은 것이 조용히 없어진다.
|
|
23
|
+
*/
|
|
24
|
+
keptFields?: Record<string, unknown>;
|
|
25
|
+
}
|
|
14
26
|
export declare class TestSpecificationList {
|
|
15
27
|
items: TestSpecification[];
|
|
16
28
|
total: number;
|
|
@@ -25,4 +37,18 @@ export declare class TestSpecificationResolver {
|
|
|
25
37
|
* 엔티티라 무엇이 저장됐는지 스키마로 확인할 수 있다.
|
|
26
38
|
*/
|
|
27
39
|
updateMultipleTestSpecification(patches: MasterPatch[], context: ResolverContext): Promise<TestSpecification[]>;
|
|
40
|
+
/**
|
|
41
|
+
* 명세 하나의 기준을 **행으로** 낸다.
|
|
42
|
+
*
|
|
43
|
+
* 화면이 JSON 을 풀지 않게 하는 자리다. 명세를 못 찾으면 빈 목록을 주지 않고 말한다 — 빈 목록은
|
|
44
|
+
* 「기준이 없는 명세」와 같아 보이고, 그러면 사람이 기준을 다시 적는다.
|
|
45
|
+
*/
|
|
46
|
+
testSpecificationCriteria(name: string, context: ResolverContext): Promise<TestSpecificationCriterionRow[]>;
|
|
47
|
+
/**
|
|
48
|
+
* 기준을 **바뀐 줄만** 받아 얹는다.
|
|
49
|
+
*
|
|
50
|
+
* 목록 전체를 받으면 두 사람이 같은 명세를 열어 각자 한 줄씩 고쳤을 때 나중에 저장한 쪽이 앞사람의
|
|
51
|
+
* 줄을 지운다. 판단은 `criteria-rows.ts` 와 `criteria-check.ts` 가 든다.
|
|
52
|
+
*/
|
|
53
|
+
updateTestSpecificationCriteria(name: string, patches: CriterionPatch[], context: ResolverContext): Promise<TestSpecificationCriterionRow[]>;
|
|
28
54
|
}
|