@things-factory/ops-master 10.0.19 → 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.
Files changed (247) hide show
  1. package/dist-client/bootstrap.d.ts +18 -0
  2. package/dist-client/bootstrap.js +36 -0
  3. package/dist-client/bootstrap.js.map +1 -0
  4. package/dist-client/index.d.ts +15 -1
  5. package/dist-client/index.js +18 -1
  6. package/dist-client/index.js.map +1 -1
  7. package/dist-client/pages/master/equipment-class.d.ts +12 -0
  8. package/dist-client/pages/master/equipment-class.js +42 -0
  9. package/dist-client/pages/master/equipment-class.js.map +1 -0
  10. package/dist-client/pages/master/equipment.d.ts +12 -0
  11. package/dist-client/pages/master/equipment.js +57 -0
  12. package/dist-client/pages/master/equipment.js.map +1 -0
  13. package/dist-client/pages/master/grist-iso-duration.d.ts +20 -0
  14. package/dist-client/pages/master/grist-iso-duration.js +70 -0
  15. package/dist-client/pages/master/grist-iso-duration.js.map +1 -0
  16. package/dist-client/pages/master/grist-reference.d.ts +91 -0
  17. package/dist-client/pages/master/grist-reference.js +419 -0
  18. package/dist-client/pages/master/grist-reference.js.map +1 -0
  19. package/dist-client/pages/master/import-rows.d.ts +35 -0
  20. package/dist-client/pages/master/import-rows.js +66 -0
  21. package/dist-client/pages/master/import-rows.js.map +1 -0
  22. package/dist-client/pages/master/iso-duration.d.ts +34 -0
  23. package/dist-client/pages/master/iso-duration.js +76 -0
  24. package/dist-client/pages/master/iso-duration.js.map +1 -0
  25. package/dist-client/pages/master/master-options.d.ts +33 -0
  26. package/dist-client/pages/master/master-options.js +47 -0
  27. package/dist-client/pages/master/master-options.js.map +1 -0
  28. package/dist-client/pages/master/master-page-base.d.ts +357 -0
  29. package/dist-client/pages/master/master-page-base.js +626 -0
  30. package/dist-client/pages/master/master-page-base.js.map +1 -0
  31. package/dist-client/pages/master/material-class.d.ts +12 -0
  32. package/dist-client/pages/master/material-class.js +42 -0
  33. package/dist-client/pages/master/material-class.js.map +1 -0
  34. package/dist-client/pages/master/material-definition.d.ts +12 -0
  35. package/dist-client/pages/master/material-definition.js +62 -0
  36. package/dist-client/pages/master/material-definition.js.map +1 -0
  37. package/dist-client/pages/master/operation-definition.d.ts +12 -0
  38. package/dist-client/pages/master/operation-definition.js +49 -0
  39. package/dist-client/pages/master/operation-definition.js.map +1 -0
  40. package/dist-client/pages/master/operational-location.d.ts +84 -0
  41. package/dist-client/pages/master/operational-location.js +426 -0
  42. package/dist-client/pages/master/operational-location.js.map +1 -0
  43. package/dist-client/pages/master/person.d.ts +12 -0
  44. package/dist-client/pages/master/person.js +41 -0
  45. package/dist-client/pages/master/person.js.map +1 -0
  46. package/dist-client/pages/master/personnel-class.d.ts +12 -0
  47. package/dist-client/pages/master/personnel-class.js +43 -0
  48. package/dist-client/pages/master/personnel-class.js.map +1 -0
  49. package/dist-client/pages/master/physical-asset-class.d.ts +12 -0
  50. package/dist-client/pages/master/physical-asset-class.js +40 -0
  51. package/dist-client/pages/master/physical-asset-class.js.map +1 -0
  52. package/dist-client/pages/master/physical-asset.d.ts +12 -0
  53. package/dist-client/pages/master/physical-asset.js +41 -0
  54. package/dist-client/pages/master/physical-asset.js.map +1 -0
  55. package/dist-client/pages/master/recipe-definition.d.ts +136 -0
  56. package/dist-client/pages/master/recipe-definition.js +404 -0
  57. package/dist-client/pages/master/recipe-definition.js.map +1 -0
  58. package/dist-client/pages/master/reference-targets.d.ts +42 -0
  59. package/dist-client/pages/master/reference-targets.js +82 -0
  60. package/dist-client/pages/master/reference-targets.js.map +1 -0
  61. package/dist-client/pages/master/route-definition.d.ts +19 -0
  62. package/dist-client/pages/master/route-definition.js +40 -0
  63. package/dist-client/pages/master/route-definition.js.map +1 -0
  64. package/dist-client/pages/master/stop-reason.d.ts +12 -0
  65. package/dist-client/pages/master/stop-reason.js +48 -0
  66. package/dist-client/pages/master/stop-reason.js.map +1 -0
  67. package/dist-client/pages/master/test-specification.d.ts +93 -0
  68. package/dist-client/pages/master/test-specification.js +449 -0
  69. package/dist-client/pages/master/test-specification.js.map +1 -0
  70. package/dist-client/pages/master-axes.d.ts +20 -29
  71. package/dist-client/pages/master-axes.js +32 -182
  72. package/dist-client/pages/master-axes.js.map +1 -1
  73. package/dist-client/route.d.ts +1 -1
  74. package/dist-client/route.js +76 -3
  75. package/dist-client/route.js.map +1 -1
  76. package/dist-client/tsconfig.tsbuildinfo +1 -1
  77. package/dist-server/service/asset/asset-lifecycle.d.ts +30 -7
  78. package/dist-server/service/asset/asset-lifecycle.js +33 -6
  79. package/dist-server/service/asset/asset-lifecycle.js.map +1 -1
  80. package/dist-server/service/asset/asset-master-resolver.d.ts +9 -0
  81. package/dist-server/service/asset/asset-master-resolver.js +41 -0
  82. package/dist-server/service/asset/asset-master-resolver.js.map +1 -1
  83. package/dist-server/service/asset/asset-master.js +1 -1
  84. package/dist-server/service/asset/asset-master.js.map +1 -1
  85. package/dist-server/service/asset/asset-query.d.ts +9 -1
  86. package/dist-server/service/asset/asset-query.js +28 -6
  87. package/dist-server/service/asset/asset-query.js.map +1 -1
  88. package/dist-server/service/asset/physical-asset-class.js +1 -1
  89. package/dist-server/service/asset/physical-asset-class.js.map +1 -1
  90. package/dist-server/service/asset/physical-asset.d.ts +17 -4
  91. package/dist-server/service/asset/physical-asset.js +6 -6
  92. package/dist-server/service/asset/physical-asset.js.map +1 -1
  93. package/dist-server/service/equipment/equipment-class.js +1 -1
  94. package/dist-server/service/equipment/equipment-class.js.map +1 -1
  95. package/dist-server/service/equipment/equipment-master-resolver.d.ts +9 -0
  96. package/dist-server/service/equipment/equipment-master-resolver.js +58 -0
  97. package/dist-server/service/equipment/equipment-master-resolver.js.map +1 -1
  98. package/dist-server/service/equipment/equipment-master.d.ts +3 -2
  99. package/dist-server/service/equipment/equipment-master.js +21 -4
  100. package/dist-server/service/equipment/equipment-master.js.map +1 -1
  101. package/dist-server/service/equipment/equipment-query.d.ts +9 -1
  102. package/dist-server/service/equipment/equipment-query.js +28 -6
  103. package/dist-server/service/equipment/equipment-query.js.map +1 -1
  104. package/dist-server/service/equipment/equipment.d.ts +27 -1
  105. package/dist-server/service/equipment/equipment.js +8 -3
  106. package/dist-server/service/equipment/equipment.js.map +1 -1
  107. package/dist-server/service/equipment/state-period-rules.d.ts +19 -0
  108. package/dist-server/service/equipment/state-period-rules.js +35 -1
  109. package/dist-server/service/equipment/state-period-rules.js.map +1 -1
  110. package/dist-server/service/index.d.ts +4 -1
  111. package/dist-server/service/index.js +11 -2
  112. package/dist-server/service/index.js.map +1 -1
  113. package/dist-server/service/location/location-master-resolver.d.ts +29 -0
  114. package/dist-server/service/location/location-master-resolver.js +228 -1
  115. package/dist-server/service/location/location-master-resolver.js.map +1 -1
  116. package/dist-server/service/location/location-query.d.ts +9 -1
  117. package/dist-server/service/location/location-query.js +24 -9
  118. package/dist-server/service/location/location-query.js.map +1 -1
  119. package/dist-server/service/location/operational-location.d.ts +37 -0
  120. package/dist-server/service/location/operational-location.js +22 -2
  121. package/dist-server/service/location/operational-location.js.map +1 -1
  122. package/dist-server/service/master-axis.js +2 -2
  123. package/dist-server/service/master-axis.js.map +1 -1
  124. package/dist-server/service/master-conformance-resolver.d.ts +34 -0
  125. package/dist-server/service/master-conformance-resolver.js +143 -0
  126. package/dist-server/service/master-conformance-resolver.js.map +1 -0
  127. package/dist-server/service/master-conformance.d.ts +80 -0
  128. package/dist-server/service/master-conformance.js +266 -0
  129. package/dist-server/service/master-conformance.js.map +1 -0
  130. package/dist-server/service/master-page-query.d.ts +33 -0
  131. package/dist-server/service/master-page-query.js +55 -0
  132. package/dist-server/service/master-page-query.js.map +1 -0
  133. package/dist-server/service/master-page.d.ts +85 -0
  134. package/dist-server/service/master-page.js +104 -0
  135. package/dist-server/service/master-page.js.map +1 -0
  136. package/dist-server/service/material/material-class.js +1 -1
  137. package/dist-server/service/material/material-class.js.map +1 -1
  138. package/dist-server/service/material/material-definition.js +2 -2
  139. package/dist-server/service/material/material-definition.js.map +1 -1
  140. package/dist-server/service/material/material-master-resolver.d.ts +62 -0
  141. package/dist-server/service/material/material-master-resolver.js +213 -0
  142. package/dist-server/service/material/material-master-resolver.js.map +1 -1
  143. package/dist-server/service/material/material-query.d.ts +9 -1
  144. package/dist-server/service/material/material-query.js +28 -6
  145. package/dist-server/service/material/material-query.js.map +1 -1
  146. package/dist-server/service/material/recipe-index.d.ts +23 -0
  147. package/dist-server/service/material/recipe-index.js +44 -0
  148. package/dist-server/service/material/recipe-index.js.map +1 -0
  149. package/dist-server/service/operation/operation-definition.d.ts +1 -1
  150. package/dist-server/service/operation/operation-definition.js +4 -4
  151. package/dist-server/service/operation/operation-definition.js.map +1 -1
  152. package/dist-server/service/operation/operation-master-resolver.d.ts +24 -0
  153. package/dist-server/service/operation/operation-master-resolver.js +107 -0
  154. package/dist-server/service/operation/operation-master-resolver.js.map +1 -1
  155. package/dist-server/service/operation/operation-query.d.ts +10 -2
  156. package/dist-server/service/operation/operation-query.js +25 -10
  157. package/dist-server/service/operation/operation-query.js.map +1 -1
  158. package/dist-server/service/personnel/person.js +3 -3
  159. package/dist-server/service/personnel/person.js.map +1 -1
  160. package/dist-server/service/personnel/personnel-class.d.ts +1 -1
  161. package/dist-server/service/personnel/personnel-class.js +2 -2
  162. package/dist-server/service/personnel/personnel-class.js.map +1 -1
  163. package/dist-server/service/personnel/personnel-master-resolver.d.ts +9 -0
  164. package/dist-server/service/personnel/personnel-master-resolver.js +43 -0
  165. package/dist-server/service/personnel/personnel-master-resolver.js.map +1 -1
  166. package/dist-server/service/personnel/personnel-master.js +1 -1
  167. package/dist-server/service/personnel/personnel-master.js.map +1 -1
  168. package/dist-server/service/personnel/personnel-query.d.ts +9 -1
  169. package/dist-server/service/personnel/personnel-query.js +28 -6
  170. package/dist-server/service/personnel/personnel-query.js.map +1 -1
  171. package/dist-server/service/recipe/recipe-definition.d.ts +3 -3
  172. package/dist-server/service/recipe/recipe-definition.js +3 -3
  173. package/dist-server/service/recipe/recipe-definition.js.map +1 -1
  174. package/dist-server/service/recipe/recipe-master-resolver.d.ts +1 -1
  175. package/dist-server/service/recipe/recipe-master-resolver.js +1 -1
  176. package/dist-server/service/recipe/recipe-master-resolver.js.map +1 -1
  177. package/dist-server/service/recipe/recipe-master.d.ts +1 -1
  178. package/dist-server/service/recipe/recipe-master.js +2 -2
  179. package/dist-server/service/recipe/recipe-master.js.map +1 -1
  180. package/dist-server/service/recipe/recipe-query.d.ts +11 -3
  181. package/dist-server/service/recipe/recipe-query.js +30 -9
  182. package/dist-server/service/recipe/recipe-query.js.map +1 -1
  183. package/dist-server/service/recipe/route-definition.d.ts +6 -6
  184. package/dist-server/service/recipe/route-definition.js +6 -6
  185. package/dist-server/service/recipe/route-definition.js.map +1 -1
  186. package/dist-server/service/resource/class-lineage.d.ts +46 -0
  187. package/dist-server/service/resource/class-lineage.js +80 -0
  188. package/dist-server/service/resource/class-lineage.js.map +1 -0
  189. package/dist-server/service/resource/class-members.d.ts +36 -0
  190. package/dist-server/service/resource/class-members.js +50 -0
  191. package/dist-server/service/resource/class-members.js.map +1 -0
  192. package/dist-server/service/resource/referrers.d.ts +54 -0
  193. package/dist-server/service/resource/referrers.js +99 -0
  194. package/dist-server/service/resource/referrers.js.map +1 -0
  195. package/dist-server/service/test-spec/criteria-check.d.ts +53 -0
  196. package/dist-server/service/test-spec/criteria-check.js +142 -0
  197. package/dist-server/service/test-spec/criteria-check.js.map +1 -0
  198. package/dist-server/service/test-spec/criteria-rows.d.ts +89 -0
  199. package/dist-server/service/test-spec/criteria-rows.js +139 -0
  200. package/dist-server/service/test-spec/criteria-rows.js.map +1 -0
  201. package/dist-server/service/test-spec/test-spec-master-resolver.d.ts +33 -7
  202. package/dist-server/service/test-spec/test-spec-master-resolver.js +137 -2
  203. package/dist-server/service/test-spec/test-spec-master-resolver.js.map +1 -1
  204. package/dist-server/service/test-spec/test-spec-query.d.ts +9 -1
  205. package/dist-server/service/test-spec/test-spec-query.js +23 -9
  206. package/dist-server/service/test-spec/test-spec-query.js.map +1 -1
  207. package/dist-server/service/test-spec/test-specification.js +1 -1
  208. package/dist-server/service/test-spec/test-specification.js.map +1 -1
  209. package/dist-server/tsconfig.tsbuildinfo +1 -1
  210. package/helps/ops-master/equipment-class.ko.md +11 -0
  211. package/helps/ops-master/equipment-class.md +11 -0
  212. package/helps/ops-master/equipment.ko.md +11 -0
  213. package/helps/ops-master/equipment.md +11 -0
  214. package/helps/ops-master/material-class.ko.md +11 -0
  215. package/helps/ops-master/material-class.md +11 -0
  216. package/helps/ops-master/material-definition.ko.md +11 -0
  217. package/helps/ops-master/material-definition.md +11 -0
  218. package/helps/ops-master/operation-definition.ko.md +11 -0
  219. package/helps/ops-master/operation-definition.md +11 -0
  220. package/helps/ops-master/operational-location.ko.md +11 -0
  221. package/helps/ops-master/operational-location.md +11 -0
  222. package/helps/ops-master/person.ko.md +11 -0
  223. package/helps/ops-master/person.md +11 -0
  224. package/helps/ops-master/personnel-class.ko.md +11 -0
  225. package/helps/ops-master/personnel-class.md +11 -0
  226. package/helps/ops-master/physical-asset-class.ko.md +11 -0
  227. package/helps/ops-master/physical-asset-class.md +11 -0
  228. package/helps/ops-master/physical-asset.ko.md +11 -0
  229. package/helps/ops-master/physical-asset.md +11 -0
  230. package/helps/ops-master/recipe-definition.ko.md +11 -0
  231. package/helps/ops-master/recipe-definition.md +11 -0
  232. package/helps/ops-master/route-definition.ko.md +11 -0
  233. package/helps/ops-master/route-definition.md +11 -0
  234. package/helps/ops-master/stop-reason.ko.md +11 -0
  235. package/helps/ops-master/stop-reason.md +11 -0
  236. package/helps/ops-master/test-specification.ko.md +11 -0
  237. package/helps/ops-master/test-specification.md +11 -0
  238. package/package.json +5 -5
  239. package/things-factory.config.js +22 -2
  240. package/translations/en.json +86 -3
  241. package/translations/ja.json +92 -1
  242. package/translations/ko.json +93 -10
  243. package/translations/ms.json +92 -1
  244. package/translations/zh.json +92 -1
  245. package/dist-client/pages/master-list.d.ts +0 -68
  246. package/dist-client/pages/master-list.js +0 -239
  247. package/dist-client/pages/master-list.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import type { ResourceClassDef, TestResult } from '@operato/ops-contract';
1
+ import type { RatedUsage, RatedUsageMissing, ResourceClassDef, TestResult } from '@operato/ops-contract';
2
2
  /**
3
3
  * 이 자산을 지금 써도 되나 — **반복해서 쓰는 물건의 수명을 거는 자리.**
4
4
  *
@@ -27,8 +27,16 @@ export type AssetLack =
27
27
  | 'calibration-failed'
28
28
  /** 교정 기한이 지났다 — 교정하면 된다. */
29
29
  | 'calibration-expired'
30
- /** 정격 사용 횟수를 넘겼다 — 교체하거나 정격을 다시 정한다. */
31
- | 'cycle-exceeded';
30
+ /** 정격 사용량을 넘겼다 — 교체하거나 정격을 다시 정한다. */
31
+ | 'cycle-exceeded'
32
+ /**
33
+ * 정격과 쓴 양의 **단위가 달라 견줄 수 없다.**
34
+ *
35
+ * 50미터 정격에 「120번 썼다」가 오면 답할 수 있는 것이 없다. 숫자만 견주면 120 > 50 이라
36
+ * 「수명 끝」이라고 말하는데, 그것은 참일 수도 거짓일 수도 있다. 막지도 통과시키지도 않고
37
+ * 이 사실을 낸다 — 마스터를 고쳐야 하는 자리다.
38
+ */
39
+ | 'usage-uom-mismatch';
32
40
  export interface AssetGap {
33
41
  assetName: string;
34
42
  lack: AssetLack;
@@ -36,10 +44,12 @@ export interface AssetGap {
36
44
  specId?: string;
37
45
  /** 만료된 것이면 언제 만료됐나(ISO). */
38
46
  expiredAt?: string;
39
- /** 정격을 넘긴 것이면 몇 번 썼고 정격이 몇 번인가. */
47
+ /** 정격을 넘긴 것이면 얼마를 썼고 정격이 얼마인가. */
40
48
  used?: number;
41
49
  /** 정격. */
42
50
  limit?: number;
51
+ /** 정격의 단위 — 「번」이면 `C62`, 길이는 `MTR`. */
52
+ uom?: string;
43
53
  }
44
54
  /** 화면이 보여 줄 수명. 남은 횟수를 **모르면 비운다** — 0 으로 두면 「다 썼다」로 읽힌다. */
45
55
  export interface AssetLifecycle {
@@ -48,8 +58,17 @@ export interface AssetLifecycle {
48
58
  used: number;
49
59
  /** 정격 — 선언하지 않았으면 없다. */
50
60
  limit?: number;
51
- /** 남은 횟수 — 정격을 선언했을 때만 있다. */
61
+ /** 정격의 단위. */
62
+ uom?: string;
63
+ /**
64
+ * 남은 양 — **낼 수 없으면 없다.**
65
+ *
66
+ * 정격이 없으면 무한이 아니고, 단위가 다르면 견줄 수 없다. 그때 0 으로 두면 「다 썼다」로
67
+ * 읽힌다.
68
+ */
52
69
  remaining?: number;
70
+ /** 왜 판정하지 못했나 — 계약의 낱말 그대로(§`RatedUsageMissing`). */
71
+ usageMissing?: RatedUsageMissing[];
53
72
  /** 언제 교정했나(ISO) — 그 시점부터 센다. */
54
73
  calibratedAt?: string;
55
74
  /** 교정이 언제 만료되나(ISO). */
@@ -60,8 +79,12 @@ export interface AssetLifecycle {
60
79
  export interface JudgeableAsset {
61
80
  name: string;
62
81
  assetClassIds?: string[];
63
- /** 정격 사용 횟수. 선언하지 않았으면 횟수로 막지 않는다. */
64
- cycleLimit?: number;
82
+ /**
83
+ * 정격 사용량 — 얼마를 쓰면 교체하나. 선언하지 않았으면 이 축으로 막지 않는다.
84
+ *
85
+ * **단위가 함께 온다.** 「번」으로 세는 것만 다루면 라우터 비트의 50미터를 담을 수 없다.
86
+ */
87
+ ratedUsage?: RatedUsage;
65
88
  testResults?: TestResult[];
66
89
  effectiveStart?: Date | string;
67
90
  effectiveEnd?: Date | string;
@@ -36,16 +36,43 @@ function assetLifecycleOf(asset, classDefs, usedSinceCalibration, at) {
36
36
  gaps.push({ assetName: asset.name, lack: 'not-effective' });
37
37
  }
38
38
  gaps.push(...calibrationGaps(asset, classDefs, at));
39
- /* 정격을 선언하지 않았으면 횟수로 막지 않는다 — 선언한 것만 걸린다. */
40
- const limit = asset.cycleLimit;
41
- if (typeof limit === 'number' && limit > 0 && usedSinceCalibration >= limit) {
42
- gaps.push({ assetName: asset.name, lack: 'cycle-exceeded', used: usedSinceCalibration, limit });
39
+ /*
40
+ * **판정은 커널이 한다.** 정격이 없으면 무한이 아니고, 쓴 양이 없으면 0 이 아니고, 단위가
41
+ * 다르면 견주지 않는다 — 그 규칙을 여기서 다시 쓰면 두 벌이 되고 한쪽만 고쳐지는 날이 온다.
42
+ *
43
+ * 부르는 쪽이 세어 준 `usedSinceCalibration` 은 **「번」**이다. 실적이 세는 것이 횟수이기
44
+ * 때문이다. 그러니 단위를 `C62` 로 붙여 넘기고, 정격이 다른 단위면 커널이 `uom-mismatch` 로
45
+ * 답한다.
46
+ */
47
+ const usage = (0, ops_contract_1.ratedUsageOf)({
48
+ ratedUsage: asset.ratedUsage,
49
+ usedUsage: { value: usedSinceCalibration, uom: ops_contract_1.USE_UOM, since: calibratedAt }
50
+ });
51
+ if (usage.exhausted) {
52
+ gaps.push({
53
+ assetName: asset.name,
54
+ lack: 'cycle-exceeded',
55
+ used: usedSinceCalibration,
56
+ limit: usage.rated?.limit,
57
+ uom: usage.rated?.uom
58
+ });
59
+ }
60
+ if (usage.missing.includes('uom-mismatch')) {
61
+ gaps.push({
62
+ assetName: asset.name,
63
+ lack: 'usage-uom-mismatch',
64
+ used: usedSinceCalibration,
65
+ limit: usage.rated?.limit,
66
+ uom: usage.rated?.uom
67
+ });
43
68
  }
44
69
  return {
45
70
  assetName: asset.name,
46
71
  used: usedSinceCalibration,
47
- limit,
48
- remaining: typeof limit === 'number' && limit > 0 ? Math.max(0, limit - usedSinceCalibration) : undefined,
72
+ limit: usage.rated?.limit,
73
+ uom: usage.rated?.uom,
74
+ remaining: usage.remaining,
75
+ usageMissing: usage.missing.length ? usage.missing : undefined,
49
76
  calibratedAt,
50
77
  calibrationExpiresAt: nearestExpiry(asset, classDefs, at),
51
78
  gaps
@@ -1 +1 @@
1
- {"version":3,"file":"asset-lifecycle.js","sourceRoot":"","sources":["../../../server/service/asset/asset-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["import { classClosure, effectivityAt, requiredTestsFor, testPassedAt } from '@operato/ops-contract'\nimport type { ResourceClassDef, TestResult } from '@operato/ops-contract'\n\n/**\n * 이 자산을 지금 써도 되나 — **반복해서 쓰는 물건의 수명을 거는 자리.**\n *\n * 자산은 설비와 달리 **돌아온다.** 그래서 「고장인가」가 아니라 「몇 번 더 쓸 수 있나 · 교정이 살아\n * 있나」를 묻는다. 정격을 넘긴 지그로 계속 찍으면 치수가 벌어지고, 그 사실은 며칠 뒤 검사에서야\n * 드러난다. 그때는 이미 만든 것이 다 나가 있다.\n *\n * ── 교정에 새 칸을 만들지 않았다 ────────────────────────────────────────────\n * 자산은 이미 `testSpecificationIds` 와 `testResults` 를 든다. 교정은 검사이고, 교정 기한이 지난 것은\n * **검사 결과가 만료된 것**이다. 자격에서 쓴 판단이 그대로 붙는다 — `calibrationDue` 같은 칸을 새로\n * 만들면 같은 사실이 두 곳에 살고, 둘이 어긋나는 날이 온다.\n *\n * 새로 필요한 것은 **사용 횟수** 하나뿐이다. 그것만 정격(`cycleLimit`)과 견준다.\n *\n * ── 기록이 없을 때 (인원과 같은 규율) ──────────────────────────────────────\n * 교정 기록이 없으면 막는다. 「교정한 적이 없다」는 「교정이 살아 있다」가 아니다.\n */\n\nexport type AssetLack =\n /** 그 이름이 자산 마스터에 없다 — 등록하거나 이름을 고친다. */\n | 'not-registered'\n /** 폐기했거나 아직 들여오지 않았다. */\n | 'not-effective'\n /** 요구된 교정의 기록이 없다. */\n | 'calibration-missing'\n /** 교정에 불합격했다. */\n | 'calibration-failed'\n /** 교정 기한이 지났다 — 교정하면 된다. */\n | 'calibration-expired'\n /** 정격 사용 횟수를 넘겼다 — 교체하거나 정격을 다시 정한다. */\n | 'cycle-exceeded'\n\nexport interface AssetGap {\n assetName: string\n lack: AssetLack\n /** 교정 때문이면 어느 검사인가. */\n specId?: string\n /** 만료된 것이면 언제 만료됐나(ISO). */\n expiredAt?: string\n /** 정격을 넘긴 것이면 몇 번 썼고 정격이 몇 번인가. */\n used?: number\n /** 정격. */\n limit?: number\n}\n\n/** 화면이 보여 줄 수명. 남은 횟수를 **모르면 비운다** — 0 으로 두면 「다 썼다」로 읽힌다. */\nexport interface AssetLifecycle {\n assetName: string\n /** 교정 이후 몇 번 썼나. */\n used: number\n /** 정격 — 선언하지 않았으면 없다. */\n limit?: number\n /** 남은 횟수 — 정격을 선언했을 때만 있다. */\n remaining?: number\n /** 언제 교정했나(ISO) — 그 시점부터 센다. */\n calibratedAt?: string\n /** 교정이 언제 만료되나(ISO). */\n calibrationExpiresAt?: string\n gaps: AssetGap[]\n}\n\n/** 판단에 필요한 자산의 사실만. 엔티티를 통째로 받지 않는다. */\nexport interface JudgeableAsset {\n name: string\n assetClassIds?: string[]\n /** 정격 사용 횟수. 선언하지 않았으면 횟수로 막지 않는다. */\n cycleLimit?: number\n testResults?: TestResult[]\n effectiveStart?: Date | string\n effectiveEnd?: Date | string\n}\n\n/**\n * **언제부터 세나** — 마지막으로 통과한 교정 시각.\n *\n * 사용 횟수는 교정 이후로 센다. 교정은 자산을 처음 상태로 되돌리는 일이고, 되돌린 뒤에도 예전 횟수를\n * 계속 세면 멀쩡한 지그가 정격 초과로 잠긴다.\n *\n * 교정 기록이 없으면 비운다 — 부르는 쪽이 **처음부터** 센다.\n */\nexport function calibrationBaselineOf(asset: JudgeableAsset): string | undefined {\n let latest: string | undefined\n\n for (const result of asset.testResults ?? []) {\n if (result.result !== 'pass' || !result.at) {\n continue\n }\n if (!latest || Date.parse(result.at) > Date.parse(latest)) {\n latest = result.at\n }\n }\n\n return latest\n}\n\n/**\n * 이 자산을 지금 써도 되나.\n *\n * `usedSinceCalibration` 은 부르는 쪽이 센다 — 사용 기록은 실적에 있고 여기는 마스터이기 때문이다.\n */\nexport function assetLifecycleOf(\n asset: JudgeableAsset,\n classDefs: readonly ResourceClassDef[],\n usedSinceCalibration: number,\n at: string\n): AssetLifecycle {\n const gaps: AssetGap[] = []\n const calibratedAt = calibrationBaselineOf(asset)\n\n const period = { effectiveStart: isoOf(asset.effectiveStart), effectiveEnd: isoOf(asset.effectiveEnd) }\n if (effectivityAt(period, at) !== undefined) {\n gaps.push({ assetName: asset.name, lack: 'not-effective' })\n }\n\n gaps.push(...calibrationGaps(asset, classDefs, at))\n\n /* 정격을 선언하지 않았으면 횟수로 막지 않는다 — 선언한 것만 걸린다. */\n const limit = asset.cycleLimit\n if (typeof limit === 'number' && limit > 0 && usedSinceCalibration >= limit) {\n gaps.push({ assetName: asset.name, lack: 'cycle-exceeded', used: usedSinceCalibration, limit })\n }\n\n return {\n assetName: asset.name,\n used: usedSinceCalibration,\n limit,\n remaining: typeof limit === 'number' && limit > 0 ? Math.max(0, limit - usedSinceCalibration) : undefined,\n calibratedAt,\n calibrationExpiresAt: nearestExpiry(asset, classDefs, at),\n gaps\n }\n}\n\nfunction calibrationGaps(asset: JudgeableAsset, classDefs: readonly ResourceClassDef[], at: string): AssetGap[] {\n const closure = classClosure(asset.assetClassIds, classDefs, at)\n const required = requiredTestsFor([...closure], classDefs, at)\n if (!required.length) {\n return []\n }\n\n const bySpec = new Map((asset.testResults ?? []).map(result => [result.specId, result]))\n const gaps: AssetGap[] = []\n\n for (const specId of required) {\n const result = bySpec.get(specId)\n\n if (!result || (result.result !== 'pass' && result.result !== 'fail')) {\n gaps.push({ assetName: asset.name, lack: 'calibration-missing', specId })\n continue\n }\n\n if (result.result === 'fail') {\n gaps.push({ assetName: asset.name, lack: 'calibration-failed', specId })\n continue\n }\n\n if (!testPassedAt(result, at)) {\n gaps.push({ assetName: asset.name, lack: 'calibration-expired', specId, expiredAt: result.expiresAt })\n }\n }\n\n return gaps\n}\n\n/** 가장 먼저 만료되는 교정 시각. 화면이 「언제까지 쓸 수 있나」를 보이는 데 쓴다. */\nfunction nearestExpiry(\n asset: JudgeableAsset,\n classDefs: readonly ResourceClassDef[],\n at: string\n): string | undefined {\n const closure = classClosure(asset.assetClassIds, classDefs, at)\n const required = new Set(requiredTestsFor([...closure], classDefs, at))\n let nearest: string | undefined\n\n for (const result of asset.testResults ?? []) {\n if (!required.has(result.specId) || result.result !== 'pass' || !result.expiresAt) {\n continue\n }\n if (!nearest || Date.parse(result.expiresAt) < Date.parse(nearest)) {\n nearest = result.expiresAt\n }\n }\n\n return nearest\n}\n\nfunction isoOf(at: Date | string | undefined): string | undefined {\n if (!at) {\n return undefined\n }\n\n return typeof at === 'string' ? at : at.toISOString()\n}\n"]}
1
+ {"version":3,"file":"asset-lifecycle.js","sourceRoot":"","sources":["../../../server/service/asset/asset-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["import { USE_UOM, classClosure, effectivityAt, ratedUsageOf, requiredTestsFor, testPassedAt } from '@operato/ops-contract'\nimport type { RatedUsage, RatedUsageMissing, ResourceClassDef, TestResult } from '@operato/ops-contract'\n\n/**\n * 이 자산을 지금 써도 되나 — **반복해서 쓰는 물건의 수명을 거는 자리.**\n *\n * 자산은 설비와 달리 **돌아온다.** 그래서 「고장인가」가 아니라 「몇 번 더 쓸 수 있나 · 교정이 살아\n * 있나」를 묻는다. 정격을 넘긴 지그로 계속 찍으면 치수가 벌어지고, 그 사실은 며칠 뒤 검사에서야\n * 드러난다. 그때는 이미 만든 것이 다 나가 있다.\n *\n * ── 교정에 새 칸을 만들지 않았다 ────────────────────────────────────────────\n * 자산은 이미 `testSpecificationIds` 와 `testResults` 를 든다. 교정은 검사이고, 교정 기한이 지난 것은\n * **검사 결과가 만료된 것**이다. 자격에서 쓴 판단이 그대로 붙는다 — `calibrationDue` 같은 칸을 새로\n * 만들면 같은 사실이 두 곳에 살고, 둘이 어긋나는 날이 온다.\n *\n * 새로 필요한 것은 **사용 횟수** 하나뿐이다. 그것만 정격(`cycleLimit`)과 견준다.\n *\n * ── 기록이 없을 때 (인원과 같은 규율) ──────────────────────────────────────\n * 교정 기록이 없으면 막는다. 「교정한 적이 없다」는 「교정이 살아 있다」가 아니다.\n */\n\nexport type AssetLack =\n /** 그 이름이 자산 마스터에 없다 — 등록하거나 이름을 고친다. */\n | 'not-registered'\n /** 폐기했거나 아직 들여오지 않았다. */\n | 'not-effective'\n /** 요구된 교정의 기록이 없다. */\n | 'calibration-missing'\n /** 교정에 불합격했다. */\n | 'calibration-failed'\n /** 교정 기한이 지났다 — 교정하면 된다. */\n | 'calibration-expired'\n /** 정격 사용량을 넘겼다 — 교체하거나 정격을 다시 정한다. */\n | 'cycle-exceeded'\n /**\n * 정격과 쓴 양의 **단위가 달라 견줄 수 없다.**\n *\n * 50미터 정격에 「120번 썼다」가 오면 답할 수 있는 것이 없다. 숫자만 견주면 120 > 50 이라\n * 「수명 끝」이라고 말하는데, 그것은 참일 수도 거짓일 수도 있다. 막지도 통과시키지도 않고\n * 이 사실을 낸다 — 마스터를 고쳐야 하는 자리다.\n */\n | 'usage-uom-mismatch'\n\nexport interface AssetGap {\n assetName: string\n lack: AssetLack\n /** 교정 때문이면 어느 검사인가. */\n specId?: string\n /** 만료된 것이면 언제 만료됐나(ISO). */\n expiredAt?: string\n /** 정격을 넘긴 것이면 얼마를 썼고 정격이 얼마인가. */\n used?: number\n /** 정격. */\n limit?: number\n /** 정격의 단위 — 「번」이면 `C62`, 길이는 `MTR`. */\n uom?: string\n}\n\n/** 화면이 보여 줄 수명. 남은 횟수를 **모르면 비운다** — 0 으로 두면 「다 썼다」로 읽힌다. */\nexport interface AssetLifecycle {\n assetName: string\n /** 교정 이후 몇 번 썼나. */\n used: number\n /** 정격 — 선언하지 않았으면 없다. */\n limit?: number\n /** 정격의 단위. */\n uom?: string\n /**\n * 남은 양 — **낼 수 없으면 없다.**\n *\n * 정격이 없으면 무한이 아니고, 단위가 다르면 견줄 수 없다. 그때 0 으로 두면 「다 썼다」로\n * 읽힌다.\n */\n remaining?: number\n /** 왜 판정하지 못했나 — 계약의 낱말 그대로(§`RatedUsageMissing`). */\n usageMissing?: RatedUsageMissing[]\n /** 언제 교정했나(ISO) — 그 시점부터 센다. */\n calibratedAt?: string\n /** 교정이 언제 만료되나(ISO). */\n calibrationExpiresAt?: string\n gaps: AssetGap[]\n}\n\n/** 판단에 필요한 자산의 사실만. 엔티티를 통째로 받지 않는다. */\nexport interface JudgeableAsset {\n name: string\n assetClassIds?: string[]\n /**\n * 정격 사용량 — 얼마를 쓰면 교체하나. 선언하지 않았으면 이 축으로 막지 않는다.\n *\n * **단위가 함께 온다.** 「번」으로 세는 것만 다루면 라우터 비트의 50미터를 담을 수 없다.\n */\n ratedUsage?: RatedUsage\n testResults?: TestResult[]\n effectiveStart?: Date | string\n effectiveEnd?: Date | string\n}\n\n/**\n * **언제부터 세나** — 마지막으로 통과한 교정 시각.\n *\n * 사용 횟수는 교정 이후로 센다. 교정은 자산을 처음 상태로 되돌리는 일이고, 되돌린 뒤에도 예전 횟수를\n * 계속 세면 멀쩡한 지그가 정격 초과로 잠긴다.\n *\n * 교정 기록이 없으면 비운다 — 부르는 쪽이 **처음부터** 센다.\n */\nexport function calibrationBaselineOf(asset: JudgeableAsset): string | undefined {\n let latest: string | undefined\n\n for (const result of asset.testResults ?? []) {\n if (result.result !== 'pass' || !result.at) {\n continue\n }\n if (!latest || Date.parse(result.at) > Date.parse(latest)) {\n latest = result.at\n }\n }\n\n return latest\n}\n\n/**\n * 이 자산을 지금 써도 되나.\n *\n * `usedSinceCalibration` 은 부르는 쪽이 센다 — 사용 기록은 실적에 있고 여기는 마스터이기 때문이다.\n */\nexport function assetLifecycleOf(\n asset: JudgeableAsset,\n classDefs: readonly ResourceClassDef[],\n usedSinceCalibration: number,\n at: string\n): AssetLifecycle {\n const gaps: AssetGap[] = []\n const calibratedAt = calibrationBaselineOf(asset)\n\n const period = { effectiveStart: isoOf(asset.effectiveStart), effectiveEnd: isoOf(asset.effectiveEnd) }\n if (effectivityAt(period, at) !== undefined) {\n gaps.push({ assetName: asset.name, lack: 'not-effective' })\n }\n\n gaps.push(...calibrationGaps(asset, classDefs, at))\n\n /*\n * **판정은 커널이 한다.** 정격이 없으면 무한이 아니고, 쓴 양이 없으면 0 이 아니고, 단위가\n * 다르면 견주지 않는다 — 그 규칙을 여기서 다시 쓰면 두 벌이 되고 한쪽만 고쳐지는 날이 온다.\n *\n * 부르는 쪽이 세어 준 `usedSinceCalibration` 은 **「번」**이다. 실적이 세는 것이 횟수이기\n * 때문이다. 그러니 단위를 `C62` 로 붙여 넘기고, 정격이 다른 단위면 커널이 `uom-mismatch` 로\n * 답한다.\n */\n const usage = ratedUsageOf({\n ratedUsage: asset.ratedUsage,\n usedUsage: { value: usedSinceCalibration, uom: USE_UOM, since: calibratedAt }\n })\n\n if (usage.exhausted) {\n gaps.push({\n assetName: asset.name,\n lack: 'cycle-exceeded',\n used: usedSinceCalibration,\n limit: usage.rated?.limit,\n uom: usage.rated?.uom\n })\n }\n\n if (usage.missing.includes('uom-mismatch')) {\n gaps.push({\n assetName: asset.name,\n lack: 'usage-uom-mismatch',\n used: usedSinceCalibration,\n limit: usage.rated?.limit,\n uom: usage.rated?.uom\n })\n }\n\n return {\n assetName: asset.name,\n used: usedSinceCalibration,\n limit: usage.rated?.limit,\n uom: usage.rated?.uom,\n remaining: usage.remaining,\n usageMissing: usage.missing.length ? usage.missing : undefined,\n calibratedAt,\n calibrationExpiresAt: nearestExpiry(asset, classDefs, at),\n gaps\n }\n}\n\nfunction calibrationGaps(asset: JudgeableAsset, classDefs: readonly ResourceClassDef[], at: string): AssetGap[] {\n const closure = classClosure(asset.assetClassIds, classDefs, at)\n const required = requiredTestsFor([...closure], classDefs, at)\n if (!required.length) {\n return []\n }\n\n const bySpec = new Map((asset.testResults ?? []).map(result => [result.specId, result]))\n const gaps: AssetGap[] = []\n\n for (const specId of required) {\n const result = bySpec.get(specId)\n\n if (!result || (result.result !== 'pass' && result.result !== 'fail')) {\n gaps.push({ assetName: asset.name, lack: 'calibration-missing', specId })\n continue\n }\n\n if (result.result === 'fail') {\n gaps.push({ assetName: asset.name, lack: 'calibration-failed', specId })\n continue\n }\n\n if (!testPassedAt(result, at)) {\n gaps.push({ assetName: asset.name, lack: 'calibration-expired', specId, expiredAt: result.expiresAt })\n }\n }\n\n return gaps\n}\n\n/** 가장 먼저 만료되는 교정 시각. 화면이 「언제까지 쓸 수 있나」를 보이는 데 쓴다. */\nfunction nearestExpiry(\n asset: JudgeableAsset,\n classDefs: readonly ResourceClassDef[],\n at: string\n): string | undefined {\n const closure = classClosure(asset.assetClassIds, classDefs, at)\n const required = new Set(requiredTestsFor([...closure], classDefs, at))\n let nearest: string | undefined\n\n for (const result of asset.testResults ?? []) {\n if (!required.has(result.specId) || result.result !== 'pass' || !result.expiresAt) {\n continue\n }\n if (!nearest || Date.parse(result.expiresAt) < Date.parse(nearest)) {\n nearest = result.expiresAt\n }\n }\n\n return nearest\n}\n\nfunction isoOf(at: Date | string | undefined): string | undefined {\n if (!at) {\n return undefined\n }\n\n return typeof at === 'string' ? at : at.toISOString()\n}\n"]}
@@ -18,6 +18,15 @@ export declare class PhysicalAssetClassList {
18
18
  }
19
19
  export declare class PhysicalAssetClassResolver {
20
20
  physicalAssetClasses(params: ListParam, context: ResolverContext): Promise<PhysicalAssetClassList>;
21
+ /**
22
+ * 이 등급에 든 자산들.
23
+ *
24
+ * 아무도 안 들면 빈 목록이다. 다만 그것이 「없다」인지 「다 못 봤다」인지는 `memberScanCapped`
25
+ * 가 말한다.
26
+ */
27
+ members(klass: PhysicalAssetClass, context: ResolverContext): Promise<string[]>;
28
+ /** 소속을 다 보지 못했나. */
29
+ memberScanCapped(klass: PhysicalAssetClass, context: ResolverContext): Promise<boolean>;
21
30
  /**
22
31
  * 그리드가 보낸 변경을 한 번에 적용한다.
23
32
  *
@@ -7,6 +7,7 @@ const shell_1 = require("@things-factory/shell");
7
7
  const master_crud_1 = require("../master-crud");
8
8
  const master_axis_1 = require("../master-axis");
9
9
  const master_list_1 = require("../master-list");
10
+ const class_members_1 = require("../resource/class-members");
10
11
  const physical_asset_class_1 = require("./physical-asset-class");
11
12
  const physical_asset_1 = require("./physical-asset");
12
13
  /**
@@ -33,12 +34,36 @@ tslib_1.__decorate([
33
34
  exports.PhysicalAssetClassList = PhysicalAssetClassList = tslib_1.__decorate([
34
35
  (0, type_graphql_1.ObjectType)({ description: 'A page of physical asset classes' })
35
36
  ], PhysicalAssetClassList);
37
+ /**
38
+ * **이 등급에 무엇이 드나** — 들어오는 관계.
39
+ *
40
+ * 세는 방식과 상한은 `class-members.ts` 머리말에 적었다 — 등급 넷이 같은 함수를 쓴다.
41
+ */
42
+ function physicalAssetClassMembersOf(context) {
43
+ const state = context.state;
44
+ state.physicalAssetClassMembers =
45
+ state.physicalAssetClassMembers || (0, class_members_1.scanClassMembers)(physical_asset_1.PhysicalAsset, 'assetClassIds', state.domain);
46
+ return state.physicalAssetClassMembers;
47
+ }
36
48
  let PhysicalAssetClassResolver = class PhysicalAssetClassResolver {
37
49
  async physicalAssetClasses(params, context) {
38
50
  const { domain } = context.state;
39
51
  const { items, total } = await (0, master_list_1.listMaster)('physicalAssetClass', params, domain);
40
52
  return { items: items, total };
41
53
  }
54
+ /**
55
+ * 이 등급에 든 자산들.
56
+ *
57
+ * 아무도 안 들면 빈 목록이다. 다만 그것이 「없다」인지 「다 못 봤다」인지는 `memberScanCapped`
58
+ * 가 말한다.
59
+ */
60
+ async members(klass, context) {
61
+ return (await physicalAssetClassMembersOf(context)).members.get(klass.name) ?? [];
62
+ }
63
+ /** 소속을 다 보지 못했나. */
64
+ async memberScanCapped(klass, context) {
65
+ return (await physicalAssetClassMembersOf(context)).capped;
66
+ }
42
67
  /**
43
68
  * 그리드가 보낸 변경을 한 번에 적용한다.
44
69
  *
@@ -65,6 +90,22 @@ tslib_1.__decorate([
65
90
  tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
66
91
  tslib_1.__metadata("design:returntype", Promise)
67
92
  ], PhysicalAssetClassResolver.prototype, "physicalAssetClasses", null);
93
+ tslib_1.__decorate([
94
+ (0, type_graphql_1.FieldResolver)(type => [String], { description: 'Physical assets belonging to this class' }),
95
+ tslib_1.__param(0, (0, type_graphql_1.Root)()),
96
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
97
+ tslib_1.__metadata("design:type", Function),
98
+ tslib_1.__metadata("design:paramtypes", [physical_asset_class_1.PhysicalAssetClass, Object]),
99
+ tslib_1.__metadata("design:returntype", Promise)
100
+ ], PhysicalAssetClassResolver.prototype, "members", null);
101
+ tslib_1.__decorate([
102
+ (0, type_graphql_1.FieldResolver)(type => Boolean, { description: 'True when members were too many to scan fully' }),
103
+ tslib_1.__param(0, (0, type_graphql_1.Root)()),
104
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
105
+ tslib_1.__metadata("design:type", Function),
106
+ tslib_1.__metadata("design:paramtypes", [physical_asset_class_1.PhysicalAssetClass, Object]),
107
+ tslib_1.__metadata("design:returntype", Promise)
108
+ ], PhysicalAssetClassResolver.prototype, "memberScanCapped", null);
68
109
  tslib_1.__decorate([
69
110
  (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
70
111
  (0, type_graphql_1.Directive)('@transaction'),
@@ -1 +1 @@
1
- {"version":3,"file":"asset-master-resolver.js","sourceRoot":"","sources":["../../../server/service/asset/asset-master-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Args, Arg, Ctx, Directive, Int, Mutation, ObjectType, Field, Query, Resolver } from 'type-graphql'\n\nimport { ListParam, ScalarObject } from '@things-factory/shell'\n\nimport { MasterPatch, saveMasterPatches } from '../master-crud'\nimport { masterAxisOf } from '../master-axis'\nimport { listMaster } from '../master-list'\nimport { PhysicalAssetClass } from './physical-asset-class'\nimport { PhysicalAsset } from './physical-asset'\n\n/**\n * 물리 자산 축을 채우고 고친다.\n *\n * ── 왜 마스터는 그리드에서 고칠 수 있나 ────────────────────────────────────\n * 업무 기록과 달리 마스터는 **선언**이다. 「이 사람이 용접 자격을 갖는다」는 일어난 일이 아니라 지금의\n * 규정이고, 규정은 바뀐다. 없앨 때 행을 지우지 않고 유효 기간의 끝을 채우는 것이 그래서다 —\n * 지우면 그것을 가리키는 지난 기록이 가리킬 것을 잃는다.\n *\n * 규칙은 `master-crud.ts` 한 곳에 있다. 여기는 축의 이름만 붙인다.\n */\n\n@ObjectType({ description: 'A page of physical asset classes' })\nexport class PhysicalAssetClassList {\n @Field(type => [PhysicalAssetClass])\n items: PhysicalAssetClass[]\n\n @Field(type => Int)\n total: number\n}\n\n@Resolver(PhysicalAssetClass)\nexport class PhysicalAssetClassResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => PhysicalAssetClassList, { description: 'To fetch physical asset classes' })\n async physicalAssetClasses(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<PhysicalAssetClassList> {\n const { domain } = context.state\n const { items, total } = await listMaster('physicalAssetClass', params, domain)\n\n return { items: items as PhysicalAssetClass[], total }\n }\n\n /**\n * 그리드가 보낸 변경을 한 번에 적용한다.\n *\n * 패치를 `ScalarObject` 로 받는다 — 축마다 입력 타입을 따로 만들면 칸을 하나 더할 때마다 두 곳을\n * 고쳐야 하고, 한쪽을 빠뜨리면 그 칸이 오류 보고 없이 저장되지 않는다. 돌려주는 것은 타입이 있는\n * 엔티티라 무엇이 저장됐는지 스키마로 확인할 수 있다.\n */\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [PhysicalAssetClass], { description: 'To create, change or retire physical asset classes' })\n async updateMultiplePhysicalAssetClass(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<PhysicalAssetClass[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('physicalAssetClass')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as PhysicalAssetClass[]\n }\n}\n\n@ObjectType({ description: 'A page of physical assets' })\nexport class PhysicalAssetList {\n @Field(type => [PhysicalAsset])\n items: PhysicalAsset[]\n\n @Field(type => Int)\n total: number\n}\n\n@Resolver(PhysicalAsset)\nexport class PhysicalAssetResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => PhysicalAssetList, { description: 'To fetch physical assets' })\n async physicalAssets(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<PhysicalAssetList> {\n const { domain } = context.state\n const { items, total } = await listMaster('physicalAsset', params, domain)\n\n return { items: items as PhysicalAsset[], total }\n }\n\n /**\n * 그리드가 보낸 변경을 한 번에 적용한다.\n *\n * 패치를 `ScalarObject` 로 받는다 — 축마다 입력 타입을 따로 만들면 칸을 하나 더할 때마다 두 곳을\n * 고쳐야 하고, 한쪽을 빠뜨리면 그 칸이 오류 보고 없이 저장되지 않는다. 돌려주는 것은 타입이 있는\n * 엔티티라 무엇이 저장됐는지 스키마로 확인할 수 있다.\n */\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [PhysicalAsset], { description: 'To create, change or retire physical assets' })\n async updateMultiplePhysicalAsset(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<PhysicalAsset[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('physicalAsset')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as PhysicalAsset[]\n }\n}\n"]}
1
+ {"version":3,"file":"asset-master-resolver.js","sourceRoot":"","sources":["../../../server/service/asset/asset-master-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { FieldResolver, Root, Args, Arg, Ctx, Directive, Int, Mutation, ObjectType, Field, Query, Resolver } from 'type-graphql'\n\nimport { Domain, ListParam, ScalarObject } from '@things-factory/shell'\n\nimport { MasterPatch, saveMasterPatches } from '../master-crud'\nimport { masterAxisOf } from '../master-axis'\nimport { listMaster } from '../master-list'\nimport { ClassMembers, scanClassMembers } from '../resource/class-members'\nimport { PhysicalAssetClass } from './physical-asset-class'\nimport { PhysicalAsset } from './physical-asset'\n\n/**\n * 물리 자산 축을 채우고 고친다.\n *\n * ── 왜 마스터는 그리드에서 고칠 수 있나 ────────────────────────────────────\n * 업무 기록과 달리 마스터는 **선언**이다. 「이 사람이 용접 자격을 갖는다」는 일어난 일이 아니라 지금의\n * 규정이고, 규정은 바뀐다. 없앨 때 행을 지우지 않고 유효 기간의 끝을 채우는 것이 그래서다 —\n * 지우면 그것을 가리키는 지난 기록이 가리킬 것을 잃는다.\n *\n * 규칙은 `master-crud.ts` 한 곳에 있다. 여기는 축의 이름만 붙인다.\n */\n\n@ObjectType({ description: 'A page of physical asset classes' })\nexport class PhysicalAssetClassList {\n @Field(type => [PhysicalAssetClass])\n items: PhysicalAssetClass[]\n\n @Field(type => Int)\n total: number\n}\n\n/**\n * **이 등급에 무엇이 드나** — 들어오는 관계.\n *\n * 세는 방식과 상한은 `class-members.ts` 머리말에 적었다 — 등급 넷이 같은 함수를 쓴다.\n */\nfunction physicalAssetClassMembersOf(context: ResolverContext): Promise<ClassMembers> {\n const state = context.state as { domain: Domain; physicalAssetClassMembers?: Promise<ClassMembers> }\n\n state.physicalAssetClassMembers =\n state.physicalAssetClassMembers || scanClassMembers(PhysicalAsset, 'assetClassIds', state.domain)\n\n return state.physicalAssetClassMembers\n}\n\n@Resolver(PhysicalAssetClass)\nexport class PhysicalAssetClassResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => PhysicalAssetClassList, { description: 'To fetch physical asset classes' })\n async physicalAssetClasses(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<PhysicalAssetClassList> {\n const { domain } = context.state\n const { items, total } = await listMaster('physicalAssetClass', params, domain)\n\n return { items: items as PhysicalAssetClass[], total }\n }\n\n /**\n * 이 등급에 든 자산들.\n *\n * 아무도 안 들면 빈 목록이다. 다만 그것이 「없다」인지 「다 못 봤다」인지는 `memberScanCapped`\n * 가 말한다.\n */\n @FieldResolver(type => [String], { description: 'Physical assets belonging to this class' })\n async members(@Root() klass: PhysicalAssetClass, @Ctx() context: ResolverContext): Promise<string[]> {\n return (await physicalAssetClassMembersOf(context)).members.get(klass.name) ?? []\n }\n\n /** 소속을 다 보지 못했나. */\n @FieldResolver(type => Boolean, { description: 'True when members were too many to scan fully' })\n async memberScanCapped(@Root() klass: PhysicalAssetClass, @Ctx() context: ResolverContext): Promise<boolean> {\n return (await physicalAssetClassMembersOf(context)).capped\n }\n\n /**\n * 그리드가 보낸 변경을 한 번에 적용한다.\n *\n * 패치를 `ScalarObject` 로 받는다 — 축마다 입력 타입을 따로 만들면 칸을 하나 더할 때마다 두 곳을\n * 고쳐야 하고, 한쪽을 빠뜨리면 그 칸이 오류 보고 없이 저장되지 않는다. 돌려주는 것은 타입이 있는\n * 엔티티라 무엇이 저장됐는지 스키마로 확인할 수 있다.\n */\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [PhysicalAssetClass], { description: 'To create, change or retire physical asset classes' })\n async updateMultiplePhysicalAssetClass(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<PhysicalAssetClass[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('physicalAssetClass')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as PhysicalAssetClass[]\n }\n}\n\n@ObjectType({ description: 'A page of physical assets' })\nexport class PhysicalAssetList {\n @Field(type => [PhysicalAsset])\n items: PhysicalAsset[]\n\n @Field(type => Int)\n total: number\n}\n\n@Resolver(PhysicalAsset)\nexport class PhysicalAssetResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => PhysicalAssetList, { description: 'To fetch physical assets' })\n async physicalAssets(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<PhysicalAssetList> {\n const { domain } = context.state\n const { items, total } = await listMaster('physicalAsset', params, domain)\n\n return { items: items as PhysicalAsset[], total }\n }\n\n /**\n * 그리드가 보낸 변경을 한 번에 적용한다.\n *\n * 패치를 `ScalarObject` 로 받는다 — 축마다 입력 타입을 따로 만들면 칸을 하나 더할 때마다 두 곳을\n * 고쳐야 하고, 한쪽을 빠뜨리면 그 칸이 오류 보고 없이 저장되지 않는다. 돌려주는 것은 타입이 있는\n * 엔티티라 무엇이 저장됐는지 스키마로 확인할 수 있다.\n */\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [PhysicalAsset], { description: 'To create, change or retire physical assets' })\n async updateMultiplePhysicalAsset(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<PhysicalAsset[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('physicalAsset')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as PhysicalAsset[]\n }\n}\n"]}
@@ -6,7 +6,7 @@ const resource_class_1 = require("../resource/resource-class");
6
6
  function toAssetEntry(asset) {
7
7
  const out = { id: asset.name };
8
8
  (0, resource_class_1.put)(out, 'assetClassIds', asset.assetClassIds);
9
- (0, resource_class_1.put)(out, 'homeLocation', asset.homeLocation);
9
+ (0, resource_class_1.put)(out, 'homeLocationId', asset.homeLocation);
10
10
  (0, resource_class_1.put)(out, 'identity', asset.identity);
11
11
  (0, resource_class_1.put)(out, 'properties', asset.properties);
12
12
  (0, resource_class_1.put)(out, 'testSpecificationIds', asset.testSpecificationIds);
@@ -1 +1 @@
1
- {"version":3,"file":"asset-master.js","sourceRoot":"","sources":["../../../server/service/asset/asset-master.ts"],"names":[],"mappings":";;AAmBA,oCAaC;AAGD,kCAQC;AAzCD,+DAAyE;AAiBzE,SAAgB,YAAY,CAAC,KAAoB;IAC/C,MAAM,GAAG,GAA4B,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,CAAA;IAEvD,IAAA,oBAAG,EAAC,GAAG,EAAE,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,CAAA;IAC9C,IAAA,oBAAG,EAAC,GAAG,EAAE,cAAc,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA;IAC5C,IAAA,oBAAG,EAAC,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;IACpC,IAAA,oBAAG,EAAC,GAAG,EAAE,YAAY,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;IACxC,IAAA,oBAAG,EAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAAA;IAC5D,IAAA,oBAAG,EAAC,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;IAC1C,IAAA,oBAAG,EAAC,GAAG,EAAE,gBAAgB,EAAE,IAAA,oBAAG,EAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAA;IACrD,IAAA,oBAAG,EAAC,GAAG,EAAE,cAAc,EAAE,IAAA,oBAAG,EAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAA;IAEjD,OAAO,GAA4B,CAAA;AACrC,CAAC;AAED,gCAAgC;AAChC,SAAgB,WAAW,CACzB,OAA6B,EAC7B,MAAuB;IAEvB,OAAO;QACL,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,mCAAkB,CAAC;QAC7C,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;KACjC,CAAA;AACH,CAAC","sourcesContent":["import type { ResourceClassDef } from '@operato/ops-contract'\n\nimport { iso, put, toResourceClassDef } from '../resource/resource-class'\nimport type { PhysicalAsset } from './physical-asset'\nimport type { PhysicalAssetClass } from './physical-asset-class'\n\n/** 트윈 `ReferenceMaster.assets` 의 한 항목. */\nexport interface AssetEntry {\n id: string\n assetClassIds?: string[]\n homeLocation?: string\n identity?: string\n properties?: unknown[]\n testSpecificationIds?: string[]\n testResults?: unknown[]\n effectiveStart?: string\n effectiveEnd?: string\n}\n\nexport function toAssetEntry(asset: PhysicalAsset): AssetEntry {\n const out: Record<string, unknown> = { id: asset.name }\n\n put(out, 'assetClassIds', asset.assetClassIds)\n put(out, 'homeLocation', asset.homeLocation)\n put(out, 'identity', asset.identity)\n put(out, 'properties', asset.properties)\n put(out, 'testSpecificationIds', asset.testSpecificationIds)\n put(out, 'testResults', asset.testResults)\n put(out, 'effectiveStart', iso(asset.effectiveStart))\n put(out, 'effectiveEnd', iso(asset.effectiveEnd))\n\n return out as unknown as AssetEntry\n}\n\n/** 자산 축 전체 — 등급과 개체가 함께 나간다. */\nexport function assetMaster(\n classes: PhysicalAssetClass[],\n assets: PhysicalAsset[]\n): { assetClasses: ResourceClassDef[]; assets: AssetEntry[] } {\n return {\n assetClasses: classes.map(toResourceClassDef),\n assets: assets.map(toAssetEntry)\n }\n}\n"]}
1
+ {"version":3,"file":"asset-master.js","sourceRoot":"","sources":["../../../server/service/asset/asset-master.ts"],"names":[],"mappings":";;AAmBA,oCAaC;AAGD,kCAQC;AAzCD,+DAAyE;AAiBzE,SAAgB,YAAY,CAAC,KAAoB;IAC/C,MAAM,GAAG,GAA4B,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,CAAA;IAEvD,IAAA,oBAAG,EAAC,GAAG,EAAE,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,CAAA;IAC9C,IAAA,oBAAG,EAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA;IAC9C,IAAA,oBAAG,EAAC,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;IACpC,IAAA,oBAAG,EAAC,GAAG,EAAE,YAAY,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;IACxC,IAAA,oBAAG,EAAC,GAAG,EAAE,sBAAsB,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAAA;IAC5D,IAAA,oBAAG,EAAC,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;IAC1C,IAAA,oBAAG,EAAC,GAAG,EAAE,gBAAgB,EAAE,IAAA,oBAAG,EAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAA;IACrD,IAAA,oBAAG,EAAC,GAAG,EAAE,cAAc,EAAE,IAAA,oBAAG,EAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAA;IAEjD,OAAO,GAA4B,CAAA;AACrC,CAAC;AAED,gCAAgC;AAChC,SAAgB,WAAW,CACzB,OAA6B,EAC7B,MAAuB;IAEvB,OAAO;QACL,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,mCAAkB,CAAC;QAC7C,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC;KACjC,CAAA;AACH,CAAC","sourcesContent":["import type { ResourceClassDef } from '@operato/ops-contract'\n\nimport { iso, put, toResourceClassDef } from '../resource/resource-class'\nimport type { PhysicalAsset } from './physical-asset'\nimport type { PhysicalAssetClass } from './physical-asset-class'\n\n/** 트윈 `ReferenceMaster.assets` 의 한 항목. */\nexport interface AssetEntry {\n id: string\n assetClassIds?: string[]\n homeLocation?: string\n identity?: string\n properties?: unknown[]\n testSpecificationIds?: string[]\n testResults?: unknown[]\n effectiveStart?: string\n effectiveEnd?: string\n}\n\nexport function toAssetEntry(asset: PhysicalAsset): AssetEntry {\n const out: Record<string, unknown> = { id: asset.name }\n\n put(out, 'assetClassIds', asset.assetClassIds)\n put(out, 'homeLocationId', asset.homeLocation)\n put(out, 'identity', asset.identity)\n put(out, 'properties', asset.properties)\n put(out, 'testSpecificationIds', asset.testSpecificationIds)\n put(out, 'testResults', asset.testResults)\n put(out, 'effectiveStart', iso(asset.effectiveStart))\n put(out, 'effectiveEnd', iso(asset.effectiveEnd))\n\n return out as unknown as AssetEntry\n}\n\n/** 자산 축 전체 — 등급과 개체가 함께 나간다. */\nexport function assetMaster(\n classes: PhysicalAssetClass[],\n assets: PhysicalAsset[]\n): { assetClasses: ResourceClassDef[]; assets: AssetEntry[] } {\n return {\n assetClasses: classes.map(toResourceClassDef),\n assets: assets.map(toAssetEntry)\n }\n}\n"]}
@@ -1,8 +1,16 @@
1
1
  export declare class AssetMaster {
2
2
  assetClasses: string;
3
3
  assets: string;
4
+ /**
5
+ * 더 있나 — **이 값을 안 보고 쓰면 조용히 잘린 마스터로 트윈을 세우게 된다.**
6
+ *
7
+ * 거짓이 될 때까지 `next` 를 `after` 로 넘겨 이어 받는다.
8
+ */
9
+ hasMore: boolean;
10
+ /** 이어 받을 자리. 더 없으면 없다. */
11
+ next?: string;
4
12
  }
5
13
  export declare class AssetQuery {
6
14
  /** 자산 축 전체를 한 번에 낸다. */
7
- assetMaster(context: ResolverContext): Promise<AssetMaster>;
15
+ assetMaster(after: string | undefined, limit: number | undefined, context: ResolverContext): Promise<AssetMaster>;
8
16
  }
@@ -4,6 +4,7 @@ exports.AssetQuery = exports.AssetMaster = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const type_graphql_1 = require("type-graphql");
6
6
  const shell_1 = require("@things-factory/shell");
7
+ const master_page_query_1 = require("../master-page-query");
7
8
  const asset_master_1 = require("./asset-master");
8
9
  const physical_asset_1 = require("./physical-asset");
9
10
  const physical_asset_class_1 = require("./physical-asset-class");
@@ -18,30 +19,51 @@ tslib_1.__decorate([
18
19
  (0, type_graphql_1.Field)(type => String, { description: 'ReferenceMaster assets[] as JSON text' }),
19
20
  tslib_1.__metadata("design:type", String)
20
21
  ], AssetMaster.prototype, "assets", void 0);
22
+ tslib_1.__decorate([
23
+ (0, type_graphql_1.Field)({ description: 'true when more rows remain — keep pulling with next as after' }),
24
+ tslib_1.__metadata("design:type", Boolean)
25
+ ], AssetMaster.prototype, "hasMore", void 0);
26
+ tslib_1.__decorate([
27
+ (0, type_graphql_1.Field)(type => String, { nullable: true, description: 'cursor for the next page — the last name in this page' }),
28
+ tslib_1.__metadata("design:type", String)
29
+ ], AssetMaster.prototype, "next", void 0);
21
30
  exports.AssetMaster = AssetMaster = tslib_1.__decorate([
22
31
  (0, type_graphql_1.ObjectType)({ description: 'Physical asset master as the twin receives it' })
23
32
  ], AssetMaster);
24
33
  let AssetQuery = class AssetQuery {
25
34
  /** 자산 축 전체를 한 번에 낸다. */
26
- async assetMaster(context) {
35
+ async assetMaster(after, limit, context) {
27
36
  const { domain } = context.state;
28
- const [classes, assets] = await Promise.all([
37
+ const request = { after, limit };
38
+ /*
39
+ * **등급은 통째로, 구성원만 나눈다.** 구성원이 자기 등급을 이름으로 가리키므로 둘을 함께 나누면
40
+ * 구성원이 먼저 도착하고 등급이 나중에 오는 순간이 생긴다 — 그 순간 받는 쪽은 없는 등급을
41
+ * 가리키는 구성원을 갖는다. 등급은 수십 단위라 매 쪽에 실어도 크지 않다.
42
+ */
43
+ const [classes, page] = await Promise.all([
29
44
  (0, shell_1.getRepository)(physical_asset_class_1.PhysicalAssetClass).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } }),
30
- (0, shell_1.getRepository)(physical_asset_1.PhysicalAsset).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } })
45
+ (0, master_page_query_1.pullPage)(physical_asset_1.PhysicalAsset, domain, request)
31
46
  ]);
47
+ /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */
48
+ (0, master_page_query_1.refuseIfTooLarge)('물리자산', request, page);
49
+ const assets = page.rows;
32
50
  const master = (0, asset_master_1.assetMaster)(classes, assets);
33
51
  return {
34
52
  assetClasses: JSON.stringify(master.assetClasses),
35
- assets: JSON.stringify(master.assets)
53
+ assets: JSON.stringify(master.assets),
54
+ hasMore: page.hasMore,
55
+ ...(page.next ? { next: page.next } : {})
36
56
  };
37
57
  }
38
58
  };
39
59
  exports.AssetQuery = AssetQuery;
40
60
  tslib_1.__decorate([
41
61
  (0, type_graphql_1.Query)(returns => AssetMaster, { description: 'Physical asset master for the twin to pull' }),
42
- tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
62
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('after', { nullable: true, description: 'resume after this name — the previous page\'s next' })),
63
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('limit', type => type_graphql_1.Int, { nullable: true, description: 'rows per page — omit only when the axis is small enough to pull whole' })),
64
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
43
65
  tslib_1.__metadata("design:type", Function),
44
- tslib_1.__metadata("design:paramtypes", [Object]),
66
+ tslib_1.__metadata("design:paramtypes", [String, Number, Object]),
45
67
  tslib_1.__metadata("design:returntype", Promise)
46
68
  ], AssetQuery.prototype, "assetMaster", null);
47
69
  exports.AssetQuery = AssetQuery = tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"asset-query.js","sourceRoot":"","sources":["../../../server/service/asset/asset-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAsE;AAEtE,iDAAqD;AAErD,iDAAgE;AAChE,qDAAgD;AAChD,iEAA2D;AAGpD,IAAM,WAAW,GAAjB,MAAM,WAAW;CAMvB,CAAA;AANY,kCAAW;AAEtB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,iCAAiC,EAAE,CAAC;;iDACtD;AAGpB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;;2CAClE;sBALH,WAAW;IADvB,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,+CAA+C,EAAE,CAAC;GAChE,WAAW,CAMvB;AAGM,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,wBAAwB;IAElB,AAAN,KAAK,CAAC,WAAW,CAAQ,OAAwB;QAC/C,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC1C,IAAA,qBAAa,EAAC,yCAAkB,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACxG,IAAA,qBAAa,EAAC,8BAAa,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;SACpG,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,IAAA,0BAAgB,EAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAEhD,OAAO;YACL,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC;YACjD,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC;SACtC,CAAA;IACH,CAAC;CACF,CAAA;AAlBY,gCAAU;AAGf;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,WAAW,EAAE,4CAA4C,EAAE,CAAC;IAC1E,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;6CAcvB;qBAjBU,UAAU;IADtB,IAAA,uBAAQ,EAAC,8BAAa,CAAC;GACX,UAAU,CAkBtB","sourcesContent":["import { Ctx, Field, ObjectType, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { assetMaster as buildAssetMaster } from './asset-master'\nimport { PhysicalAsset } from './physical-asset'\nimport { PhysicalAssetClass } from './physical-asset-class'\n\n@ObjectType({ description: 'Physical asset master as the twin receives it' })\nexport class AssetMaster {\n @Field(type => String, { description: 'ResourceClassDef[] as JSON text' })\n assetClasses: string\n\n @Field(type => String, { description: 'ReferenceMaster assets[] as JSON text' })\n assets: string\n}\n\n@Resolver(PhysicalAsset)\nexport class AssetQuery {\n /** 자산 축 전체를 한 번에 낸다. */\n @Query(returns => AssetMaster, { description: 'Physical asset master for the twin to pull' })\n async assetMaster(@Ctx() context: ResolverContext): Promise<AssetMaster> {\n const { domain } = context.state\n\n const [classes, assets] = await Promise.all([\n getRepository(PhysicalAssetClass).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } }),\n getRepository(PhysicalAsset).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } })\n ])\n\n const master = buildAssetMaster(classes, assets)\n\n return {\n assetClasses: JSON.stringify(master.assetClasses),\n assets: JSON.stringify(master.assets)\n }\n }\n}\n"]}
1
+ {"version":3,"file":"asset-query.js","sourceRoot":"","sources":["../../../server/service/asset/asset-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Field, Int, ObjectType, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { pullPage, refuseIfTooLarge } from '../master-page-query'\n\nimport { assetMaster as buildAssetMaster } from './asset-master'\nimport { PhysicalAsset } from './physical-asset'\nimport { PhysicalAssetClass } from './physical-asset-class'\n\n@ObjectType({ description: 'Physical asset master as the twin receives it' })\nexport class AssetMaster {\n @Field(type => String, { description: 'ResourceClassDef[] as JSON text' })\n assetClasses: string\n\n @Field(type => String, { description: 'ReferenceMaster assets[] as JSON text' })\n assets: string\n\n /**\n * 더 있나 — **이 값을 안 보고 쓰면 조용히 잘린 마스터로 트윈을 세우게 된다.**\n *\n * 거짓이 될 때까지 `next` 를 `after` 로 넘겨 이어 받는다.\n */\n @Field({ description: 'true when more rows remain — keep pulling with next as after' })\n hasMore: boolean\n\n /** 이어 받을 자리. 더 없으면 없다. */\n @Field(type => String, { nullable: true, description: 'cursor for the next page — the last name in this page' })\n next?: string\n\n}\n\n@Resolver(PhysicalAsset)\nexport class AssetQuery {\n /** 자산 축 전체를 한 번에 낸다. */\n @Query(returns => AssetMaster, { description: 'Physical asset master for the twin to pull' })\n async assetMaster(\n @Arg('after', { nullable: true, description: 'resume after this name — the previous page\\'s next' }) after: string | undefined,\n @Arg('limit', type => Int, { nullable: true, description: 'rows per page — omit only when the axis is small enough to pull whole' }) limit: number | undefined,\n @Ctx() context: ResolverContext\n ): Promise<AssetMaster> {\n const { domain } = context.state\n\n const request = { after, limit }\n\n /*\n * **등급은 통째로, 구성원만 나눈다.** 구성원이 자기 등급을 이름으로 가리키므로 둘을 함께 나누면\n * 구성원이 먼저 도착하고 등급이 나중에 오는 순간이 생긴다 — 그 순간 받는 쪽은 없는 등급을\n * 가리키는 구성원을 갖는다. 등급은 수십 단위라 매 쪽에 실어도 크지 않다.\n */\n const [classes, page] = await Promise.all([\n getRepository(PhysicalAssetClass).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } }),\n pullPage<PhysicalAsset>(PhysicalAsset, domain, request)\n ])\n\n /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */\n refuseIfTooLarge('물리자산', request, page)\n\n const assets = page.rows\n\n const master = buildAssetMaster(classes, assets)\n\n return {\n assetClasses: JSON.stringify(master.assetClasses),\n assets: JSON.stringify(master.assets),\n hasMore: page.hasMore,\n ...(page.next ? { next: page.next } : {})\n }\n }\n}\n"]}
@@ -53,7 +53,7 @@ tslib_1.__decorate([
53
53
  ], PhysicalAssetClass.prototype, "testSpecificationIds", void 0);
54
54
  tslib_1.__decorate([
55
55
  (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
56
- (0, type_graphql_1.Field)(type => String, { nullable: true, description: 'ResourceProperty[] as JSON text' }),
56
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'ResourceProperty[]' }),
57
57
  tslib_1.__metadata("design:type", Array)
58
58
  ], PhysicalAssetClass.prototype, "properties", void 0);
59
59
  tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"physical-asset-class.js","sourceRoot":"","sources":["../../../server/service/asset/physical-asset-class.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/**\n * 자산 등급 — **ISA-95 `PhysicalAssetClass`**.\n *\n * 물리 자산은 반복해서 쓰는 물건이다 — 팔레트 · 랙 · 지그. 설비와 다른 점은 **돌아온다**는 것이다.\n * 공정이 「팔레트 1개」를 요구하고, 그것이 모자라면 출고가 못 나간다.\n *\n * 구조는 인원 등급 · 설비 등급과 같다(표준이 셋을 같은 모양으로 정의한다).\n */\n@Entity({ name: 'ops_physical_asset_class' })\n@Index('ix_ops_physical_asset_class_0', (klass: PhysicalAssetClass) => [klass.domain, klass.name], { unique: true })\n@ObjectType({ description: 'ISA-95 PhysicalAssetClass — a kind of equipment that operations require' })\nexport class PhysicalAssetClass {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((klass: PhysicalAssetClass) => klass.domain)\n domainId?: string\n\n /** 표준 `PhysicalAssetClassID`. */\n @Column()\n @Field()\n name: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n /** 상위 등급들 — 표준 `PhysicalAssetClassBaseID`(복수). 「6축 용접기」가 「용접기」를 상속한다. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true })\n baseIds?: string[]\n\n /** 이 등급의 적격을 정하는 `TestSpecification` 참조들 — 표준 `TestSpecificationID`. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true })\n testSpecificationIds?: string[]\n\n /** 등급 속성 — 표준 `PhysicalAssetClassProperty`. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => String, { nullable: true, description: 'ResourceProperty[] as JSON text' })\n properties?: Record<string, unknown>[]\n\n /** 언제부터 유효한가 — 표준 `EffectiveStartDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveStart?: Date\n\n /** 언제까지 유효한가 — 표준 `EffectiveEndDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveEnd?: Date\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((klass: PhysicalAssetClass) => klass.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((klass: PhysicalAssetClass) => klass.updater)\n updaterId?: string\n}\n"]}
1
+ {"version":3,"file":"physical-asset-class.js","sourceRoot":"","sources":["../../../server/service/asset/physical-asset-class.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/**\n * 자산 등급 — **ISA-95 `PhysicalAssetClass`**.\n *\n * 물리 자산은 반복해서 쓰는 물건이다 — 팔레트 · 랙 · 지그. 설비와 다른 점은 **돌아온다**는 것이다.\n * 공정이 「팔레트 1개」를 요구하고, 그것이 모자라면 출고가 못 나간다.\n *\n * 구조는 인원 등급 · 설비 등급과 같다(표준이 셋을 같은 모양으로 정의한다).\n */\n@Entity({ name: 'ops_physical_asset_class' })\n@Index('ix_ops_physical_asset_class_0', (klass: PhysicalAssetClass) => [klass.domain, klass.name], { unique: true })\n@ObjectType({ description: 'ISA-95 PhysicalAssetClass — a kind of equipment that operations require' })\nexport class PhysicalAssetClass {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((klass: PhysicalAssetClass) => klass.domain)\n domainId?: string\n\n /** 표준 `PhysicalAssetClassID`. */\n @Column()\n @Field()\n name: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n /** 상위 등급들 — 표준 `PhysicalAssetClassBaseID`(복수). 「6축 용접기」가 「용접기」를 상속한다. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true })\n baseIds?: string[]\n\n /** 이 등급의 적격을 정하는 `TestSpecification` 참조들 — 표준 `TestSpecificationID`. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true })\n testSpecificationIds?: string[]\n\n /** 등급 속성 — 표준 `PhysicalAssetClassProperty`. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'ResourceProperty[]' })\n properties?: Record<string, unknown>[]\n\n /** 언제부터 유효한가 — 표준 `EffectiveStartDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveStart?: Date\n\n /** 언제까지 유효한가 — 표준 `EffectiveEndDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveEnd?: Date\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((klass: PhysicalAssetClass) => klass.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((klass: PhysicalAssetClass) => klass.updater)\n updaterId?: string\n}\n"]}
@@ -25,15 +25,28 @@ export declare class PhysicalAsset {
25
25
  /** GS1 GRAI. 없으면 비워 둔다 — 지어내지 않는다. */
26
26
  identity?: string;
27
27
  /**
28
- * 정격 사용 횟수 — **표준 밖 확장이다.**
28
+ * 정격 사용량 — **얼마를 쓰면 교체하나.**
29
29
  *
30
30
  * ISA-95 는 자산의 수명을 세지 않는다(`PhysicalAssetProperty` 로 무엇이든 담으라고 한다). 그런데
31
31
  * 속성에 두면 그 값을 **읽고 막는 코드**가 속성 배열을 뒤져야 하고, 오타 하나가 오류 보고 없이
32
- * 「정격 없음」이 된다. 막는 데 쓰는 값은 칸으로 둔다.
32
+ * 「정격 없음」이 된다. 막는 데 쓰는 값은 칸으로 둔다. 계약도 같은 이유로 `RatedUsage` 를
33
+ * 속성 밖에 두었다.
33
34
  *
34
- * 선언하지 않으면 횟수로 막지 않는다 — 선언한 것만 걸린다.
35
+ * ── 단위가 함께 있어야 한다 ──────────────────────────────────────────────
36
+ * 처음에는 `cycleLimit`(int) 하나였다. 그러면 **「번」으로 세는 것만** 담긴다 — 라우터 비트는
37
+ * 50미터인데 50을 넣으면 「50번」이 되고, 한 번에 0.2미터를 깎는 공정이면 실제 수명의
38
+ * 250분의 1에서 막힌다. 그래서 그 값들을 아예 비워 두었었다.
39
+ *
40
+ * 계약이 `RatedUsage { limit, uom }` 를 열었다(0.9.3). 단위는 UN/CEFACT 권고 20 이고,
41
+ * 「번」은 `C62`, 길이는 `MTR` 이다.
42
+ *
43
+ * 선언하지 않으면 이 축으로 막지 않는다. **무한이 아니라 모르는 것이다** — 화면이 「제한 없음」
44
+ * 으로 그리면 안 된다(§`RatedUsageMissing`).
35
45
  */
36
- cycleLimit?: number;
46
+ ratedUsage?: {
47
+ limit: number;
48
+ uom: string;
49
+ };
37
50
  /** 개체 속성 — 표준 `PhysicalAssetProperty`. */
38
51
  properties?: Record<string, unknown>[];
39
52
  /** 이 자산에 요구되는 `TestSpecification` 참조들 — 검사 주기가 이 모양에 맞는다. */
@@ -56,13 +56,13 @@ tslib_1.__decorate([
56
56
  tslib_1.__metadata("design:type", String)
57
57
  ], PhysicalAsset.prototype, "identity", void 0);
58
58
  tslib_1.__decorate([
59
- (0, typeorm_1.Column)({ type: 'int', nullable: true }),
60
- (0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'rated number of cycles before this asset must be replaced' }),
61
- tslib_1.__metadata("design:type", Number)
62
- ], PhysicalAsset.prototype, "cycleLimit", void 0);
59
+ (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
60
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'RatedUsage — { limit, uom } · uom is UN/CEFACT rec 20' }),
61
+ tslib_1.__metadata("design:type", Object)
62
+ ], PhysicalAsset.prototype, "ratedUsage", void 0);
63
63
  tslib_1.__decorate([
64
64
  (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
65
- (0, type_graphql_1.Field)(type => String, { nullable: true, description: 'ResourceProperty[] as JSON text' }),
65
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'ResourceProperty[]' }),
66
66
  tslib_1.__metadata("design:type", Array)
67
67
  ], PhysicalAsset.prototype, "properties", void 0);
68
68
  tslib_1.__decorate([
@@ -72,7 +72,7 @@ tslib_1.__decorate([
72
72
  ], PhysicalAsset.prototype, "testSpecificationIds", void 0);
73
73
  tslib_1.__decorate([
74
74
  (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
75
- (0, type_graphql_1.Field)(type => String, { nullable: true, description: 'TestResult[] as JSON text' }),
75
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'TestResult[]' }),
76
76
  tslib_1.__metadata("design:type", Array)
77
77
  ], PhysicalAsset.prototype, "testResults", void 0);
78
78
  tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"physical-asset.js","sourceRoot":"","sources":["../../../server/service/asset/physical-asset.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, Int, ObjectType } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/**\n * 물리 자산 — **ISA-95 `PhysicalAsset`**.\n *\n * 반복해서 쓰는 물건이다(팔레트 · 랙 · 지그). 설비와 나눠 두는 이유는 사는 방식이 다르기 때문이다 —\n * 설비는 고장 나고 가동률로 평가받지만, 자산은 **돌아온다.** 한 그릇에 담으면 한쪽의 어휘가 다른 쪽에\n * 붙어 둘 다 거짓이 된다.\n */\n@Entity({ name: 'ops_physical_asset' })\n@Index('ix_ops_physical_asset_0', (asset: PhysicalAsset) => [asset.domain, asset.name], { unique: true })\n@ObjectType({ description: 'ISA-95 PhysicalAsset — a reusable asset such as a pallet or rack' })\nexport class PhysicalAsset {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((asset: PhysicalAsset) => asset.domain)\n domainId?: string\n\n /** 표준 `PhysicalAssetID` — 트윈으로 나갈 때 `assets[].id` 가 된다. */\n @Column()\n @Field()\n name: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n /**\n * 속한 등급들 — 표준 `PhysicalAssetClassID`(복수).\n *\n * 복수인 이유는 인원과 같다. 한 자산이 여러 등급에 들 수 있고, 하나만 담으면 한쪽 요구에서 보이지 않는다.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true })\n assetClassIds?: string[]\n\n /** 이 자산이 주로 있는 자리 — 표준 `OperationalLocation`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n homeLocation?: string\n\n /** GS1 GRAI. 없으면 비워 둔다 — 지어내지 않는다. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n identity?: string\n\n /**\n * 정격 사용 횟수 — **표준 밖 확장이다.**\n *\n * ISA-95 는 자산의 수명을 세지 않는다(`PhysicalAssetProperty` 로 무엇이든 담으라고 한다). 그런데\n * 속성에 두면 그 값을 **읽고 막는 코드**가 속성 배열을 뒤져야 하고, 오타 하나가 오류 보고 없이\n * 「정격 없음」이 된다. 막는 데 쓰는 값은 칸으로 둔다.\n *\n * 선언하지 않으면 횟수로 막지 않는다 — 선언한 것만 걸린다.\n */\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'rated number of cycles before this asset must be replaced' })\n cycleLimit?: number\n\n /** 개체 속성 — 표준 `PhysicalAssetProperty`. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => String, { nullable: true, description: 'ResourceProperty[] as JSON text' })\n properties?: Record<string, unknown>[]\n\n /** 이 자산에 요구되는 `TestSpecification` 참조들 — 검사 주기가 이 모양에 맞는다. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true })\n testSpecificationIds?: string[]\n\n /** `TestResult`. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => String, { nullable: true, description: 'TestResult[] as JSON text' })\n testResults?: Record<string, unknown>[]\n\n /** 언제부터 — 표준 `EffectiveStartDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveStart?: Date\n\n /** 언제까지 — 표준 `EffectiveEndDate`. 폐기한 자산이 계속 배정되지 않게 한다. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveEnd?: Date\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((asset: PhysicalAsset) => asset.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((asset: PhysicalAsset) => asset.updater)\n updaterId?: string\n}\n"]}
1
+ {"version":3,"file":"physical-asset.js","sourceRoot":"","sources":["../../../server/service/asset/physical-asset.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, ID, Int, ObjectType } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/**\n * 물리 자산 — **ISA-95 `PhysicalAsset`**.\n *\n * 반복해서 쓰는 물건이다(팔레트 · 랙 · 지그). 설비와 나눠 두는 이유는 사는 방식이 다르기 때문이다 —\n * 설비는 고장 나고 가동률로 평가받지만, 자산은 **돌아온다.** 한 그릇에 담으면 한쪽의 어휘가 다른 쪽에\n * 붙어 둘 다 거짓이 된다.\n */\n@Entity({ name: 'ops_physical_asset' })\n@Index('ix_ops_physical_asset_0', (asset: PhysicalAsset) => [asset.domain, asset.name], { unique: true })\n@ObjectType({ description: 'ISA-95 PhysicalAsset — a reusable asset such as a pallet or rack' })\nexport class PhysicalAsset {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((asset: PhysicalAsset) => asset.domain)\n domainId?: string\n\n /** 표준 `PhysicalAssetID` — 트윈으로 나갈 때 `assets[].id` 가 된다. */\n @Column()\n @Field()\n name: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n /**\n * 속한 등급들 — 표준 `PhysicalAssetClassID`(복수).\n *\n * 복수인 이유는 인원과 같다. 한 자산이 여러 등급에 들 수 있고, 하나만 담으면 한쪽 요구에서 보이지 않는다.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true })\n assetClassIds?: string[]\n\n /** 이 자산이 주로 있는 자리 — 표준 `OperationalLocation`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n homeLocation?: string\n\n /** GS1 GRAI. 없으면 비워 둔다 — 지어내지 않는다. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n identity?: string\n\n /**\n * 정격 사용량 — **얼마를 쓰면 교체하나.**\n *\n * ISA-95 는 자산의 수명을 세지 않는다(`PhysicalAssetProperty` 로 무엇이든 담으라고 한다). 그런데\n * 속성에 두면 그 값을 **읽고 막는 코드**가 속성 배열을 뒤져야 하고, 오타 하나가 오류 보고 없이\n * 「정격 없음」이 된다. 막는 데 쓰는 값은 칸으로 둔다. 계약도 같은 이유로 `RatedUsage` 를\n * 속성 밖에 두었다.\n *\n * ── 단위가 함께 있어야 한다 ──────────────────────────────────────────────\n * 처음에는 `cycleLimit`(int) 하나였다. 그러면 **「번」으로 세는 것만** 담긴다 — 라우터 비트는\n * 50미터인데 50을 넣으면 「50번」이 되고, 한 번에 0.2미터를 깎는 공정이면 실제 수명의\n * 250분의 1에서 막힌다. 그래서 그 값들을 아예 비워 두었었다.\n *\n * 계약이 `RatedUsage { limit, uom }` 를 열었다(0.9.3). 단위는 UN/CEFACT 권고 20 이고,\n * 「번」은 `C62`, 길이는 `MTR` 이다.\n *\n * 선언하지 않으면 이 축으로 막지 않는다. **무한이 아니라 모르는 것이다** — 화면이 「제한 없음」\n * 으로 그리면 안 된다(§`RatedUsageMissing`).\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'RatedUsage — { limit, uom } · uom is UN/CEFACT rec 20' })\n ratedUsage?: { limit: number; uom: string }\n\n /** 개체 속성 — 표준 `PhysicalAssetProperty`. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'ResourceProperty[]' })\n properties?: Record<string, unknown>[]\n\n /** 이 자산에 요구되는 `TestSpecification` 참조들 — 검사 주기가 이 모양에 맞는다. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => [String], { nullable: true })\n testSpecificationIds?: string[]\n\n /** `TestResult`. */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'TestResult[]' })\n testResults?: Record<string, unknown>[]\n\n /** 언제부터 — 표준 `EffectiveStartDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveStart?: Date\n\n /** 언제까지 — 표준 `EffectiveEndDate`. 폐기한 자산이 계속 배정되지 않게 한다. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveEnd?: Date\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((asset: PhysicalAsset) => asset.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((asset: PhysicalAsset) => asset.updater)\n updaterId?: string\n}\n"]}