@things-factory/ops-master 10.0.19 → 10.1.1

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 +6 -6
  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 +1 @@
1
- {"version":3,"file":"recipe-query.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAsE;AAEtE,iDAAqD;AAErD,2DAAsD;AACtD,yDAAoD;AACpD,mDAA6D;AAGtD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAMxB,CAAA;AANY,oCAAY;AAEvB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,0BAA0B,EAAE,CAAC;;6CACpD;AAGf;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;;4CACpD;uBALH,YAAY;IADxB,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;GAC/D,YAAY,CAMxB;AAGM,IAAM,WAAW,GAAjB,MAAM,WAAW;IACtB;;;;;OAKG;IAEG,AAAN,KAAK,CAAC,YAAY,CAAQ,OAAwB;QAChD,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,oCAAgB,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;YACtG,IAAA,qBAAa,EAAC,kCAAe,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;SACtG,CAAC,CAAA;QAEF,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,6BAAa,CAAC,CAAC;YACnD,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,4BAAY,CAAC,CAAC;SACjD,CAAA;IACH,CAAC;CACF,CAAA;AArBY,kCAAW;AAQhB;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;IACvE,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+CAYxB;sBApBU,WAAW;IADvB,IAAA,uBAAQ,EAAC,oCAAgB,CAAC;GACd,WAAW,CAqBvB","sourcesContent":["import { Ctx, Field, ObjectType, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { RecipeDefinition } from './recipe-definition'\nimport { RouteDefinition } from './route-definition'\nimport { toRecipeEntry, toRouteEntry } from './recipe-master'\n\n@ObjectType({ description: 'Recipes and routes as the twin receives them' })\nexport class RecipeMaster {\n @Field(type => String, { description: 'RecipeDef[] as JSON text' })\n recipes: string\n\n @Field(type => String, { description: 'RouteDef[] as JSON text' })\n routes: string\n}\n\n@Resolver(RecipeDefinition)\nexport class RecipeQuery {\n /**\n * 레시피와 라우트를 **한 질의로** 낸다.\n *\n * 따로 물으면 그 사이에 마스터가 바뀌어 레시피가 없는 라우트를 가리키는 순간이 생긴다. 구조는\n * 서로를 가리키므로 통째로 맞아야 한다 — 마스터를 당겨 가는 다른 축과 같은 규율이다.\n */\n @Query(returns => RecipeMaster, { description: 'Recipes and routes for the twin to pull' })\n async recipeMaster(@Ctx() context: ResolverContext): Promise<RecipeMaster> {\n const { domain } = context.state\n\n const [recipes, routes] = await Promise.all([\n getRepository(RecipeDefinition).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } }),\n getRepository(RouteDefinition).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } })\n ])\n\n return {\n recipes: JSON.stringify(recipes.map(toRecipeEntry)),\n routes: JSON.stringify(routes.map(toRouteEntry))\n }\n }\n}\n"]}
1
+ {"version":3,"file":"recipe-query.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAgF;AAEhF,iDAAqD;AAErD,4DAAiE;AAEjE,2DAAsD;AACtD,yDAAoD;AACpD,mDAA6D;AAGtD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAmBxB,CAAA;AAnBY,oCAAY;AAEvB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,0BAA0B,EAAE,CAAC;;6CACpD;AAGf;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;;4CACpD;AAQd;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,8DAA8D,EAAE,CAAC;;6CACvE;AAIhB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uDAAuD,EAAE,CAAC;;0CACnG;uBAjBF,YAAY;IADxB,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;GAC/D,YAAY,CAmBxB;AAGM,IAAM,WAAW,GAAjB,MAAM,WAAW;IACtB;;;;;OAKG;IAEG,AAAN,KAAK,CAAC,YAAY,CACqF,KAAyB,EACO,KAAyB,EACvJ,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;QAEhC;;;;WAIG;QACH,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACvC,IAAA,4BAAQ,EAAmB,oCAAgB,EAAE,MAAM,EAAE,OAAO,CAAC;YAC7D,IAAA,qBAAa,EAAC,kCAAe,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;SACtG,CAAC,CAAA;QAEF,0DAA0D;QAC1D,IAAA,oCAAgB,EAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;QAEtC,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,6BAAa,CAAC,CAAC;YACrD,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,4BAAY,CAAC,CAAC;YAChD,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,CAAA;IACH,CAAC;CACF,CAAA;AArCY,kCAAW;AAQhB;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;IAExF,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oDAAoD,EAAE,CAAC,CAAA;IACnG,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uEAAuE,EAAE,CAAC,CAAA;IACnI,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+CAyBP;sBApCU,WAAW;IADvB,IAAA,uBAAQ,EAAC,oCAAgB,CAAC;GACd,WAAW,CAqCvB","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 { RecipeDefinition } from './recipe-definition'\nimport { RouteDefinition } from './route-definition'\nimport { toRecipeEntry, toRouteEntry } from './recipe-master'\n\n@ObjectType({ description: 'Recipes and routes as the twin receives them' })\nexport class RecipeMaster {\n @Field(type => String, { description: 'RecipeDef[] as JSON text' })\n recipes: string\n\n @Field(type => String, { description: 'RouteDef[] as JSON text' })\n routes: 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(RecipeDefinition)\nexport class RecipeQuery {\n /**\n * 레시피와 라우팅을 **한 질의로** 낸다.\n *\n * 따로 물으면 그 사이에 마스터가 바뀌어 레시피가 없는 라우팅을 가리키는 순간이 생긴다. 구조는\n * 서로를 가리키므로 통째로 맞아야 한다 — 마스터를 당겨 가는 다른 축과 같은 규율이다.\n */\n @Query(returns => RecipeMaster, { description: 'Recipes and routes for the twin to pull' })\n async recipeMaster(\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<RecipeMaster> {\n const { domain } = context.state\n\n const request = { after, limit }\n\n /*\n * **라우팅은 통째로, 레시피만 나눈다.** 레시피가 라우팅을 이름으로 가리키므로, 둘을 함께 나누면\n * 레시피가 먼저 도착하고 라우팅이 나중에 오는 순간이 생긴다 — 이 파일이 원래 「따로 물으면 레시피가\n * 없는 라우팅을 가리키는 순간이 생긴다」고 적어 둔 그 이유다.\n */\n const [page, routes] = await Promise.all([\n pullPage<RecipeDefinition>(RecipeDefinition, domain, request),\n getRepository(RouteDefinition).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } })\n ])\n\n /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */\n refuseIfTooLarge('레시피', request, page)\n\n return {\n recipes: JSON.stringify(page.rows.map(toRecipeEntry)),\n routes: JSON.stringify(routes.map(toRouteEntry)),\n hasMore: page.hasMore,\n ...(page.next ? { next: page.next } : {})\n }\n }\n}\n"]}
@@ -1,17 +1,17 @@
1
1
  import { Domain } from '@things-factory/shell';
2
2
  import { User } from '@things-factory/auth-base';
3
3
  /**
4
- * 라우트 — **공정을 지나는 순서**. 커널 `RouteDef` 와 같은 모양이다.
4
+ * 라우팅 — **공정을 지나는 순서**. 커널 `RouteDef` 와 같은 모양이다.
5
5
  *
6
6
  * ── 왜 마스터에 있나 (2026-08-31) ────────────────────────────────────────
7
7
  * 커널에 `RouteDef` 가 있고 `JobOrder.routeKey` 가 그것을 가리키는데, **그 값이 가리킬 표가 없었다.**
8
- * 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은 작업지시에 라우트를 자동으로 붙일 수 없었다.
8
+ * 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은 작업지시에 라우팅을 자동으로 붙일 수 없었다.
9
9
  *
10
10
  * 트윈 모델 JSON 에 선언하는 길도 있었는데 그러지 않은 이유가 셋이다.
11
11
  *
12
- * · **유효기간을 담을 자리가 없다.** 라우트는 개정된다. 모델에 두면 「언제부터 이 라우트였나」가 없고,
13
- * 그러면 지난 실적이 **어느 라우트로 만든 것인지** 잃는다. 정산과 추적이 그 위에 선다.
14
- * · **MES 가 트윈 없이 배포된다.** 라우트가 모델에 살면 MES 가 트윈을 세워야 자기 지시서를 만든다.
12
+ * · **유효기간을 담을 자리가 없다.** 라우팅은 개정된다. 모델에 두면 「언제부터 이 라우팅였나」가 없고,
13
+ * 그러면 지난 실적이 **어느 라우팅으로 만든 것인지** 잃는다. 정산과 추적이 그 위에 선다.
14
+ * · **MES 가 트윈 없이 배포된다.** 라우팅이 모델에 살면 MES 가 트윈을 세워야 자기 지시서를 만든다.
15
15
  * · **표준이 그 자리에 둔다.** `OperationsDefinition` 은 Level 4·3 의 마스터이고 실행 모델이 아니다.
16
16
  *
17
17
  * ── 순서의 정본은 `steps` 다 ──────────────────────────────────────────────
@@ -22,7 +22,7 @@ export declare class RouteDefinition {
22
22
  readonly id: string;
23
23
  domain?: Domain;
24
24
  domainId?: string;
25
- /** 라우트 키 — 레시피와 오더가 이 값을 가리킨다(커널 `RouteDef.key`). */
25
+ /** 라우팅 키 — 레시피와 오더가 이 값을 가리킨다(커널 `RouteDef.key`). */
26
26
  name: string;
27
27
  description?: string;
28
28
  /**
@@ -7,17 +7,17 @@ const type_graphql_1 = require("type-graphql");
7
7
  const shell_1 = require("@things-factory/shell");
8
8
  const auth_base_1 = require("@things-factory/auth-base");
9
9
  /**
10
- * 라우트 — **공정을 지나는 순서**. 커널 `RouteDef` 와 같은 모양이다.
10
+ * 라우팅 — **공정을 지나는 순서**. 커널 `RouteDef` 와 같은 모양이다.
11
11
  *
12
12
  * ── 왜 마스터에 있나 (2026-08-31) ────────────────────────────────────────
13
13
  * 커널에 `RouteDef` 가 있고 `JobOrder.routeKey` 가 그것을 가리키는데, **그 값이 가리킬 표가 없었다.**
14
- * 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은 작업지시에 라우트를 자동으로 붙일 수 없었다.
14
+ * 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은 작업지시에 라우팅을 자동으로 붙일 수 없었다.
15
15
  *
16
16
  * 트윈 모델 JSON 에 선언하는 길도 있었는데 그러지 않은 이유가 셋이다.
17
17
  *
18
- * · **유효기간을 담을 자리가 없다.** 라우트는 개정된다. 모델에 두면 「언제부터 이 라우트였나」가 없고,
19
- * 그러면 지난 실적이 **어느 라우트로 만든 것인지** 잃는다. 정산과 추적이 그 위에 선다.
20
- * · **MES 가 트윈 없이 배포된다.** 라우트가 모델에 살면 MES 가 트윈을 세워야 자기 지시서를 만든다.
18
+ * · **유효기간을 담을 자리가 없다.** 라우팅은 개정된다. 모델에 두면 「언제부터 이 라우팅였나」가 없고,
19
+ * 그러면 지난 실적이 **어느 라우팅으로 만든 것인지** 잃는다. 정산과 추적이 그 위에 선다.
20
+ * · **MES 가 트윈 없이 배포된다.** 라우팅이 모델에 살면 MES 가 트윈을 세워야 자기 지시서를 만든다.
21
21
  * · **표준이 그 자리에 둔다.** `OperationsDefinition` 은 Level 4·3 의 마스터이고 실행 모델이 아니다.
22
22
  *
23
23
  * ── 순서의 정본은 `steps` 다 ──────────────────────────────────────────────
@@ -58,7 +58,7 @@ tslib_1.__decorate([
58
58
  ], RouteDefinition.prototype, "steps", void 0);
59
59
  tslib_1.__decorate([
60
60
  (0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
61
- (0, type_graphql_1.Field)(type => String, { nullable: true, description: 'ProcedureElementDef[] as JSON text' }),
61
+ (0, type_graphql_1.Field)(type => shell_1.ScalarObject, { nullable: true, description: 'ProcedureElementDef[]' }),
62
62
  tslib_1.__metadata("design:type", Array)
63
63
  ], RouteDefinition.prototype, "procedure", void 0);
64
64
  tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"route-definition.js","sourceRoot":"","sources":["../../../server/service/recipe/route-definition.ts"],"names":[],"mappings":"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","sourcesContent":["import { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } 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 * 라우트 — **공정을 지나는 순서**. 커널 `RouteDef` 와 같은 모양이다.\n *\n * ── 왜 마스터에 있나 (2026-08-31) ────────────────────────────────────────\n * 커널에 `RouteDef` 가 있고 `JobOrder.routeKey` 가 그것을 가리키는데, **그 값이 가리킬 표가 없었다.**\n * 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은 작업지시에 라우트를 자동으로 붙일 수 없었다.\n *\n * 트윈 모델 JSON 에 선언하는 길도 있었는데 그러지 않은 이유가 셋이다.\n *\n * · **유효기간을 담을 자리가 없다.** 라우트는 개정된다. 모델에 두면 「언제부터 이 라우트였나」가 없고,\n * 그러면 지난 실적이 **어느 라우트로 만든 것인지** 잃는다. 정산과 추적이 그 위에 선다.\n * · **MES 가 트윈 없이 배포된다.** 라우트가 모델에 살면 MES 가 트윈을 세워야 자기 지시서를 만든다.\n * · **표준이 그 자리에 둔다.** `OperationsDefinition` 은 Level 4·3 의 마스터이고 실행 모델이 아니다.\n *\n * ── 순서의 정본은 `steps` 다 ──────────────────────────────────────────────\n * `procedure`(ISA-88 절차 계층)는 그 위에 이름을 붙이는 것이다. 트리를 따로 두면 두 곳이 어긋날 수\n * 있고, 어긋났을 때 어느 쪽이 맞는지 말할 근거가 없다(§`RouteDef.procedure`).\n */\n@Entity({ name: 'ops_route_definition' })\n@Index('ix_ops_route_definition_0', (route: RouteDefinition) => [route.domain, route.name], { unique: true })\n@ObjectType({ description: 'Route — the ordered operations a material passes through (kernel RouteDef).' })\nexport class RouteDefinition {\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((route: RouteDefinition) => route.domain)\n domainId?: string\n\n /** 라우트 키 — 레시피와 오더가 이 값을 가리킨다(커널 `RouteDef.key`). */\n @Column()\n @Field()\n name: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n /**\n * 지나는 공정의 **순서** — `OperationDefinition.name` 의 목록(커널 `RouteDef.steps`).\n *\n * 이름으로 가리킨다. 외래키로 묶지 않는 이유는, 공정이 유효기간으로 끝나도 그것을 지나간 지난 기록이\n * 가리킬 것을 잃지 않아야 하기 때문이다 — 마스터의 다른 축도 같은 규율이다.\n */\n @Column({ type: 'simple-json' })\n @Field(type => [String])\n steps: string[]\n\n /** ISA-88 절차 계층 — 선언하지 않으면 `steps` 만으로 돈다(커널 `RouteDef.procedure`). */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => String, { nullable: true, description: 'ProcedureElementDef[] as JSON text' })\n procedure?: Record<string, unknown>[]\n\n /** 언제부터 유효한가 — 표준 `EffectiveStartDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveStart?: Date\n\n /**\n * 언제까지 유효한가 — 표준 `EffectiveEndDate`.\n *\n * **없앨 때 행을 지우지 않고 이 칸을 채운다.** 지우면 그것으로 만든 지난 기록이 가리킬 것을 잃는다.\n */\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((route: RouteDefinition) => route.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((route: RouteDefinition) => route.updater)\n updaterId?: string\n}\n"]}
1
+ {"version":3,"file":"route-definition.js","sourceRoot":"","sources":["../../../server/service/recipe/route-definition.ts"],"names":[],"mappings":";;;;AAAA,qCAAkI;AAClI,+CAAoD;AAEpD,iDAA4D;AAC5D,yDAAgD;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AAII,IAAM,eAAe,GAArB,MAAM,eAAe;CAuE3B,CAAA;AAvEY,0CAAe;AAGjB;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,CAAC;;2CACC;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;sCACb,cAAM;+CAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,KAAsB,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC;;iDACpC;AAKjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,GAAE;;6CACI;AAIZ;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oDACN;AAUpB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC/B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;;8CACT;AAKf;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;;kDACjD;AAKrC;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACT,IAAI;IAErB;;;;OAIG;;uDANkB;AASrB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACX,IAAI;qDAAA;AAInB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;kDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;kDAAA;AAIhB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,gBAAI;gDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,KAAsB,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;;kDACpC;AAIlB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,gBAAI;gDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,KAAsB,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;;kDACpC;0BAtEP,eAAe;IAH3B,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC;IACxC,IAAA,eAAK,EAAC,2BAA2B,EAAE,CAAC,KAAsB,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC5G,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,6EAA6E,EAAE,CAAC;GAC9F,eAAe,CAuE3B","sourcesContent":["import { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } 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 * 라우팅 — **공정을 지나는 순서**. 커널 `RouteDef` 와 같은 모양이다.\n *\n * ── 왜 마스터에 있나 (2026-08-31) ────────────────────────────────────────\n * 커널에 `RouteDef` 가 있고 `JobOrder.routeKey` 가 그것을 가리키는데, **그 값이 가리킬 표가 없었다.**\n * 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은 작업지시에 라우팅을 자동으로 붙일 수 없었다.\n *\n * 트윈 모델 JSON 에 선언하는 길도 있었는데 그러지 않은 이유가 셋이다.\n *\n * · **유효기간을 담을 자리가 없다.** 라우팅은 개정된다. 모델에 두면 「언제부터 이 라우팅였나」가 없고,\n * 그러면 지난 실적이 **어느 라우팅으로 만든 것인지** 잃는다. 정산과 추적이 그 위에 선다.\n * · **MES 가 트윈 없이 배포된다.** 라우팅이 모델에 살면 MES 가 트윈을 세워야 자기 지시서를 만든다.\n * · **표준이 그 자리에 둔다.** `OperationsDefinition` 은 Level 4·3 의 마스터이고 실행 모델이 아니다.\n *\n * ── 순서의 정본은 `steps` 다 ──────────────────────────────────────────────\n * `procedure`(ISA-88 절차 계층)는 그 위에 이름을 붙이는 것이다. 트리를 따로 두면 두 곳이 어긋날 수\n * 있고, 어긋났을 때 어느 쪽이 맞는지 말할 근거가 없다(§`RouteDef.procedure`).\n */\n@Entity({ name: 'ops_route_definition' })\n@Index('ix_ops_route_definition_0', (route: RouteDefinition) => [route.domain, route.name], { unique: true })\n@ObjectType({ description: 'Route — the ordered operations a material passes through (kernel RouteDef).' })\nexport class RouteDefinition {\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((route: RouteDefinition) => route.domain)\n domainId?: string\n\n /** 라우팅 키 — 레시피와 오더가 이 값을 가리킨다(커널 `RouteDef.key`). */\n @Column()\n @Field()\n name: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n /**\n * 지나는 공정의 **순서** — `OperationDefinition.name` 의 목록(커널 `RouteDef.steps`).\n *\n * 이름으로 가리킨다. 외래키로 묶지 않는 이유는, 공정이 유효기간으로 끝나도 그것을 지나간 지난 기록이\n * 가리킬 것을 잃지 않아야 하기 때문이다 — 마스터의 다른 축도 같은 규율이다.\n */\n @Column({ type: 'simple-json' })\n @Field(type => [String])\n steps: string[]\n\n /** ISA-88 절차 계층 — 선언하지 않으면 `steps` 만으로 돈다(커널 `RouteDef.procedure`). */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'ProcedureElementDef[]' })\n procedure?: Record<string, unknown>[]\n\n /** 언제부터 유효한가 — 표준 `EffectiveStartDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveStart?: Date\n\n /**\n * 언제까지 유효한가 — 표준 `EffectiveEndDate`.\n *\n * **없앨 때 행을 지우지 않고 이 칸을 채운다.** 지우면 그것으로 만든 지난 기록이 가리킬 것을 잃는다.\n */\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((route: RouteDefinition) => route.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((route: RouteDefinition) => route.updater)\n updaterId?: string\n}\n"]}
@@ -0,0 +1,46 @@
1
+ import type { ResourceClassFields } from './resource-class';
2
+ /**
3
+ * 등급 상속을 걷는다 — **`baseIds` 는 네 등급 표에 다 선언되어 있는데 아무도 걷지 않았다.**
4
+ *
5
+ * 표준 `MaterialClassBaseID` · `PersonnelClassBaseID` 는 등급이 등급을 상속한다고 말한다. 우리 표에도
6
+ * 칸이 있고 화면에서 편집도 되고 트윈에도 나간다. 그런데 그 값을 **읽어서 무엇을 답하는 곳이 한 군데도
7
+ * 없었다.** 그래서 「이 품목이 완제품인가」를 물으면 등급 이름 `MC-ECU-FG` 가 나왔다 — 사람은 읽지만
8
+ * 화면은 못 읽는다.
9
+ *
10
+ * ISA-95 에는 「완제품/반제품/원재료」를 담는 전용 요소가 없다. `MaterialDefinitionType` 을 전수로 보면
11
+ * `ID` · `Version` · `Description*` · `PublishedDate` · `EffectiveStartDate`/`EndDate` · `HierarchyScope` ·
12
+ * `SpatialDefinition` · `MaterialDefinitionProperty*` · `MaterialClassID*` · `MaterialLotSourceID*` ·
13
+ * `TestSpecificationID*` · `AssemblyDefinition*` 뿐이다. 표준이 그 구분을 두는 자리는 **등급**이고,
14
+ * 등급 표의 주석도 「원자재·완제품처럼 품목을 묶는 상위 개념」이라고 적고 있다.
15
+ *
16
+ * 그러니 새 축을 만들지 않는다. 이미 있는 상속을 걷는다.
17
+ */
18
+ /** 등급 이름 → 등급. 이름이 등급의 식별자다(표준 `MaterialClassID`). */
19
+ export type ClassIndex = ReadonlyMap<string, ResourceClassFields>;
20
+ /**
21
+ * 한 등급에서 위로 걸어 **뿌리 등급**들을 낸다.
22
+ *
23
+ * 뿌리는 `baseIds` 가 없는 등급이다. 「완제품」·「반제품」·「원재료」처럼 더 위가 없는 것.
24
+ *
25
+ * 답이 여럿일 수 있다 — 한 등급이 두 갈래를 상속하면 뿌리가 둘이다. **하나로 줄이지 않는다.**
26
+ * 줄이면 화면이 「완제품」이라고 단정하는데 실제로는 「완제품이면서 반제품」인 모순을 감춘다. 여럿이면
27
+ * 여럿으로 내고, 그것을 본 사람이 등급을 고치게 한다.
28
+ */
29
+ export declare function rootClassesOf(className: string, index: ClassIndex): string[];
30
+ /**
31
+ * 한 등급에서 뿌리까지의 **경로**를 낸다 — 뿌리가 앞, 자기가 뒤.
32
+ *
33
+ * `['원재료', 'ECU 기구부품']` 처럼 읽는다. 화면이 이것을 그대로 보이면 「이 품목이 무엇인가」를
34
+ * 사람이 한 번에 읽는다.
35
+ *
36
+ * 갈래가 여럿이면 **첫 갈래만** 낸다. 경로는 보여 주기 위한 것이고, 판정은 `rootClassesOf` 로 한다.
37
+ * 여기서 여러 경로를 다 내면 화면 한 칸에 못 담는다.
38
+ */
39
+ export declare function classPathOf(className: string, index: ClassIndex): string[];
40
+ /**
41
+ * 품목 하나가 어느 뿌리 등급들에 드는가 — 등급이 여럿이니 뿌리도 여럿일 수 있다.
42
+ *
43
+ * **등급이 하나도 없으면 빈 배열이다.** 「원재료」로 넘겨짚지 않는다 — 등급을 안 붙인 품목과 원재료를
44
+ * 섞으면, 붙이는 것을 잊었다는 사실이 화면에서 사라진다.
45
+ */
46
+ export declare function rootClassesOfMaterial(classNames: string[] | undefined, index: ClassIndex): string[];
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rootClassesOf = rootClassesOf;
4
+ exports.classPathOf = classPathOf;
5
+ exports.rootClassesOfMaterial = rootClassesOfMaterial;
6
+ /**
7
+ * 한 등급에서 위로 걸어 **뿌리 등급**들을 낸다.
8
+ *
9
+ * 뿌리는 `baseIds` 가 없는 등급이다. 「완제품」·「반제품」·「원재료」처럼 더 위가 없는 것.
10
+ *
11
+ * 답이 여럿일 수 있다 — 한 등급이 두 갈래를 상속하면 뿌리가 둘이다. **하나로 줄이지 않는다.**
12
+ * 줄이면 화면이 「완제품」이라고 단정하는데 실제로는 「완제품이면서 반제품」인 모순을 감춘다. 여럿이면
13
+ * 여럿으로 내고, 그것을 본 사람이 등급을 고치게 한다.
14
+ */
15
+ function rootClassesOf(className, index) {
16
+ const roots = [];
17
+ const seen = new Set();
18
+ const walk = (name) => {
19
+ /*
20
+ * **고리를 밟으면 멈춘다.** 화면에서 `baseIds` 를 손으로 넣을 수 있으니 A→B→A 가 만들어질 수
21
+ * 있다. 그때 이 함수가 안 돌아오면 목록 한 줄이 서버를 세운다.
22
+ */
23
+ if (seen.has(name)) {
24
+ return;
25
+ }
26
+ seen.add(name);
27
+ const klass = index.get(name);
28
+ /*
29
+ * **모르는 등급은 그 자체를 뿌리로 낸다.** 등급 표에 없는 이름이 `baseIds` 에 적혀 있을 수 있다
30
+ * (지워졌거나 오타). 조용히 버리면 계보가 짧아진 채 그럴듯해 보인다 — 이름을 그대로 내서 이상한
31
+ * 것이 화면에 보이게 한다.
32
+ */
33
+ if (!klass) {
34
+ roots.push(name);
35
+ return;
36
+ }
37
+ const bases = (klass.baseIds ?? []).filter(one => one);
38
+ if (bases.length === 0) {
39
+ roots.push(name);
40
+ return;
41
+ }
42
+ for (const base of bases) {
43
+ walk(base);
44
+ }
45
+ };
46
+ walk(className);
47
+ return [...new Set(roots)];
48
+ }
49
+ /**
50
+ * 한 등급에서 뿌리까지의 **경로**를 낸다 — 뿌리가 앞, 자기가 뒤.
51
+ *
52
+ * `['원재료', 'ECU 기구부품']` 처럼 읽는다. 화면이 이것을 그대로 보이면 「이 품목이 무엇인가」를
53
+ * 사람이 한 번에 읽는다.
54
+ *
55
+ * 갈래가 여럿이면 **첫 갈래만** 낸다. 경로는 보여 주기 위한 것이고, 판정은 `rootClassesOf` 로 한다.
56
+ * 여기서 여러 경로를 다 내면 화면 한 칸에 못 담는다.
57
+ */
58
+ function classPathOf(className, index) {
59
+ const path = [];
60
+ const seen = new Set();
61
+ let current = className;
62
+ while (current && !seen.has(current)) {
63
+ seen.add(current);
64
+ path.unshift(current);
65
+ const bases = (index.get(current)?.baseIds ?? []).filter(one => one);
66
+ current = bases[0];
67
+ }
68
+ return path;
69
+ }
70
+ /**
71
+ * 품목 하나가 어느 뿌리 등급들에 드는가 — 등급이 여럿이니 뿌리도 여럿일 수 있다.
72
+ *
73
+ * **등급이 하나도 없으면 빈 배열이다.** 「원재료」로 넘겨짚지 않는다 — 등급을 안 붙인 품목과 원재료를
74
+ * 섞으면, 붙이는 것을 잊었다는 사실이 화면에서 사라진다.
75
+ */
76
+ function rootClassesOfMaterial(classNames, index) {
77
+ const roots = (classNames ?? []).flatMap(one => rootClassesOf(one, index));
78
+ return [...new Set(roots)];
79
+ }
80
+ //# sourceMappingURL=class-lineage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"class-lineage.js","sourceRoot":"","sources":["../../../server/service/resource/class-lineage.ts"],"names":[],"mappings":";;AA+BA,sCAyCC;AAWD,kCAeC;AAQD,sDAIC;AAxFD;;;;;;;;GAQG;AACH,SAAgB,aAAa,CAAC,SAAiB,EAAE,KAAiB;IAChE,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAE9B,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE;QAC5B;;;WAGG;QACH,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACnB,OAAM;QACR,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAEd,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAE7B;;;;WAIG;QACH,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAChB,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;QAEtD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAChB,OAAM;QACR,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,CAAA;QACZ,CAAC;IACH,CAAC,CAAA;IAED,IAAI,CAAC,SAAS,CAAC,CAAA;IAEf,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;AAC5B,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,WAAW,CAAC,SAAiB,EAAE,KAAiB;IAC9D,MAAM,IAAI,GAAa,EAAE,CAAA;IACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAE9B,IAAI,OAAO,GAAuB,SAAS,CAAA;IAE3C,OAAO,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACjB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAErB,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;QACpE,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACpB,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,SAAgB,qBAAqB,CAAC,UAAgC,EAAE,KAAiB;IACvF,MAAM,KAAK,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;IAE1E,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;AAC5B,CAAC","sourcesContent":["import type { ResourceClassFields } from './resource-class'\n\n/**\n * 등급 상속을 걷는다 — **`baseIds` 는 네 등급 표에 다 선언되어 있는데 아무도 걷지 않았다.**\n *\n * 표준 `MaterialClassBaseID` · `PersonnelClassBaseID` 는 등급이 등급을 상속한다고 말한다. 우리 표에도\n * 칸이 있고 화면에서 편집도 되고 트윈에도 나간다. 그런데 그 값을 **읽어서 무엇을 답하는 곳이 한 군데도\n * 없었다.** 그래서 「이 품목이 완제품인가」를 물으면 등급 이름 `MC-ECU-FG` 가 나왔다 — 사람은 읽지만\n * 화면은 못 읽는다.\n *\n * ISA-95 에는 「완제품/반제품/원재료」를 담는 전용 요소가 없다. `MaterialDefinitionType` 을 전수로 보면\n * `ID` · `Version` · `Description*` · `PublishedDate` · `EffectiveStartDate`/`EndDate` · `HierarchyScope` ·\n * `SpatialDefinition` · `MaterialDefinitionProperty*` · `MaterialClassID*` · `MaterialLotSourceID*` ·\n * `TestSpecificationID*` · `AssemblyDefinition*` 뿐이다. 표준이 그 구분을 두는 자리는 **등급**이고,\n * 등급 표의 주석도 「원자재·완제품처럼 품목을 묶는 상위 개념」이라고 적고 있다.\n *\n * 그러니 새 축을 만들지 않는다. 이미 있는 상속을 걷는다.\n */\n\n/** 등급 이름 → 등급. 이름이 등급의 식별자다(표준 `MaterialClassID`). */\nexport type ClassIndex = ReadonlyMap<string, ResourceClassFields>\n\n/**\n * 한 등급에서 위로 걸어 **뿌리 등급**들을 낸다.\n *\n * 뿌리는 `baseIds` 가 없는 등급이다. 「완제품」·「반제품」·「원재료」처럼 더 위가 없는 것.\n *\n * 답이 여럿일 수 있다 — 한 등급이 두 갈래를 상속하면 뿌리가 둘이다. **하나로 줄이지 않는다.**\n * 줄이면 화면이 「완제품」이라고 단정하는데 실제로는 「완제품이면서 반제품」인 모순을 감춘다. 여럿이면\n * 여럿으로 내고, 그것을 본 사람이 등급을 고치게 한다.\n */\nexport function rootClassesOf(className: string, index: ClassIndex): string[] {\n const roots: string[] = []\n const seen = new Set<string>()\n\n const walk = (name: string) => {\n /*\n * **고리를 밟으면 멈춘다.** 화면에서 `baseIds` 를 손으로 넣을 수 있으니 A→B→A 가 만들어질 수\n * 있다. 그때 이 함수가 안 돌아오면 목록 한 줄이 서버를 세운다.\n */\n if (seen.has(name)) {\n return\n }\n seen.add(name)\n\n const klass = index.get(name)\n\n /*\n * **모르는 등급은 그 자체를 뿌리로 낸다.** 등급 표에 없는 이름이 `baseIds` 에 적혀 있을 수 있다\n * (지워졌거나 오타). 조용히 버리면 계보가 짧아진 채 그럴듯해 보인다 — 이름을 그대로 내서 이상한\n * 것이 화면에 보이게 한다.\n */\n if (!klass) {\n roots.push(name)\n return\n }\n\n const bases = (klass.baseIds ?? []).filter(one => one)\n\n if (bases.length === 0) {\n roots.push(name)\n return\n }\n\n for (const base of bases) {\n walk(base)\n }\n }\n\n walk(className)\n\n return [...new Set(roots)]\n}\n\n/**\n * 한 등급에서 뿌리까지의 **경로**를 낸다 — 뿌리가 앞, 자기가 뒤.\n *\n * `['원재료', 'ECU 기구부품']` 처럼 읽는다. 화면이 이것을 그대로 보이면 「이 품목이 무엇인가」를\n * 사람이 한 번에 읽는다.\n *\n * 갈래가 여럿이면 **첫 갈래만** 낸다. 경로는 보여 주기 위한 것이고, 판정은 `rootClassesOf` 로 한다.\n * 여기서 여러 경로를 다 내면 화면 한 칸에 못 담는다.\n */\nexport function classPathOf(className: string, index: ClassIndex): string[] {\n const path: string[] = []\n const seen = new Set<string>()\n\n let current: string | undefined = className\n\n while (current && !seen.has(current)) {\n seen.add(current)\n path.unshift(current)\n\n const bases = (index.get(current)?.baseIds ?? []).filter(one => one)\n current = bases[0]\n }\n\n return path\n}\n\n/**\n * 품목 하나가 어느 뿌리 등급들에 드는가 — 등급이 여럿이니 뿌리도 여럿일 수 있다.\n *\n * **등급이 하나도 없으면 빈 배열이다.** 「원재료」로 넘겨짚지 않는다 — 등급을 안 붙인 품목과 원재료를\n * 섞으면, 붙이는 것을 잊었다는 사실이 화면에서 사라진다.\n */\nexport function rootClassesOfMaterial(classNames: string[] | undefined, index: ClassIndex): string[] {\n const roots = (classNames ?? []).flatMap(one => rootClassesOf(one, index))\n\n return [...new Set(roots)]\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { Domain } from '@things-factory/shell';
2
+ /**
3
+ * **이 등급에 무엇이 드나** — 등급 넷이 나눠 쓰는 한 벌.
4
+ *
5
+ * ── 왜 필요한가 ────────────────────────────────────────────────────────────
6
+ * 등급을 만들어 놓고 거기 아무도 안 들어 있는 일이 실제로 있었다. 그러면 「자격이 맞는 사람만 그
7
+ * 공정에 선다」는 규칙이 한 번도 걸린 적이 없다는 뜻인데, 화면에는 등급 이름만 있어서 그 사실이
8
+ * 안 보였다.
9
+ *
10
+ * 등급을 없앨 때도 마찬가지다. 무엇이 딸려 자격을 잃는지 알 수 없었다.
11
+ *
12
+ * ── 넷이 구조가 같다 ───────────────────────────────────────────────────────
13
+ * 표준이 `PersonnelClass` · `EquipmentClass` · `PhysicalAssetClass` · `MaterialClass` 를 따로
14
+ * 정의하지만 「소속을 배열로 든다」는 모양은 같다. 그래서 세는 함수도 하나다 — 넷으로 나누면
15
+ * 한쪽만 고쳐지는 날이 온다.
16
+ *
17
+ * ── 배열 칸이라 훑는다 ─────────────────────────────────────────────────────
18
+ * `personnelClassIds` 는 `simple-json` 이다. 데이터베이스가 그 안을 못 보므로 `IN` 으로 못 좁힌다.
19
+ *
20
+ * **그래서 상한이 있고, 상한에 걸리면 말한다.** 조용히 자르면 「이 등급에 아무도 없다」와 「너무
21
+ * 많아 다 못 봤다」가 같아 보인다. 앞엣것은 고쳐야 할 선언이고 뒤엣것은 멀쩡한 등급이다.
22
+ */
23
+ /** 한 번에 훑을 소속 행 수. 품목의 제조법 훑기와 같은 값을 쓴다. */
24
+ export declare const MEMBER_SCAN_LIMIT = 2000;
25
+ export interface ClassMembers {
26
+ /** 등급 이름 → 그 등급에 든 것들의 이름. */
27
+ members: Map<string, string[]>;
28
+ /** 상한에 걸려 다 보지 못했나. */
29
+ capped: boolean;
30
+ }
31
+ /**
32
+ * 소속 표를 훑어 등급별로 뒤집는다.
33
+ *
34
+ * `Entity` 는 소속을 가진 쪽(사람 · 설비 · 자산 · 품목)이고, `field` 는 그 소속을 담은 칸 이름이다.
35
+ */
36
+ export declare function scanClassMembers(Entity: any, field: string, domain: Domain): Promise<ClassMembers>;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MEMBER_SCAN_LIMIT = void 0;
4
+ exports.scanClassMembers = scanClassMembers;
5
+ const shell_1 = require("@things-factory/shell");
6
+ const referrers_1 = require("./referrers");
7
+ /**
8
+ * **이 등급에 무엇이 드나** — 등급 넷이 나눠 쓰는 한 벌.
9
+ *
10
+ * ── 왜 필요한가 ────────────────────────────────────────────────────────────
11
+ * 등급을 만들어 놓고 거기 아무도 안 들어 있는 일이 실제로 있었다. 그러면 「자격이 맞는 사람만 그
12
+ * 공정에 선다」는 규칙이 한 번도 걸린 적이 없다는 뜻인데, 화면에는 등급 이름만 있어서 그 사실이
13
+ * 안 보였다.
14
+ *
15
+ * 등급을 없앨 때도 마찬가지다. 무엇이 딸려 자격을 잃는지 알 수 없었다.
16
+ *
17
+ * ── 넷이 구조가 같다 ───────────────────────────────────────────────────────
18
+ * 표준이 `PersonnelClass` · `EquipmentClass` · `PhysicalAssetClass` · `MaterialClass` 를 따로
19
+ * 정의하지만 「소속을 배열로 든다」는 모양은 같다. 그래서 세는 함수도 하나다 — 넷으로 나누면
20
+ * 한쪽만 고쳐지는 날이 온다.
21
+ *
22
+ * ── 배열 칸이라 훑는다 ─────────────────────────────────────────────────────
23
+ * `personnelClassIds` 는 `simple-json` 이다. 데이터베이스가 그 안을 못 보므로 `IN` 으로 못 좁힌다.
24
+ *
25
+ * **그래서 상한이 있고, 상한에 걸리면 말한다.** 조용히 자르면 「이 등급에 아무도 없다」와 「너무
26
+ * 많아 다 못 봤다」가 같아 보인다. 앞엣것은 고쳐야 할 선언이고 뒤엣것은 멀쩡한 등급이다.
27
+ */
28
+ /** 한 번에 훑을 소속 행 수. 품목의 제조법 훑기와 같은 값을 쓴다. */
29
+ exports.MEMBER_SCAN_LIMIT = 2000;
30
+ /**
31
+ * 소속 표를 훑어 등급별로 뒤집는다.
32
+ *
33
+ * `Entity` 는 소속을 가진 쪽(사람 · 설비 · 자산 · 품목)이고, `field` 는 그 소속을 담은 칸 이름이다.
34
+ */
35
+ async function scanClassMembers(Entity, field, domain) {
36
+ const repo = (0, shell_1.getRepository)(Entity);
37
+ const total = await repo.count({ where: { domain: { id: domain.id } } });
38
+ /*
39
+ * `id` 를 함께 뽑는다. 빼면 TypeORM 이 「no such column: distinctAlias.…_id」로 터진다 — `take` 를
40
+ * 쓰면 중복 제거를 위해 기본 키로 한 번 더 감싸는데, 그 키를 안 뽑으면 감쌀 것이 없다.
41
+ */
42
+ const rows = await repo.find({
43
+ where: { domain: { id: domain.id } },
44
+ select: ['id', 'name', field],
45
+ take: exports.MEMBER_SCAN_LIMIT
46
+ });
47
+ const members = (0, referrers_1.indexMultiReferrers)(rows, one => one[field] ?? [], one => one.name);
48
+ return { members, capped: total > rows.length };
49
+ }
50
+ //# sourceMappingURL=class-members.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"class-members.js","sourceRoot":"","sources":["../../../server/service/resource/class-members.ts"],"names":[],"mappings":";;;AAyCA,4CAqBC;AA9DD,iDAA6D;AAE7D,2CAAiD;AAEjD;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,4CAA4C;AAC/B,QAAA,iBAAiB,GAAG,IAAI,CAAA;AASrC;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CAAC,MAAW,EAAE,KAAa,EAAE,MAAc;IAC/E,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,MAAM,CAAC,CAAA;IAClC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAS,EAAE,CAAC,CAAA;IAE/E;;;OAGG;IACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC;QAC3B,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAS;QAC3C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAQ;QACpC,IAAI,EAAE,yBAAiB;KACxB,CAAC,CAAA;IAEF,MAAM,OAAO,GAAG,IAAA,+BAAmB,EACjC,IAAa,EACb,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,EACvB,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAChB,CAAA;IAED,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAI,IAAc,CAAC,MAAM,EAAE,CAAA;AAC5D,CAAC","sourcesContent":["import { Domain, getRepository } from '@things-factory/shell'\n\nimport { indexMultiReferrers } from './referrers'\n\n/**\n * **이 등급에 무엇이 드나** — 등급 넷이 나눠 쓰는 한 벌.\n *\n * ── 왜 필요한가 ────────────────────────────────────────────────────────────\n * 등급을 만들어 놓고 거기 아무도 안 들어 있는 일이 실제로 있었다. 그러면 「자격이 맞는 사람만 그\n * 공정에 선다」는 규칙이 한 번도 걸린 적이 없다는 뜻인데, 화면에는 등급 이름만 있어서 그 사실이\n * 안 보였다.\n *\n * 등급을 없앨 때도 마찬가지다. 무엇이 딸려 자격을 잃는지 알 수 없었다.\n *\n * ── 넷이 구조가 같다 ───────────────────────────────────────────────────────\n * 표준이 `PersonnelClass` · `EquipmentClass` · `PhysicalAssetClass` · `MaterialClass` 를 따로\n * 정의하지만 「소속을 배열로 든다」는 모양은 같다. 그래서 세는 함수도 하나다 — 넷으로 나누면\n * 한쪽만 고쳐지는 날이 온다.\n *\n * ── 배열 칸이라 훑는다 ─────────────────────────────────────────────────────\n * `personnelClassIds` 는 `simple-json` 이다. 데이터베이스가 그 안을 못 보므로 `IN` 으로 못 좁힌다.\n *\n * **그래서 상한이 있고, 상한에 걸리면 말한다.** 조용히 자르면 「이 등급에 아무도 없다」와 「너무\n * 많아 다 못 봤다」가 같아 보인다. 앞엣것은 고쳐야 할 선언이고 뒤엣것은 멀쩡한 등급이다.\n */\n\n/** 한 번에 훑을 소속 행 수. 품목의 제조법 훑기와 같은 값을 쓴다. */\nexport const MEMBER_SCAN_LIMIT = 2000\n\nexport interface ClassMembers {\n /** 등급 이름 → 그 등급에 든 것들의 이름. */\n members: Map<string, string[]>\n /** 상한에 걸려 다 보지 못했나. */\n capped: boolean\n}\n\n/**\n * 소속 표를 훑어 등급별로 뒤집는다.\n *\n * `Entity` 는 소속을 가진 쪽(사람 · 설비 · 자산 · 품목)이고, `field` 는 그 소속을 담은 칸 이름이다.\n */\nexport async function scanClassMembers(Entity: any, field: string, domain: Domain): Promise<ClassMembers> {\n const repo = getRepository(Entity)\n const total = await repo.count({ where: { domain: { id: domain.id } } as any })\n\n /*\n * `id` 를 함께 뽑는다. 빼면 TypeORM 이 「no such column: distinctAlias.…_id」로 터진다 — `take` 를\n * 쓰면 중복 제거를 위해 기본 키로 한 번 더 감싸는데, 그 키를 안 뽑으면 감쌀 것이 없다.\n */\n const rows = await repo.find({\n where: { domain: { id: domain.id } } as any,\n select: ['id', 'name', field] as any,\n take: MEMBER_SCAN_LIMIT\n })\n\n const members = indexMultiReferrers(\n rows as any[],\n one => one[field] ?? [],\n one => one.name\n )\n\n return { members, capped: total > (rows as any[]).length }\n}\n"]}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * **나를 가리키는 것들** — 들어오는 관계를 세는 공통 부분.
3
+ *
4
+ * ── 왜 필요한가 ────────────────────────────────────────────────────────────
5
+ * 마스터의 관계는 전부 「남의 표 칸에 든 이름」 한 가지 모양이다. 커널이 이름으로 주소를 잡기
6
+ * 때문이다. 그래서 나가는 쪽은 그 칸을 그대로 보이면 되는데, **들어오는 쪽은 아무도 안 세었다.**
7
+ *
8
+ * 자리 하나를 열어도 「여기서 누가 일하나 · 무슨 설비가 있나」를 알 수 없었다. 사람 화면을 열어
9
+ * 근무 자리로 거르는 수밖에 없었고, 설비는 또 설비 화면에서 같은 일을 해야 했다.
10
+ *
11
+ * ── 줄마다 세지 않는다 ─────────────────────────────────────────────────────
12
+ * 필드 리졸버는 줄마다 불린다. 거기서 그때그때 세면 `limit 20` 에 질의 20회다 — N+1 이다.
13
+ * 그래서 **페이지의 이름들을 한 번에** 묻고 그 답을 요청 안에 담는다. 담는 것은 결과가 아니라
14
+ * **약속**이다 — 줄들이 거의 동시에 묻기 때문에, 결과를 담으면 스무 개가 각자 질의를 띄운다.
15
+ *
16
+ * `childCount` 가 같은 방식을 쓴다(`location-master-resolver.ts`). 여기서는 그 방식을 축 여럿이
17
+ * 나눠 쓸 수 있게 뽑아 두었다.
18
+ */
19
+ /** 한 줄이 무엇을 가리키나 — 가리키는 이름과, 그 줄 자신의 이름. */
20
+ export interface Referrer {
21
+ /** 이 줄이 가리키는 대상의 이름. 비었으면 아무도 안 가리킨다. */
22
+ points?: string | null;
23
+ /** 이 줄의 이름. 사람이 목록에서 알아보는 값이다. */
24
+ name?: string | null;
25
+ }
26
+ /**
27
+ * 대상 이름 → 그것을 가리키는 줄들의 이름.
28
+ *
29
+ * **빈 값은 버린다.** 가리키는 곳이 없는 줄과 이름 없는 줄은 관계가 아니다. 그것을 빈 문자열
30
+ * 키로 모으면 「아무 데도 안 놓인 것들」이 한 자리에 뭉쳐 그럴듯한 관계처럼 보인다.
31
+ */
32
+ export declare function indexReferrers(rows: Referrer[]): Map<string, string[]>;
33
+ /**
34
+ * 화면에 보일 만큼만 자르고 **몇이 남았는지 함께 낸다.**
35
+ *
36
+ * 조용히 자르면 「여기 셋이 있다」와 「여기 서른이 있는데 셋만 보인다」가 같아 보인다. 자른 수를
37
+ * 말하지 않으면 그 칸을 보고 사람이 판단을 내린다.
38
+ */
39
+ export declare function cappedList(names: string[] | undefined, limit: number): {
40
+ shown: string[];
41
+ hidden: number;
42
+ };
43
+ /**
44
+ * **배열 칸을 가리키는 것들.**
45
+ *
46
+ * `steps` 나 `materialClassIds` 처럼 한 줄이 여럿을 가리키는 칸이 있다. 이런 칸은 `IN` 으로 못
47
+ * 좁힌다 — 저장이 `simple-json` 이라 데이터베이스가 그 안을 못 본다. 그래서 표를 훑어 여기서
48
+ * 뒤집는다.
49
+ *
50
+ * **훑는 데는 반드시 상한이 있어야 하고, 상한에 걸렸으면 말해야 한다.** 조용히 자르면 「이 공정을
51
+ * 아무도 안 쓴다」와 「너무 많아 다 못 봤다」가 같아 보인다. 앞엣것은 지워도 되는 공정이고
52
+ * 뒤엣것은 지우면 라인이 선다.
53
+ */
54
+ export declare function indexMultiReferrers<T>(rows: T[], pointsOf: (row: T) => (string | null | undefined)[], nameOf: (row: T) => string | null | undefined): Map<string, string[]>;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * **나를 가리키는 것들** — 들어오는 관계를 세는 공통 부분.
4
+ *
5
+ * ── 왜 필요한가 ────────────────────────────────────────────────────────────
6
+ * 마스터의 관계는 전부 「남의 표 칸에 든 이름」 한 가지 모양이다. 커널이 이름으로 주소를 잡기
7
+ * 때문이다. 그래서 나가는 쪽은 그 칸을 그대로 보이면 되는데, **들어오는 쪽은 아무도 안 세었다.**
8
+ *
9
+ * 자리 하나를 열어도 「여기서 누가 일하나 · 무슨 설비가 있나」를 알 수 없었다. 사람 화면을 열어
10
+ * 근무 자리로 거르는 수밖에 없었고, 설비는 또 설비 화면에서 같은 일을 해야 했다.
11
+ *
12
+ * ── 줄마다 세지 않는다 ─────────────────────────────────────────────────────
13
+ * 필드 리졸버는 줄마다 불린다. 거기서 그때그때 세면 `limit 20` 에 질의 20회다 — N+1 이다.
14
+ * 그래서 **페이지의 이름들을 한 번에** 묻고 그 답을 요청 안에 담는다. 담는 것은 결과가 아니라
15
+ * **약속**이다 — 줄들이 거의 동시에 묻기 때문에, 결과를 담으면 스무 개가 각자 질의를 띄운다.
16
+ *
17
+ * `childCount` 가 같은 방식을 쓴다(`location-master-resolver.ts`). 여기서는 그 방식을 축 여럿이
18
+ * 나눠 쓸 수 있게 뽑아 두었다.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.indexReferrers = indexReferrers;
22
+ exports.cappedList = cappedList;
23
+ exports.indexMultiReferrers = indexMultiReferrers;
24
+ /**
25
+ * 대상 이름 → 그것을 가리키는 줄들의 이름.
26
+ *
27
+ * **빈 값은 버린다.** 가리키는 곳이 없는 줄과 이름 없는 줄은 관계가 아니다. 그것을 빈 문자열
28
+ * 키로 모으면 「아무 데도 안 놓인 것들」이 한 자리에 뭉쳐 그럴듯한 관계처럼 보인다.
29
+ */
30
+ function indexReferrers(rows) {
31
+ const index = new Map();
32
+ for (const row of rows) {
33
+ const target = (row.points ?? '').trim();
34
+ const who = (row.name ?? '').trim();
35
+ if (!target || !who) {
36
+ continue;
37
+ }
38
+ const list = index.get(target) ?? [];
39
+ /*
40
+ * 같은 줄이 두 번 들어오면 한 번만 센다. 한 줄이 같은 대상을 두 칸에서 가리키는 축이 생길 수
41
+ * 있고(자리와 예비 자리처럼), 그때 개수가 부풀면 화면이 「둘이 있다」고 말한다.
42
+ */
43
+ if (!list.includes(who)) {
44
+ list.push(who);
45
+ }
46
+ index.set(target, list);
47
+ }
48
+ /* 목록의 순서를 정한다 — 같은 자료에 같은 화면이 나와야 한다. */
49
+ for (const list of index.values()) {
50
+ list.sort();
51
+ }
52
+ return index;
53
+ }
54
+ /**
55
+ * 화면에 보일 만큼만 자르고 **몇이 남았는지 함께 낸다.**
56
+ *
57
+ * 조용히 자르면 「여기 셋이 있다」와 「여기 서른이 있는데 셋만 보인다」가 같아 보인다. 자른 수를
58
+ * 말하지 않으면 그 칸을 보고 사람이 판단을 내린다.
59
+ */
60
+ function cappedList(names, limit) {
61
+ const all = names ?? [];
62
+ return { shown: all.slice(0, limit), hidden: Math.max(0, all.length - limit) };
63
+ }
64
+ /**
65
+ * **배열 칸을 가리키는 것들.**
66
+ *
67
+ * `steps` 나 `materialClassIds` 처럼 한 줄이 여럿을 가리키는 칸이 있다. 이런 칸은 `IN` 으로 못
68
+ * 좁힌다 — 저장이 `simple-json` 이라 데이터베이스가 그 안을 못 본다. 그래서 표를 훑어 여기서
69
+ * 뒤집는다.
70
+ *
71
+ * **훑는 데는 반드시 상한이 있어야 하고, 상한에 걸렸으면 말해야 한다.** 조용히 자르면 「이 공정을
72
+ * 아무도 안 쓴다」와 「너무 많아 다 못 봤다」가 같아 보인다. 앞엣것은 지워도 되는 공정이고
73
+ * 뒤엣것은 지우면 라인이 선다.
74
+ */
75
+ function indexMultiReferrers(rows, pointsOf, nameOf) {
76
+ const index = new Map();
77
+ for (const row of rows) {
78
+ const who = (nameOf(row) ?? '').trim();
79
+ if (!who) {
80
+ continue;
81
+ }
82
+ for (const raw of pointsOf(row) ?? []) {
83
+ const target = (raw ?? '').trim();
84
+ if (!target) {
85
+ continue;
86
+ }
87
+ const list = index.get(target) ?? [];
88
+ if (!list.includes(who)) {
89
+ list.push(who);
90
+ }
91
+ index.set(target, list);
92
+ }
93
+ }
94
+ for (const list of index.values()) {
95
+ list.sort();
96
+ }
97
+ return index;
98
+ }
99
+ //# sourceMappingURL=referrers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"referrers.js","sourceRoot":"","sources":["../../../server/service/resource/referrers.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AAgBH,wCA8BC;AAQD,gCAIC;AAaD,kDAoCC;AAjGD;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,IAAgB;IAC7C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAA;IAEzC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACxC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAEnC,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;YACpB,SAAQ;QACV,CAAC;QAED,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;QAEpC;;;WAGG;QACH,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAChB,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC;IAED,yCAAyC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,EAAE,CAAA;IACb,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;GAKG;AACH,SAAgB,UAAU,CAAC,KAA2B,EAAE,KAAa;IACnE,MAAM,GAAG,GAAG,KAAK,IAAI,EAAE,CAAA;IAEvB,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,EAAE,CAAA;AAChF,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,mBAAmB,CACjC,IAAS,EACT,QAAmD,EACnD,MAA6C;IAE7C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAA;IAEzC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAEtC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,SAAQ;QACV,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YAEjC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,SAAQ;YACV,CAAC;YAED,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;YAEpC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YAChB,CAAC;YAED,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,EAAE,CAAA;IACb,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["/**\n * **나를 가리키는 것들** — 들어오는 관계를 세는 공통 부분.\n *\n * ── 왜 필요한가 ────────────────────────────────────────────────────────────\n * 마스터의 관계는 전부 「남의 표 칸에 든 이름」 한 가지 모양이다. 커널이 이름으로 주소를 잡기\n * 때문이다. 그래서 나가는 쪽은 그 칸을 그대로 보이면 되는데, **들어오는 쪽은 아무도 안 세었다.**\n *\n * 자리 하나를 열어도 「여기서 누가 일하나 · 무슨 설비가 있나」를 알 수 없었다. 사람 화면을 열어\n * 근무 자리로 거르는 수밖에 없었고, 설비는 또 설비 화면에서 같은 일을 해야 했다.\n *\n * ── 줄마다 세지 않는다 ─────────────────────────────────────────────────────\n * 필드 리졸버는 줄마다 불린다. 거기서 그때그때 세면 `limit 20` 에 질의 20회다 — N+1 이다.\n * 그래서 **페이지의 이름들을 한 번에** 묻고 그 답을 요청 안에 담는다. 담는 것은 결과가 아니라\n * **약속**이다 — 줄들이 거의 동시에 묻기 때문에, 결과를 담으면 스무 개가 각자 질의를 띄운다.\n *\n * `childCount` 가 같은 방식을 쓴다(`location-master-resolver.ts`). 여기서는 그 방식을 축 여럿이\n * 나눠 쓸 수 있게 뽑아 두었다.\n */\n\n/** 한 줄이 무엇을 가리키나 — 가리키는 이름과, 그 줄 자신의 이름. */\nexport interface Referrer {\n /** 이 줄이 가리키는 대상의 이름. 비었으면 아무도 안 가리킨다. */\n points?: string | null\n /** 이 줄의 이름. 사람이 목록에서 알아보는 값이다. */\n name?: string | null\n}\n\n/**\n * 대상 이름 → 그것을 가리키는 줄들의 이름.\n *\n * **빈 값은 버린다.** 가리키는 곳이 없는 줄과 이름 없는 줄은 관계가 아니다. 그것을 빈 문자열\n * 키로 모으면 「아무 데도 안 놓인 것들」이 한 자리에 뭉쳐 그럴듯한 관계처럼 보인다.\n */\nexport function indexReferrers(rows: Referrer[]): Map<string, string[]> {\n const index = new Map<string, string[]>()\n\n for (const row of rows) {\n const target = (row.points ?? '').trim()\n const who = (row.name ?? '').trim()\n\n if (!target || !who) {\n continue\n }\n\n const list = index.get(target) ?? []\n\n /*\n * 같은 줄이 두 번 들어오면 한 번만 센다. 한 줄이 같은 대상을 두 칸에서 가리키는 축이 생길 수\n * 있고(자리와 예비 자리처럼), 그때 개수가 부풀면 화면이 「둘이 있다」고 말한다.\n */\n if (!list.includes(who)) {\n list.push(who)\n }\n\n index.set(target, list)\n }\n\n /* 목록의 순서를 정한다 — 같은 자료에 같은 화면이 나와야 한다. */\n for (const list of index.values()) {\n list.sort()\n }\n\n return index\n}\n\n/**\n * 화면에 보일 만큼만 자르고 **몇이 남았는지 함께 낸다.**\n *\n * 조용히 자르면 「여기 셋이 있다」와 「여기 서른이 있는데 셋만 보인다」가 같아 보인다. 자른 수를\n * 말하지 않으면 그 칸을 보고 사람이 판단을 내린다.\n */\nexport function cappedList(names: string[] | undefined, limit: number): { shown: string[]; hidden: number } {\n const all = names ?? []\n\n return { shown: all.slice(0, limit), hidden: Math.max(0, all.length - limit) }\n}\n\n/**\n * **배열 칸을 가리키는 것들.**\n *\n * `steps` 나 `materialClassIds` 처럼 한 줄이 여럿을 가리키는 칸이 있다. 이런 칸은 `IN` 으로 못\n * 좁힌다 — 저장이 `simple-json` 이라 데이터베이스가 그 안을 못 본다. 그래서 표를 훑어 여기서\n * 뒤집는다.\n *\n * **훑는 데는 반드시 상한이 있어야 하고, 상한에 걸렸으면 말해야 한다.** 조용히 자르면 「이 공정을\n * 아무도 안 쓴다」와 「너무 많아 다 못 봤다」가 같아 보인다. 앞엣것은 지워도 되는 공정이고\n * 뒤엣것은 지우면 라인이 선다.\n */\nexport function indexMultiReferrers<T>(\n rows: T[],\n pointsOf: (row: T) => (string | null | undefined)[],\n nameOf: (row: T) => string | null | undefined\n): Map<string, string[]> {\n const index = new Map<string, string[]>()\n\n for (const row of rows) {\n const who = (nameOf(row) ?? '').trim()\n\n if (!who) {\n continue\n }\n\n for (const raw of pointsOf(row) ?? []) {\n const target = (raw ?? '').trim()\n\n if (!target) {\n continue\n }\n\n const list = index.get(target) ?? []\n\n if (!list.includes(who)) {\n list.push(who)\n }\n\n index.set(target, list)\n }\n }\n\n for (const list of index.values()) {\n list.sort()\n }\n\n return index\n}\n"]}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * 검사 기준이 **판정에 쓸 수 있는 모양인가** — 순수 판단.
3
+ *
4
+ * ── 왜 저장하기 전에 보나 ──────────────────────────────────────────────────
5
+ * 기준을 저장하는 자리와 그 기준으로 판정하는 자리가 다르다. 판정하는 쪽(`measurement-judge`)은
6
+ * **모르는 것을 통과로 읽지 않도록** 만들어져 있어서, 망가진 기준이 들어와도 오류를 내지 않고 조용히
7
+ * 「판정하지 않음」이 된다. 그 조용함이 여기서는 위험하다 — 기준을 적은 사람은 적었다고 믿고,
8
+ * 검사원은 통과했다고 읽는다.
9
+ *
10
+ * 세 가지가 특히 조용하다.
11
+ *
12
+ * 한계가 숫자가 아니다 그리드 칸에서 온 `"39.7"` 은 글자다. `값 < "39.7"` 은 견주기가 되긴 하지만
13
+ * `"약 40"` 같은 것이 들어오면 어느 쪽 비교도 참이 아니어서 **모든 값이
14
+ * 한계 안**으로 읽힌다. 그 기준은 아무것도 막지 않는다.
15
+ * 한계에 단위가 없다 관측이 단위를 말해도 견줄 단위가 없으면 단위 검사를 건너뛴다. 화씨 관측이
16
+ * 섭씨 한계와 견주어지고 아무도 모른다.
17
+ * 기준 이름이 비었다 잰 값은 이름으로 기준을 찾는다. 이름이 없으면 영원히 「안 쟀음」이 된다.
18
+ *
19
+ * ── 막지 않는 것 ───────────────────────────────────────────────────────────
20
+ * **숫자 한계가 없는 기준은 정상이다.** 「표면에 흠이 없을 것」처럼 사람이 보고 판단하는 기준이 있고,
21
+ * 그때는 `expression` 만 있다. 그것까지 막으면 현장의 사실을 담을 수 없다.
22
+ *
23
+ * 다만 `limit` 을 **적다 만 것**과 애초에 안 적은 것은 다르다. 앞은 막고 뒤는 통과시킨다.
24
+ */
25
+ /** 저장하려는 기준 하나 — 무엇이 들어올지 모르므로 열어 둔다. */
26
+ export type CriterionInput = Record<string, unknown>;
27
+ export interface CriteriaVerdict {
28
+ ok: boolean;
29
+ /** 무엇이 왜 막혔는지. 한 줄에 하나씩, 고칠 자리를 가리킨다. */
30
+ problems: string[];
31
+ }
32
+ export declare function judgeCriteria(criteria: readonly CriterionInput[]): CriteriaVerdict;
33
+ export declare function criteriaRejected(verdict: CriteriaVerdict): boolean;
34
+ /**
35
+ * 그리드가 보낸 값을 기준 목록으로 읽는다.
36
+ *
37
+ * 화면은 JSON 글자로 보내고 저장 칸은 배열이다. **읽지 못한 것을 빈 목록으로 바꾸지 않는다** —
38
+ * 그러면 사람이 적은 기준이 통째로 사라지고 명세는 기준 없는 명세가 된다.
39
+ */
40
+ /**
41
+ * 읽음 · 못 읽음을 **글자로** 가른다.
42
+ *
43
+ * `ok: boolean` 으로 두면 이 저장소의 설정(`strict: false`)에서 `if (!read.ok)` 가 갈래를 좁히지 못해
44
+ * 못 읽은 쪽의 칸을 꺼낼 수 없다. 글자 표식은 어느 설정에서나 좁혀진다.
45
+ */
46
+ export type CriteriaRead = {
47
+ read: 'ok';
48
+ criteria: CriterionInput[];
49
+ } | {
50
+ read: 'failed';
51
+ detail: string;
52
+ };
53
+ export declare function readCriteria(value: unknown): CriteriaRead;