@things-factory/ops-master 10.1.33 → 10.1.35

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 (150) hide show
  1. package/dist-client/index.d.ts +1 -0
  2. package/dist-client/index.js +1 -0
  3. package/dist-client/index.js.map +1 -1
  4. package/dist-client/master-change-approval.d.ts +14 -0
  5. package/dist-client/master-change-approval.js +36 -0
  6. package/dist-client/master-change-approval.js.map +1 -0
  7. package/dist-client/pages/master/material-definition.d.ts +4 -11
  8. package/dist-client/pages/master/material-definition.js +5 -53
  9. package/dist-client/pages/master/material-definition.js.map +1 -1
  10. package/dist-client/pages/master/material-revision-fields.d.ts +2 -0
  11. package/dist-client/pages/master/material-revision-fields.js +43 -0
  12. package/dist-client/pages/master/material-revision-fields.js.map +1 -0
  13. package/dist-client/pages/master/recipe-definition.d.ts +4 -135
  14. package/dist-client/pages/master/recipe-definition.js +7 -397
  15. package/dist-client/pages/master/recipe-definition.js.map +1 -1
  16. package/dist-client/pages/master/revision-comparison.d.ts +48 -0
  17. package/dist-client/pages/master/revision-comparison.js +553 -0
  18. package/dist-client/pages/master/revision-comparison.js.map +1 -0
  19. package/dist-client/pages/master/revision-view.d.ts +19 -0
  20. package/dist-client/pages/master/revision-view.js +133 -0
  21. package/dist-client/pages/master/revision-view.js.map +1 -0
  22. package/dist-client/pages/master/revision-workspace.d.ts +50 -0
  23. package/dist-client/pages/master/revision-workspace.js +752 -0
  24. package/dist-client/pages/master/revision-workspace.js.map +1 -0
  25. package/dist-client/pages/master/test-specification.d.ts +4 -92
  26. package/dist-client/pages/master/test-specification.js +5 -440
  27. package/dist-client/pages/master/test-specification.js.map +1 -1
  28. package/dist-client/tsconfig.tsbuildinfo +1 -1
  29. package/dist-server/service/asset/asset-master-resolver.js +38 -34
  30. package/dist-server/service/asset/asset-master-resolver.js.map +1 -1
  31. package/dist-server/service/asset/index.d.ts +2 -2
  32. package/dist-server/service/equipment/equipment-master-resolver.js +54 -49
  33. package/dist-server/service/equipment/equipment-master-resolver.js.map +1 -1
  34. package/dist-server/service/equipment/index.d.ts +2 -2
  35. package/dist-server/service/index.d.ts +18 -2
  36. package/dist-server/service/index.js +18 -0
  37. package/dist-server/service/index.js.map +1 -1
  38. package/dist-server/service/location/index.d.ts +1 -1
  39. package/dist-server/service/location/location-master-resolver.js +34 -31
  40. package/dist-server/service/location/location-master-resolver.js.map +1 -1
  41. package/dist-server/service/master-axis.js +1 -1
  42. package/dist-server/service/master-axis.js.map +1 -1
  43. package/dist-server/service/master-change-policy.d.ts +23 -0
  44. package/dist-server/service/master-change-policy.js +134 -0
  45. package/dist-server/service/master-change-policy.js.map +1 -0
  46. package/dist-server/service/master-conformance-resolver.js +11 -1
  47. package/dist-server/service/master-conformance-resolver.js.map +1 -1
  48. package/dist-server/service/master-crud.js +9 -0
  49. package/dist-server/service/master-crud.js.map +1 -1
  50. package/dist-server/service/master-list.js +6 -0
  51. package/dist-server/service/master-list.js.map +1 -1
  52. package/dist-server/service/material/index.d.ts +3 -2
  53. package/dist-server/service/material/index.js +4 -2
  54. package/dist-server/service/material/index.js.map +1 -1
  55. package/dist-server/service/material/material-axis-status.d.ts +8 -0
  56. package/dist-server/service/material/material-axis-status.js +19 -0
  57. package/dist-server/service/material/material-axis-status.js.map +1 -0
  58. package/dist-server/service/material/material-definition.d.ts +3 -0
  59. package/dist-server/service/material/material-definition.js +13 -0
  60. package/dist-server/service/material/material-definition.js.map +1 -1
  61. package/dist-server/service/material/material-effective-list.d.ts +39 -0
  62. package/dist-server/service/material/material-effective-list.js +54 -0
  63. package/dist-server/service/material/material-effective-list.js.map +1 -0
  64. package/dist-server/service/material/material-master-resolver.js +59 -61
  65. package/dist-server/service/material/material-master-resolver.js.map +1 -1
  66. package/dist-server/service/material/material-query.js +9 -2
  67. package/dist-server/service/material/material-query.js.map +1 -1
  68. package/dist-server/service/material/material-revision-content.d.ts +10 -0
  69. package/dist-server/service/material/material-revision-content.js +92 -0
  70. package/dist-server/service/material/material-revision-content.js.map +1 -0
  71. package/dist-server/service/material/material-revision-resolver.d.ts +54 -0
  72. package/dist-server/service/material/material-revision-resolver.js +76 -0
  73. package/dist-server/service/material/material-revision-resolver.js.map +1 -0
  74. package/dist-server/service/material/material-revision-service.d.ts +144 -0
  75. package/dist-server/service/material/material-revision-service.js +534 -0
  76. package/dist-server/service/material/material-revision-service.js.map +1 -0
  77. package/dist-server/service/material/material-revision.d.ts +84 -0
  78. package/dist-server/service/material/material-revision.js +339 -0
  79. package/dist-server/service/material/material-revision.js.map +1 -0
  80. package/dist-server/service/operation/index.d.ts +1 -1
  81. package/dist-server/service/operation/operation-definition.js +1 -0
  82. package/dist-server/service/operation/operation-definition.js.map +1 -1
  83. package/dist-server/service/operation/operation-master-resolver.js +30 -30
  84. package/dist-server/service/operation/operation-master-resolver.js.map +1 -1
  85. package/dist-server/service/personnel/index.d.ts +2 -2
  86. package/dist-server/service/personnel/personnel-master-resolver.js +38 -34
  87. package/dist-server/service/personnel/personnel-master-resolver.js.map +1 -1
  88. package/dist-server/service/quality/nonconformance-cause-resolver.js +17 -14
  89. package/dist-server/service/quality/nonconformance-cause-resolver.js.map +1 -1
  90. package/dist-server/service/recipe/index.d.ts +3 -2
  91. package/dist-server/service/recipe/index.js +4 -2
  92. package/dist-server/service/recipe/index.js.map +1 -1
  93. package/dist-server/service/recipe/recipe-definition.d.ts +4 -0
  94. package/dist-server/service/recipe/recipe-definition.js +14 -1
  95. package/dist-server/service/recipe/recipe-definition.js.map +1 -1
  96. package/dist-server/service/recipe/recipe-effective-list.d.ts +49 -0
  97. package/dist-server/service/recipe/recipe-effective-list.js +96 -0
  98. package/dist-server/service/recipe/recipe-effective-list.js.map +1 -0
  99. package/dist-server/service/recipe/recipe-master-resolver.js +32 -28
  100. package/dist-server/service/recipe/recipe-master-resolver.js.map +1 -1
  101. package/dist-server/service/recipe/recipe-query.js +9 -2
  102. package/dist-server/service/recipe/recipe-query.js.map +1 -1
  103. package/dist-server/service/recipe/recipe-revision-content.d.ts +32 -0
  104. package/dist-server/service/recipe/recipe-revision-content.js +65 -0
  105. package/dist-server/service/recipe/recipe-revision-content.js.map +1 -0
  106. package/dist-server/service/recipe/recipe-revision-line.d.ts +30 -0
  107. package/dist-server/service/recipe/recipe-revision-line.js +95 -0
  108. package/dist-server/service/recipe/recipe-revision-line.js.map +1 -0
  109. package/dist-server/service/recipe/recipe-revision-resolver.d.ts +59 -0
  110. package/dist-server/service/recipe/recipe-revision-resolver.js +76 -0
  111. package/dist-server/service/recipe/recipe-revision-resolver.js.map +1 -0
  112. package/dist-server/service/recipe/recipe-revision-service.d.ts +147 -0
  113. package/dist-server/service/recipe/recipe-revision-service.js +591 -0
  114. package/dist-server/service/recipe/recipe-revision-service.js.map +1 -0
  115. package/dist-server/service/recipe/recipe-revision.d.ts +100 -0
  116. package/dist-server/service/recipe/recipe-revision.js +373 -0
  117. package/dist-server/service/recipe/recipe-revision.js.map +1 -0
  118. package/dist-server/service/recipe/route-definition.js +1 -0
  119. package/dist-server/service/recipe/route-definition.js.map +1 -1
  120. package/dist-server/service/resource/class-members.js +9 -0
  121. package/dist-server/service/resource/class-members.js.map +1 -1
  122. package/dist-server/service/status/master-status.js +2 -1
  123. package/dist-server/service/status/master-status.js.map +1 -1
  124. package/dist-server/service/test-spec/index.d.ts +3 -2
  125. package/dist-server/service/test-spec/index.js +4 -2
  126. package/dist-server/service/test-spec/index.js.map +1 -1
  127. package/dist-server/service/test-spec/test-spec-effective-list.d.ts +34 -0
  128. package/dist-server/service/test-spec/test-spec-effective-list.js +42 -0
  129. package/dist-server/service/test-spec/test-spec-effective-list.js.map +1 -0
  130. package/dist-server/service/test-spec/test-spec-master-resolver.d.ts +4 -14
  131. package/dist-server/service/test-spec/test-spec-master-resolver.js +12 -83
  132. package/dist-server/service/test-spec/test-spec-master-resolver.js.map +1 -1
  133. package/dist-server/service/test-spec/test-spec-query.js +7 -1
  134. package/dist-server/service/test-spec/test-spec-query.js.map +1 -1
  135. package/dist-server/service/test-spec/test-spec-revision-resolver.d.ts +53 -0
  136. package/dist-server/service/test-spec/test-spec-revision-resolver.js +76 -0
  137. package/dist-server/service/test-spec/test-spec-revision-resolver.js.map +1 -0
  138. package/dist-server/service/test-spec/test-spec-revision-service.d.ts +138 -0
  139. package/dist-server/service/test-spec/test-spec-revision-service.js +538 -0
  140. package/dist-server/service/test-spec/test-spec-revision-service.js.map +1 -0
  141. package/dist-server/service/test-spec/test-spec-revision.d.ts +84 -0
  142. package/dist-server/service/test-spec/test-spec-revision.js +334 -0
  143. package/dist-server/service/test-spec/test-spec-revision.js.map +1 -0
  144. package/dist-server/service/test-spec/test-specification.d.ts +6 -0
  145. package/dist-server/service/test-spec/test-specification.js +15 -2
  146. package/dist-server/service/test-spec/test-specification.js.map +1 -1
  147. package/dist-server/tsconfig.tsbuildinfo +1 -1
  148. package/package.json +10 -6
  149. package/translations/en.json +148 -0
  150. package/translations/ko.json +148 -0
@@ -4,12 +4,15 @@ exports.MaterialDefinitionResolver = exports.MaterialDefinitionList = exports.Ma
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 type_graphql_2 = require("type-graphql");
8
+ const shell_2 = require("@things-factory/shell");
7
9
  const master_crud_1 = require("../master-crud");
8
10
  const master_axis_1 = require("../master-axis");
9
11
  const master_list_1 = require("../master-list");
10
12
  const class_members_1 = require("../resource/class-members");
11
13
  const material_class_1 = require("./material-class");
12
14
  const recipe_definition_1 = require("../recipe/recipe-definition");
15
+ const recipe_effective_list_1 = require("../recipe/recipe-effective-list");
13
16
  const recipe_index_1 = require("./recipe-index");
14
17
  const class_lineage_1 = require("../resource/class-lineage");
15
18
  const material_definition_1 = require("./material-definition");
@@ -27,15 +30,15 @@ let MaterialClassList = class MaterialClassList {
27
30
  };
28
31
  exports.MaterialClassList = MaterialClassList;
29
32
  tslib_1.__decorate([
30
- (0, type_graphql_1.Field)(type => [material_class_1.MaterialClass]),
33
+ (0, type_graphql_2.Field)(type => [material_class_1.MaterialClass]),
31
34
  tslib_1.__metadata("design:type", Array)
32
35
  ], MaterialClassList.prototype, "items", void 0);
33
36
  tslib_1.__decorate([
34
- (0, type_graphql_1.Field)(type => type_graphql_1.Int),
37
+ (0, type_graphql_2.Field)(type => type_graphql_2.Int),
35
38
  tslib_1.__metadata("design:type", Number)
36
39
  ], MaterialClassList.prototype, "total", void 0);
37
40
  exports.MaterialClassList = MaterialClassList = tslib_1.__decorate([
38
- (0, type_graphql_1.ObjectType)({ description: 'A page of material classes' })
41
+ (0, type_graphql_2.ObjectType)({ description: 'A page of material classes' })
39
42
  ], MaterialClassList);
40
43
  /**
41
44
  * **이 등급에 무엇이 드나** — 들어오는 관계.
@@ -87,56 +90,57 @@ let MaterialClassResolver = class MaterialClassResolver {
87
90
  };
88
91
  exports.MaterialClassResolver = MaterialClassResolver;
89
92
  tslib_1.__decorate([
90
- (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
91
- (0, type_graphql_1.Query)(returns => MaterialClassList, { description: 'To fetch material classes' }),
92
- tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
93
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
93
+ (0, type_graphql_2.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
94
+ (0, type_graphql_2.Query)(returns => MaterialClassList, { description: 'To fetch material classes' }),
95
+ tslib_1.__param(0, (0, type_graphql_2.Args)(type => shell_2.ListParam)),
96
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
94
97
  tslib_1.__metadata("design:type", Function),
95
- tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
98
+ tslib_1.__metadata("design:paramtypes", [shell_2.ListParam, Object]),
96
99
  tslib_1.__metadata("design:returntype", Promise)
97
100
  ], MaterialClassResolver.prototype, "materialClasses", null);
98
101
  tslib_1.__decorate([
99
- (0, type_graphql_1.FieldResolver)(type => [String], { description: 'Materials belonging to this class' }),
100
- tslib_1.__param(0, (0, type_graphql_1.Root)()),
101
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
102
+ (0, type_graphql_2.FieldResolver)(type => [String], { description: 'Materials belonging to this class' }),
103
+ tslib_1.__param(0, (0, type_graphql_2.Root)()),
104
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
102
105
  tslib_1.__metadata("design:type", Function),
103
106
  tslib_1.__metadata("design:paramtypes", [material_class_1.MaterialClass, Object]),
104
107
  tslib_1.__metadata("design:returntype", Promise)
105
108
  ], MaterialClassResolver.prototype, "members", null);
106
109
  tslib_1.__decorate([
107
- (0, type_graphql_1.FieldResolver)(type => Boolean, { description: 'True when members were too many to scan fully' }),
108
- tslib_1.__param(0, (0, type_graphql_1.Root)()),
109
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
110
+ (0, type_graphql_2.FieldResolver)(type => Boolean, { description: 'True when members were too many to scan fully' }),
111
+ tslib_1.__param(0, (0, type_graphql_2.Root)()),
112
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
110
113
  tslib_1.__metadata("design:type", Function),
111
114
  tslib_1.__metadata("design:paramtypes", [material_class_1.MaterialClass, Object]),
112
115
  tslib_1.__metadata("design:returntype", Promise)
113
116
  ], MaterialClassResolver.prototype, "memberScanCapped", null);
114
117
  tslib_1.__decorate([
115
- (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
116
- (0, type_graphql_1.Directive)('@transaction'),
117
- (0, type_graphql_1.Mutation)(returns => [material_class_1.MaterialClass], { description: 'To create, change or retire material classes' }),
118
- tslib_1.__param(0, (0, type_graphql_1.Arg)('patches', type => [shell_1.ScalarObject])),
119
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
118
+ (0, type_graphql_2.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
119
+ (0, type_graphql_2.Directive)('@transaction'),
120
+ (0, type_graphql_2.Mutation)(returns => [material_class_1.MaterialClass], { description: 'To create, change or retire material classes' }),
121
+ tslib_1.__param(0, (0, type_graphql_2.Arg)('patches', type => [shell_2.ScalarObject])),
122
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
120
123
  tslib_1.__metadata("design:type", Function),
121
124
  tslib_1.__metadata("design:paramtypes", [Array, Object]),
122
125
  tslib_1.__metadata("design:returntype", Promise)
123
126
  ], MaterialClassResolver.prototype, "updateMultipleMaterialClass", null);
124
127
  exports.MaterialClassResolver = MaterialClassResolver = tslib_1.__decorate([
125
- (0, type_graphql_1.Resolver)(material_class_1.MaterialClass)
128
+ (0, type_graphql_2.Resolver)(material_class_1.MaterialClass),
129
+ (0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
126
130
  ], MaterialClassResolver);
127
131
  let MaterialDefinitionList = class MaterialDefinitionList {
128
132
  };
129
133
  exports.MaterialDefinitionList = MaterialDefinitionList;
130
134
  tslib_1.__decorate([
131
- (0, type_graphql_1.Field)(type => [material_definition_1.MaterialDefinition]),
135
+ (0, type_graphql_2.Field)(type => [material_definition_1.MaterialDefinition]),
132
136
  tslib_1.__metadata("design:type", Array)
133
137
  ], MaterialDefinitionList.prototype, "items", void 0);
134
138
  tslib_1.__decorate([
135
- (0, type_graphql_1.Field)(type => type_graphql_1.Int),
139
+ (0, type_graphql_2.Field)(type => type_graphql_2.Int),
136
140
  tslib_1.__metadata("design:type", Number)
137
141
  ], MaterialDefinitionList.prototype, "total", void 0);
138
142
  exports.MaterialDefinitionList = MaterialDefinitionList = tslib_1.__decorate([
139
- (0, type_graphql_1.ObjectType)({ description: 'A page of material definitions' })
143
+ (0, type_graphql_2.ObjectType)({ description: 'A page of material definitions' })
140
144
  ], MaterialDefinitionList);
141
145
  /**
142
146
  * 이 도메인의 제조법을 **한 요청에 한 번만** 읽는다.
@@ -158,17 +162,10 @@ exports.MaterialDefinitionList = MaterialDefinitionList = tslib_1.__decorate([
158
162
  */
159
163
  const RECIPE_SCAN_LIMIT = 2000;
160
164
  async function scanRecipes(domain) {
161
- const repo = (0, shell_1.getRepository)(recipe_definition_1.RecipeDefinition);
162
- const total = await repo.count({ where: { domain: { id: domain.id } } });
163
- const rows = await repo.find({
164
- where: { domain: { id: domain.id } },
165
- /*
166
- * `id` 를 함께 뽑는다. 빼면 TypeORM 이 「no such column: distinctAlias.RecipeDefinition_id」로
167
- * 터진다 — `take` 를 쓰면 중복 제거를 위해 기본 키로 한 번 더 감싸는데, 그 키를 안 뽑으면
168
- * 감쌀 것이 없다. [확인함] 2026-09-04
169
- */
170
- select: ['id', 'name', 'materialDefinitionName', 'inputs'],
171
- take: RECIPE_SCAN_LIMIT
165
+ const repo = (0, shell_2.getRepository)(recipe_definition_1.RecipeDefinition);
166
+ const { items: rows, total } = await (0, recipe_effective_list_1.listEffectiveRecipes)(repo.manager, domain, {
167
+ filters: [{ name: 'materialDefinitionName', operator: 'is_not_null', value: true }],
168
+ pagination: { page: 1, limit: RECIPE_SCAN_LIMIT }
172
169
  });
173
170
  const { makes, uses } = (0, recipe_index_1.indexRecipes)(rows);
174
171
  return { makes, uses, capped: total > rows.length };
@@ -180,7 +177,7 @@ async function scanRecipes(domain) {
180
177
  * 두면 계보가 중간에서 끊긴 채 짧은 답이 나오고, 그것은 「뿌리가 여기다」와 구별되지 않는다.
181
178
  */
182
179
  async function scanMaterialClasses(domain) {
183
- const rows = await (0, shell_1.getRepository)(material_class_1.MaterialClass).find({
180
+ const rows = await (0, shell_2.getRepository)(material_class_1.MaterialClass).find({
184
181
  where: { domain: { id: domain.id } },
185
182
  select: ['id', 'name', 'baseIds']
186
183
  });
@@ -289,65 +286,66 @@ let MaterialDefinitionResolver = class MaterialDefinitionResolver {
289
286
  };
290
287
  exports.MaterialDefinitionResolver = MaterialDefinitionResolver;
291
288
  tslib_1.__decorate([
292
- (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
293
- (0, type_graphql_1.Query)(returns => MaterialDefinitionList, { description: 'To fetch material definitions' }),
294
- tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
295
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
289
+ (0, type_graphql_2.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
290
+ (0, type_graphql_2.Query)(returns => MaterialDefinitionList, { description: 'To fetch material definitions' }),
291
+ tslib_1.__param(0, (0, type_graphql_2.Args)(type => shell_2.ListParam)),
292
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
296
293
  tslib_1.__metadata("design:type", Function),
297
- tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
294
+ tslib_1.__metadata("design:paramtypes", [shell_2.ListParam, Object]),
298
295
  tslib_1.__metadata("design:returntype", Promise)
299
296
  ], MaterialDefinitionResolver.prototype, "materialDefinitions", null);
300
297
  tslib_1.__decorate([
301
- (0, type_graphql_1.FieldResolver)(type => [String], { description: 'Recipes that produce this material' }),
302
- tslib_1.__param(0, (0, type_graphql_1.Root)()),
303
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
298
+ (0, type_graphql_2.FieldResolver)(type => [String], { description: 'Recipes that produce this material' }),
299
+ tslib_1.__param(0, (0, type_graphql_2.Root)()),
300
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
304
301
  tslib_1.__metadata("design:type", Function),
305
302
  tslib_1.__metadata("design:paramtypes", [material_definition_1.MaterialDefinition, Object]),
306
303
  tslib_1.__metadata("design:returntype", Promise)
307
304
  ], MaterialDefinitionResolver.prototype, "madeBy", null);
308
305
  tslib_1.__decorate([
309
- (0, type_graphql_1.FieldResolver)(type => [String], { description: 'Recipes that consume this material' }),
310
- tslib_1.__param(0, (0, type_graphql_1.Root)()),
311
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
306
+ (0, type_graphql_2.FieldResolver)(type => [String], { description: 'Recipes that consume this material' }),
307
+ tslib_1.__param(0, (0, type_graphql_2.Root)()),
308
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
312
309
  tslib_1.__metadata("design:type", Function),
313
310
  tslib_1.__metadata("design:paramtypes", [material_definition_1.MaterialDefinition, Object]),
314
311
  tslib_1.__metadata("design:returntype", Promise)
315
312
  ], MaterialDefinitionResolver.prototype, "usedIn", null);
316
313
  tslib_1.__decorate([
317
- (0, type_graphql_1.FieldResolver)(type => [String], { description: 'Root material classes this material belongs to' }),
318
- tslib_1.__param(0, (0, type_graphql_1.Root)()),
319
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
314
+ (0, type_graphql_2.FieldResolver)(type => [String], { description: 'Root material classes this material belongs to' }),
315
+ tslib_1.__param(0, (0, type_graphql_2.Root)()),
316
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
320
317
  tslib_1.__metadata("design:type", Function),
321
318
  tslib_1.__metadata("design:paramtypes", [material_definition_1.MaterialDefinition, Object]),
322
319
  tslib_1.__metadata("design:returntype", Promise)
323
320
  ], MaterialDefinitionResolver.prototype, "rootClasses", null);
324
321
  tslib_1.__decorate([
325
- (0, type_graphql_1.FieldResolver)(type => [String], { description: 'Class lineage from root to own class' }),
326
- tslib_1.__param(0, (0, type_graphql_1.Root)()),
327
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
322
+ (0, type_graphql_2.FieldResolver)(type => [String], { description: 'Class lineage from root to own class' }),
323
+ tslib_1.__param(0, (0, type_graphql_2.Root)()),
324
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
328
325
  tslib_1.__metadata("design:type", Function),
329
326
  tslib_1.__metadata("design:paramtypes", [material_definition_1.MaterialDefinition, Object]),
330
327
  tslib_1.__metadata("design:returntype", Promise)
331
328
  ], MaterialDefinitionResolver.prototype, "classPath", null);
332
329
  tslib_1.__decorate([
333
- (0, type_graphql_1.FieldResolver)(type => Boolean, { description: 'True when recipes were too many to scan fully' }),
334
- tslib_1.__param(0, (0, type_graphql_1.Root)()),
335
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
330
+ (0, type_graphql_2.FieldResolver)(type => Boolean, { description: 'True when recipes were too many to scan fully' }),
331
+ tslib_1.__param(0, (0, type_graphql_2.Root)()),
332
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
336
333
  tslib_1.__metadata("design:type", Function),
337
334
  tslib_1.__metadata("design:paramtypes", [material_definition_1.MaterialDefinition, Object]),
338
335
  tslib_1.__metadata("design:returntype", Promise)
339
336
  ], MaterialDefinitionResolver.prototype, "recipeScanCapped", null);
340
337
  tslib_1.__decorate([
341
- (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
342
- (0, type_graphql_1.Directive)('@transaction'),
343
- (0, type_graphql_1.Mutation)(returns => [material_definition_1.MaterialDefinition], { description: 'To create, change or retire material definitions' }),
344
- tslib_1.__param(0, (0, type_graphql_1.Arg)('patches', type => [shell_1.ScalarObject])),
345
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
338
+ (0, type_graphql_2.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
339
+ (0, type_graphql_2.Directive)('@transaction'),
340
+ (0, type_graphql_2.Mutation)(returns => [material_definition_1.MaterialDefinition], { description: 'To create, change or retire material definitions' }),
341
+ tslib_1.__param(0, (0, type_graphql_2.Arg)('patches', type => [shell_2.ScalarObject])),
342
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
346
343
  tslib_1.__metadata("design:type", Function),
347
344
  tslib_1.__metadata("design:paramtypes", [Array, Object]),
348
345
  tslib_1.__metadata("design:returntype", Promise)
349
346
  ], MaterialDefinitionResolver.prototype, "updateMultipleMaterialDefinition", null);
350
347
  exports.MaterialDefinitionResolver = MaterialDefinitionResolver = tslib_1.__decorate([
351
- (0, type_graphql_1.Resolver)(material_definition_1.MaterialDefinition)
348
+ (0, type_graphql_2.Resolver)(material_definition_1.MaterialDefinition),
349
+ (0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
352
350
  ], MaterialDefinitionResolver);
353
351
  //# sourceMappingURL=material-master-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"material-master-resolver.js","sourceRoot":"","sources":["../../../server/service/material/material-master-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Args, Arg, Ctx, Directive, FieldResolver, Int, Mutation, ObjectType, Field, Query, Resolver, Root } from 'type-graphql'\n\nimport { Domain, ListParam, ScalarObject, getRepository } 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 { MaterialClass } from './material-class'\nimport { RecipeDefinition } from '../recipe/recipe-definition'\nimport { indexRecipes } from './recipe-index'\nimport { classPathOf, rootClassesOfMaterial } from '../resource/class-lineage'\nimport { MaterialDefinition } from './material-definition'\n\n/**\n * 자재 축을 채우고 고친다.\n *\n * ── 왜 마스터는 그리드에서 고칠 수 있나 ────────────────────────────────────\n * 업무 기록과 달리 마스터는 **선언**이다. 「이 사람이 용접 자격을 갖는다」는 일어난 일이 아니라 지금의\n * 규정이고, 규정은 바뀐다. 없앨 때 행을 지우지 않고 유효 기간의 끝을 채우는 것이 그래서다 —\n * 지우면 그것을 가리키는 지난 기록이 가리킬 것을 잃는다.\n *\n * 규칙은 `master-crud.ts` 한 곳에 있다. 여기는 축의 이름만 붙인다.\n */\n\n@ObjectType({ description: 'A page of material classes' })\nexport class MaterialClassList {\n @Field(type => [MaterialClass])\n items: MaterialClass[]\n\n @Field(type => Int)\n total: number\n}\n\n/**\n * **이 등급에 무엇이 드나** — 들어오는 관계.\n *\n * 등급을 만들어 놓고 아무도 안 들어 있는 일이 실제로 있었다. 화면에는 등급 이름만 있어서 그 사실이\n * 안 보였다. 없앨 때 무엇이 딸려 자격을 잃는지도 알 수 없었다.\n *\n * 세는 방식과 상한은 `class-members.ts` 머리말에 적었다 — 등급 넷이 같은 함수를 쓴다.\n */\nfunction materialClassMembersOf(context: ResolverContext): Promise<ClassMembers> {\n const state = context.state as { domain: Domain; materialClassMembers?: Promise<ClassMembers> }\n\n state.materialClassMembers = state.materialClassMembers || scanClassMembers(MaterialDefinition, 'materialClassIds', state.domain)\n\n return state.materialClassMembers\n}\n\n@Resolver(MaterialClass)\nexport class MaterialClassResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => MaterialClassList, { description: 'To fetch material classes' })\n async materialClasses(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<MaterialClassList> {\n const { domain } = context.state\n const { items, total } = await listMaster('materialClass', params, domain)\n\n return { items: items as MaterialClass[], total }\n }\n\n\n /**\n * 이 등급에 든 품목들\n *\n * 아무도 안 들면 빈 목록이다. 다만 그것이 「없다」인지 「다 못 봤다」인지는 `memberScanCapped`\n * 가 말한다 — 조용히 자르면 고쳐야 할 선언과 멀쩡한 등급이 같아 보인다.\n */\n @FieldResolver(type => [String], { description: 'Materials belonging to this class' })\n async members(@Root() klass: MaterialClass, @Ctx() context: ResolverContext): Promise<string[]> {\n return (await materialClassMembersOf(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: MaterialClass, @Ctx() context: ResolverContext): Promise<boolean> {\n return (await materialClassMembersOf(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 => [MaterialClass], { description: 'To create, change or retire material classes' })\n async updateMultipleMaterialClass(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<MaterialClass[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('materialClass')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as MaterialClass[]\n }\n}\n\n@ObjectType({ description: 'A page of material definitions' })\nexport class MaterialDefinitionList {\n @Field(type => [MaterialDefinition])\n items: MaterialDefinition[]\n\n @Field(type => Int)\n total: number\n}\n\n/**\n * 이 도메인의 제조법을 **한 요청에 한 번만** 읽는다.\n *\n * ── 왜 통째로 읽나 ─────────────────────────────────────────────────────────\n * 「이 품목이 어디에 쓰이나」는 제조법의 `inputs` 안을 봐야 안다. 그런데 그것은 JSON 문자열 칸이라\n * **다섯 드라이버에서 같은 방식으로 물을 수 없다.** 드라이버마다 다른 JSON 함수를 쓰면 그것이 곧\n * raw SQL 이고, 한 드라이버에서만 도는 코드가 된다.\n *\n * 그래서 제조법을 읽어 와 메모리에서 센다. 제조법은 품목보다 훨씬 적다 — 품목이 백만이어도 제조법은\n * 수백에서 수천이다.\n *\n * ── 그래도 무한하지는 않다 ─────────────────────────────────────────────────\n * 상한을 둔다. 넘으면 **본 만큼만 답하고 그 사실을 함께 낸다**(`usedInCapped`) — 조용히 잘라 내면\n * 「쓰이는 데가 없다」와 「못 봤다」가 같아 보인다.\n *\n * 제대로 된 답은 투입을 줄로 갖는 표(`RecipePart`)를 두고 품목으로 색인하는 것이다. 그때 이 함수는\n * 사라진다. 지금은 그 표가 없다.\n */\nconst RECIPE_SCAN_LIMIT = 2000\n\ninterface RecipeScan {\n /** 만드는 품목 → 제조법 이름들. */\n makes: Map<string, string[]>\n /** 투입 품목 → 제조법 이름들. */\n uses: Map<string, string[]>\n /** 상한에 걸려 다 보지 못했나. */\n capped: boolean\n}\n\nasync function scanRecipes(domain: Domain): Promise<RecipeScan> {\n const repo = getRepository(RecipeDefinition)\n const total = await repo.count({ where: { domain: { id: domain.id } } as any })\n const rows = await repo.find({\n where: { domain: { id: domain.id } } as any,\n /*\n * `id` 를 함께 뽑는다. 빼면 TypeORM 이 「no such column: distinctAlias.RecipeDefinition_id」로\n * 터진다 — `take` 를 쓰면 중복 제거를 위해 기본 키로 한 번 더 감싸는데, 그 키를 안 뽑으면\n * 감쌀 것이 없다. [확인함] 2026-09-04\n */\n select: ['id', 'name', 'materialDefinitionName', 'inputs'] as any,\n take: RECIPE_SCAN_LIMIT\n })\n\n const { makes, uses } = indexRecipes(rows)\n\n return { makes, uses, capped: total > rows.length }\n}\n\n/**\n * 등급 색인 — **이름 → 등급.** 상속을 걸으려면 등급 표 전체가 손에 있어야 한다.\n *\n * 등급은 품목과 달리 수가 적다(한 도메인에 수십 개). 그래서 상한을 두지 않고 다 읽는다 — 상한을\n * 두면 계보가 중간에서 끊긴 채 짧은 답이 나오고, 그것은 「뿌리가 여기다」와 구별되지 않는다.\n */\nasync function scanMaterialClasses(domain: Domain): Promise<Map<string, MaterialClass>> {\n const rows = await getRepository(MaterialClass).find({\n where: { domain: { id: domain.id } } as any,\n select: ['id', 'name', 'baseIds'] as any\n })\n\n return new Map(rows.map(one => [one.name, one]))\n}\n\n/** 요청 안에서 한 번만 읽는다 — 목록 한 화면이 이 칸을 스무 줄에서 묻는다. */\nfunction classIndexOf(context: ResolverContext): Promise<Map<string, MaterialClass>> {\n const state = context.state as { domain: Domain; materialClassIndex?: Promise<Map<string, MaterialClass>> }\n state.materialClassIndex = state.materialClassIndex || scanMaterialClasses(state.domain)\n\n return state.materialClassIndex\n}\n\n/** 요청 안에서 한 번만 훑는다. **약속을 담는다** — 줄들이 거의 동시에 묻는다. */\nfunction scanOf(context: ResolverContext): Promise<RecipeScan> {\n const state = context.state as { domain: Domain; recipeScan?: Promise<RecipeScan> }\n state.recipeScan = state.recipeScan || scanRecipes(state.domain)\n\n return state.recipeScan\n}\n\n@Resolver(MaterialDefinition)\nexport class MaterialDefinitionResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => MaterialDefinitionList, { description: 'To fetch material definitions' })\n async materialDefinitions(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<MaterialDefinitionList> {\n const { domain } = context.state\n const { items, total } = await listMaster('materialDefinition', params, domain)\n\n return { items: items as MaterialDefinition[], total }\n }\n\n /**\n * 이 품목을 **만드는** 제조법들.\n *\n * ── 왜 품목 화면에 이 칸이 있나 ──────────────────────────────────────────\n * BOM 은 제조법에 있는데 품목 화면에서 거기로 가는 문이 없었다. 완제품 한 줄을 보면서 「이 한 대에\n * 무엇이 들어가나」를 물을 수 없었다 — 제조법 화면을 따로 열고 이름으로 찾아야 했다.\n *\n * 여럿일 수 있다. 같은 품목을 다른 라우팅으로 만드는 일이 있다(라인 증설 · 시제품). 하나로 줄이면\n * 그중 하나가 조용히 사라진다.\n *\n * **묻는 요청만 훑는다** — GraphQL 이 이 칸을 물어본 조회에서만 이 리졸버가 불린다.\n */\n @FieldResolver(type => [String], { description: 'Recipes that produce this material' })\n async madeBy(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<string[]> {\n return (await scanOf(context)).makes.get(material.name) ?? []\n }\n\n /**\n * 이 품목이 **들어가는** 제조법들 — 역전개.\n *\n * 「이 나사가 어디에 쓰이나」의 답이다. 품목 하나를 바꾸거나 끊을 때 무엇이 영향을 받는지가 이\n * 칸에 있다.\n */\n @FieldResolver(type => [String], { description: 'Recipes that consume this material' })\n async usedIn(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<string[]> {\n return (await scanOf(context)).uses.get(material.name) ?? []\n }\n\n /**\n * 제조법을 다 보지 못했나.\n *\n * **조용히 자르지 않는다.** 상한에 걸리면 위의 두 칸이 덜 찬 답이고, 그것을 말하지 않으면\n * 「쓰이는 데가 없다」와 「못 봤다」가 같아 보인다.\n */\n /**\n * 이 품목이 어느 **뿌리 등급**에 드는가 — 「완제품인가 · 반제품인가 · 원재료인가」의 답.\n *\n * ── 왜 새 칸을 안 만들었나 ──────────────────────────────────────────────\n * ISA-95 `MaterialDefinitionType` 에는 그 구분을 담는 전용 요소가 없다. 표준이 그것을 두는 자리는\n * **등급**이고, 등급 표의 주석도 「원자재·완제품처럼 품목을 묶는 상위 개념」이라고 적고 있다.\n * 그래서 `materialType` 같은 칸을 새로 만들지 않고 이미 있는 `baseIds` 상속을 걷는다.\n *\n * 그 칸은 네 등급 표에 다 있었고 화면에서 편집도 됐고 트윈에도 나갔는데 **읽는 곳이 없었다.**\n *\n * 여럿일 수 있다. 품목이 등급 여럿에 들 수 있고(표준 `MaterialClassID` 가 복수다), 그 등급들의\n * 뿌리가 다를 수 있다. 하나로 줄이면 「완제품이면서 원재료」인 모순이 화면에서 사라진다.\n *\n * 등급이 하나도 없으면 빈 배열이다 — 「원재료」로 넘겨짚지 않는다.\n */\n @FieldResolver(type => [String], { description: 'Root material classes this material belongs to' })\n async rootClasses(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<string[]> {\n return rootClassesOfMaterial(material.materialClassIds, await classIndexOf(context))\n }\n\n /**\n * 등급 계보를 뿌리부터 자기까지 — `['원재료', 'ECU 기구부품']`.\n *\n * 목록에서 이 한 칸만 보면 「무엇인가」가 읽힌다. 등급 이름만 있으면 `MC-ECU-MECH` 가 원재료인지\n * 반제품인지 사람이 외우고 있어야 한다.\n *\n * **첫 갈래만 낸다** — 보여 주기 위한 칸이라 한 줄에 담아야 한다. 판정은 위의 `rootClasses` 로\n * 한다. 품목이 등급 여럿에 들면 첫 등급의 계보다.\n */\n @FieldResolver(type => [String], { description: 'Class lineage from root to own class' })\n async classPath(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<string[]> {\n const first = (material.materialClassIds ?? [])[0]\n\n if (!first) {\n return []\n }\n\n return classPathOf(first, await classIndexOf(context))\n }\n\n @FieldResolver(type => Boolean, { description: 'True when recipes were too many to scan fully' })\n async recipeScanCapped(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<boolean> {\n return (await scanOf(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 => [MaterialDefinition], { description: 'To create, change or retire material definitions' })\n async updateMultipleMaterialDefinition(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<MaterialDefinition[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('materialDefinition')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as MaterialDefinition[]\n }\n}\n"]}
1
+ {"version":3,"file":"material-master-resolver.js","sourceRoot":"","sources":["../../../server/service/material/material-master-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { UseMiddleware } from 'type-graphql'\nimport { SpeakRefusals } from '@things-factory/shell'\nimport { Args, Arg, Ctx, Directive, FieldResolver, Int, Mutation, ObjectType, Field, Query, Resolver, Root } from 'type-graphql'\n\nimport { Domain, ListParam, ScalarObject, getRepository } 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 { MaterialClass } from './material-class'\nimport { RecipeDefinition } from '../recipe/recipe-definition'\nimport { listEffectiveRecipes } from '../recipe/recipe-effective-list'\nimport { indexRecipes } from './recipe-index'\nimport { classPathOf, rootClassesOfMaterial } from '../resource/class-lineage'\nimport { MaterialDefinition } from './material-definition'\n\n/**\n * 자재 축을 채우고 고친다.\n *\n * ── 왜 마스터는 그리드에서 고칠 수 있나 ────────────────────────────────────\n * 업무 기록과 달리 마스터는 **선언**이다. 「이 사람이 용접 자격을 갖는다」는 일어난 일이 아니라 지금의\n * 규정이고, 규정은 바뀐다. 없앨 때 행을 지우지 않고 유효 기간의 끝을 채우는 것이 그래서다 —\n * 지우면 그것을 가리키는 지난 기록이 가리킬 것을 잃는다.\n *\n * 규칙은 `master-crud.ts` 한 곳에 있다. 여기는 축의 이름만 붙인다.\n */\n\n@ObjectType({ description: 'A page of material classes' })\nexport class MaterialClassList {\n @Field(type => [MaterialClass])\n items: MaterialClass[]\n\n @Field(type => Int)\n total: number\n}\n\n/**\n * **이 등급에 무엇이 드나** — 들어오는 관계.\n *\n * 등급을 만들어 놓고 아무도 안 들어 있는 일이 실제로 있었다. 화면에는 등급 이름만 있어서 그 사실이\n * 안 보였다. 없앨 때 무엇이 딸려 자격을 잃는지도 알 수 없었다.\n *\n * 세는 방식과 상한은 `class-members.ts` 머리말에 적었다 — 등급 넷이 같은 함수를 쓴다.\n */\nfunction materialClassMembersOf(context: ResolverContext): Promise<ClassMembers> {\n const state = context.state as { domain: Domain; materialClassMembers?: Promise<ClassMembers> }\n\n state.materialClassMembers = state.materialClassMembers || scanClassMembers(MaterialDefinition, 'materialClassIds', state.domain)\n\n return state.materialClassMembers\n}\n\n@Resolver(MaterialClass)\n@UseMiddleware(SpeakRefusals)\nexport class MaterialClassResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => MaterialClassList, { description: 'To fetch material classes' })\n async materialClasses(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<MaterialClassList> {\n const { domain } = context.state\n const { items, total } = await listMaster('materialClass', params, domain)\n\n return { items: items as MaterialClass[], total }\n }\n\n\n /**\n * 이 등급에 든 품목들\n *\n * 아무도 안 들면 빈 목록이다. 다만 그것이 「없다」인지 「다 못 봤다」인지는 `memberScanCapped`\n * 가 말한다 — 조용히 자르면 고쳐야 할 선언과 멀쩡한 등급이 같아 보인다.\n */\n @FieldResolver(type => [String], { description: 'Materials belonging to this class' })\n async members(@Root() klass: MaterialClass, @Ctx() context: ResolverContext): Promise<string[]> {\n return (await materialClassMembersOf(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: MaterialClass, @Ctx() context: ResolverContext): Promise<boolean> {\n return (await materialClassMembersOf(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 => [MaterialClass], { description: 'To create, change or retire material classes' })\n async updateMultipleMaterialClass(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<MaterialClass[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('materialClass')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as MaterialClass[]\n }\n}\n\n@ObjectType({ description: 'A page of material definitions' })\nexport class MaterialDefinitionList {\n @Field(type => [MaterialDefinition])\n items: MaterialDefinition[]\n\n @Field(type => Int)\n total: number\n}\n\n/**\n * 이 도메인의 제조법을 **한 요청에 한 번만** 읽는다.\n *\n * ── 왜 통째로 읽나 ─────────────────────────────────────────────────────────\n * 「이 품목이 어디에 쓰이나」는 제조법의 `inputs` 안을 봐야 안다. 그런데 그것은 JSON 문자열 칸이라\n * **다섯 드라이버에서 같은 방식으로 물을 수 없다.** 드라이버마다 다른 JSON 함수를 쓰면 그것이 곧\n * raw SQL 이고, 한 드라이버에서만 도는 코드가 된다.\n *\n * 그래서 제조법을 읽어 와 메모리에서 센다. 제조법은 품목보다 훨씬 적다 — 품목이 백만이어도 제조법은\n * 수백에서 수천이다.\n *\n * ── 그래도 무한하지는 않다 ─────────────────────────────────────────────────\n * 상한을 둔다. 넘으면 **본 만큼만 답하고 그 사실을 함께 낸다**(`usedInCapped`) — 조용히 잘라 내면\n * 「쓰이는 데가 없다」와 「못 봤다」가 같아 보인다.\n *\n * 제대로 된 답은 투입을 줄로 갖는 표(`RecipePart`)를 두고 품목으로 색인하는 것이다. 그때 이 함수는\n * 사라진다. 지금은 그 표가 없다.\n */\nconst RECIPE_SCAN_LIMIT = 2000\n\ninterface RecipeScan {\n /** 만드는 품목 → 제조법 이름들. */\n makes: Map<string, string[]>\n /** 투입 품목 → 제조법 이름들. */\n uses: Map<string, string[]>\n /** 상한에 걸려 다 보지 못했나. */\n capped: boolean\n}\n\nasync function scanRecipes(domain: Domain): Promise<RecipeScan> {\n const repo = getRepository(RecipeDefinition)\n const { items: rows, total } = await listEffectiveRecipes(repo.manager, domain, {\n filters: [{ name:'materialDefinitionName', operator:'is_not_null', value:true }],\n pagination: { page:1, limit:RECIPE_SCAN_LIMIT }\n } as any)\n\n const { makes, uses } = indexRecipes(rows)\n\n return { makes, uses, capped: total > rows.length }\n}\n\n/**\n * 등급 색인 — **이름 → 등급.** 상속을 걸으려면 등급 표 전체가 손에 있어야 한다.\n *\n * 등급은 품목과 달리 수가 적다(한 도메인에 수십 개). 그래서 상한을 두지 않고 다 읽는다 — 상한을\n * 두면 계보가 중간에서 끊긴 채 짧은 답이 나오고, 그것은 「뿌리가 여기다」와 구별되지 않는다.\n */\nasync function scanMaterialClasses(domain: Domain): Promise<Map<string, MaterialClass>> {\n const rows = await getRepository(MaterialClass).find({\n where: { domain: { id: domain.id } } as any,\n select: ['id', 'name', 'baseIds'] as any\n })\n\n return new Map(rows.map(one => [one.name, one]))\n}\n\n/** 요청 안에서 한 번만 읽는다 — 목록 한 화면이 이 칸을 스무 줄에서 묻는다. */\nfunction classIndexOf(context: ResolverContext): Promise<Map<string, MaterialClass>> {\n const state = context.state as { domain: Domain; materialClassIndex?: Promise<Map<string, MaterialClass>> }\n state.materialClassIndex = state.materialClassIndex || scanMaterialClasses(state.domain)\n\n return state.materialClassIndex\n}\n\n/** 요청 안에서 한 번만 훑는다. **약속을 담는다** — 줄들이 거의 동시에 묻는다. */\nfunction scanOf(context: ResolverContext): Promise<RecipeScan> {\n const state = context.state as { domain: Domain; recipeScan?: Promise<RecipeScan> }\n state.recipeScan = state.recipeScan || scanRecipes(state.domain)\n\n return state.recipeScan\n}\n\n@Resolver(MaterialDefinition)\n@UseMiddleware(SpeakRefusals)\nexport class MaterialDefinitionResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => MaterialDefinitionList, { description: 'To fetch material definitions' })\n async materialDefinitions(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<MaterialDefinitionList> {\n const { domain } = context.state\n const { items, total } = await listMaster('materialDefinition', params, domain)\n\n return { items: items as MaterialDefinition[], total }\n }\n\n /**\n * 이 품목을 **만드는** 제조법들.\n *\n * ── 왜 품목 화면에 이 칸이 있나 ──────────────────────────────────────────\n * BOM 은 제조법에 있는데 품목 화면에서 거기로 가는 문이 없었다. 완제품 한 줄을 보면서 「이 한 대에\n * 무엇이 들어가나」를 물을 수 없었다 — 제조법 화면을 따로 열고 이름으로 찾아야 했다.\n *\n * 여럿일 수 있다. 같은 품목을 다른 라우팅으로 만드는 일이 있다(라인 증설 · 시제품). 하나로 줄이면\n * 그중 하나가 조용히 사라진다.\n *\n * **묻는 요청만 훑는다** — GraphQL 이 이 칸을 물어본 조회에서만 이 리졸버가 불린다.\n */\n @FieldResolver(type => [String], { description: 'Recipes that produce this material' })\n async madeBy(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<string[]> {\n return (await scanOf(context)).makes.get(material.name) ?? []\n }\n\n /**\n * 이 품목이 **들어가는** 제조법들 — 역전개.\n *\n * 「이 나사가 어디에 쓰이나」의 답이다. 품목 하나를 바꾸거나 끊을 때 무엇이 영향을 받는지가 이\n * 칸에 있다.\n */\n @FieldResolver(type => [String], { description: 'Recipes that consume this material' })\n async usedIn(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<string[]> {\n return (await scanOf(context)).uses.get(material.name) ?? []\n }\n\n /**\n * 제조법을 다 보지 못했나.\n *\n * **조용히 자르지 않는다.** 상한에 걸리면 위의 두 칸이 덜 찬 답이고, 그것을 말하지 않으면\n * 「쓰이는 데가 없다」와 「못 봤다」가 같아 보인다.\n */\n /**\n * 이 품목이 어느 **뿌리 등급**에 드는가 — 「완제품인가 · 반제품인가 · 원재료인가」의 답.\n *\n * ── 왜 새 칸을 안 만들었나 ──────────────────────────────────────────────\n * ISA-95 `MaterialDefinitionType` 에는 그 구분을 담는 전용 요소가 없다. 표준이 그것을 두는 자리는\n * **등급**이고, 등급 표의 주석도 「원자재·완제품처럼 품목을 묶는 상위 개념」이라고 적고 있다.\n * 그래서 `materialType` 같은 칸을 새로 만들지 않고 이미 있는 `baseIds` 상속을 걷는다.\n *\n * 그 칸은 네 등급 표에 다 있었고 화면에서 편집도 됐고 트윈에도 나갔는데 **읽는 곳이 없었다.**\n *\n * 여럿일 수 있다. 품목이 등급 여럿에 들 수 있고(표준 `MaterialClassID` 가 복수다), 그 등급들의\n * 뿌리가 다를 수 있다. 하나로 줄이면 「완제품이면서 원재료」인 모순이 화면에서 사라진다.\n *\n * 등급이 하나도 없으면 빈 배열이다 — 「원재료」로 넘겨짚지 않는다.\n */\n @FieldResolver(type => [String], { description: 'Root material classes this material belongs to' })\n async rootClasses(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<string[]> {\n return rootClassesOfMaterial(material.materialClassIds, await classIndexOf(context))\n }\n\n /**\n * 등급 계보를 뿌리부터 자기까지 — `['원재료', 'ECU 기구부품']`.\n *\n * 목록에서 이 한 칸만 보면 「무엇인가」가 읽힌다. 등급 이름만 있으면 `MC-ECU-MECH` 가 원재료인지\n * 반제품인지 사람이 외우고 있어야 한다.\n *\n * **첫 갈래만 낸다** — 보여 주기 위한 칸이라 한 줄에 담아야 한다. 판정은 위의 `rootClasses` 로\n * 한다. 품목이 등급 여럿에 들면 첫 등급의 계보다.\n */\n @FieldResolver(type => [String], { description: 'Class lineage from root to own class' })\n async classPath(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<string[]> {\n const first = (material.materialClassIds ?? [])[0]\n\n if (!first) {\n return []\n }\n\n return classPathOf(first, await classIndexOf(context))\n }\n\n @FieldResolver(type => Boolean, { description: 'True when recipes were too many to scan fully' })\n async recipeScanCapped(@Root() material: MaterialDefinition, @Ctx() context: ResolverContext): Promise<boolean> {\n return (await scanOf(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 => [MaterialDefinition], { description: 'To create, change or retire material definitions' })\n async updateMultipleMaterialDefinition(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<MaterialDefinition[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('materialDefinition')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as MaterialDefinition[]\n }\n}\n"]}
@@ -8,6 +8,8 @@ const master_page_query_1 = require("../master-page-query");
8
8
  const material_class_1 = require("./material-class");
9
9
  const material_definition_1 = require("./material-definition");
10
10
  const material_master_1 = require("./material-master");
11
+ const material_effective_list_1 = require("./material-effective-list");
12
+ const master_page_1 = require("../master-page");
11
13
  let MaterialMaster = class MaterialMaster {
12
14
  };
13
15
  exports.MaterialMaster = MaterialMaster;
@@ -40,10 +42,15 @@ let MaterialQuery = class MaterialQuery {
40
42
  * 구성원이 먼저 도착하고 등급이 나중에 오는 순간이 생긴다 — 그 순간 받는 쪽은 없는 등급을
41
43
  * 가리키는 구성원을 갖는다. 등급은 수십 단위라 매 쪽에 실어도 크지 않다.
42
44
  */
43
- const [classes, page] = await Promise.all([
45
+ const [classes, effective] = await Promise.all([
44
46
  (0, shell_1.getRepository)(material_class_1.MaterialClass).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } }),
45
- (0, master_page_query_1.pullPage)(material_definition_1.MaterialDefinition, domain, request)
47
+ (0, material_effective_list_1.listEffectiveMaterialDefinitions)((0, shell_1.getRepository)(material_definition_1.MaterialDefinition).manager, domain, {
48
+ filters: [...(after ? [{ name: 'name', operator: 'gt', value: after }] : []),
49
+ { name: 'revisionNumber', operator: 'is_not_null', value: true }],
50
+ sortings: [{ name: 'name', desc: false }], pagination: { limit: (0, master_page_1.takeFor)(request), page: 1 }
51
+ })
46
52
  ]);
53
+ const page = (0, master_page_1.pageOf)(effective.items, request, row => row.name);
47
54
  /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */
48
55
  (0, master_page_query_1.refuseIfTooLarge)('품목', request, page);
49
56
  const materials = page.rows;
@@ -1 +1 @@
1
- {"version":3,"file":"material-query.js","sourceRoot":"","sources":["../../../server/service/material/material-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 { MaterialClass } from './material-class'\nimport { MaterialDefinition } from './material-definition'\nimport { materialMaster as buildMaterialMaster } from './material-master'\n\n@ObjectType({ description: 'Material master as the twin receives it' })\nexport class MaterialMaster {\n @Field(type => String, { description: 'ResourceClassDef[] as JSON text' })\n materialClasses: string\n\n @Field(type => String, { description: 'MaterialDefinition[] as JSON text' })\n materialDefinitions: 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(MaterialDefinition)\nexport class MaterialQuery {\n /** 품목 축 전체 — 품목이 자기 등급을 가리키므로 등급과 함께 낸다. */\n @Query(returns => MaterialMaster, { description: 'Material master for the twin to pull' })\n async materialMaster(\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<MaterialMaster> {\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(MaterialClass).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } }),\n pullPage<MaterialDefinition>(MaterialDefinition, domain, request)\n ])\n\n /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */\n refuseIfTooLarge('품목', request, page)\n\n const materials = page.rows\n\n const master = buildMaterialMaster(classes, materials)\n\n return {\n materialClasses: JSON.stringify(master.materialClasses),\n materialDefinitions: JSON.stringify(master.materialDefinitions),\n hasMore: page.hasMore,\n ...(page.next ? { next: page.next } : {})\n }\n }\n}\n"]}
1
+ {"version":3,"file":"material-query.js","sourceRoot":"","sources":["../../../server/service/material/material-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 { refuseIfTooLarge } from '../master-page-query'\n\nimport { MaterialClass } from './material-class'\nimport { MaterialDefinition } from './material-definition'\nimport { materialMaster as buildMaterialMaster } from './material-master'\nimport { listEffectiveMaterialDefinitions } from './material-effective-list'\nimport { pageOf, takeFor } from '../master-page'\n\n@ObjectType({ description: 'Material master as the twin receives it' })\nexport class MaterialMaster {\n @Field(type => String, { description: 'ResourceClassDef[] as JSON text' })\n materialClasses: string\n\n @Field(type => String, { description: 'MaterialDefinition[] as JSON text' })\n materialDefinitions: 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(MaterialDefinition)\nexport class MaterialQuery {\n /** 품목 축 전체 — 품목이 자기 등급을 가리키므로 등급과 함께 낸다. */\n @Query(returns => MaterialMaster, { description: 'Material master for the twin to pull' })\n async materialMaster(\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<MaterialMaster> {\n const { domain } = context.state\n\n const request = { after, limit }\n\n /*\n * **등급은 통째로, 구성원만 나눈다.** 구성원이 자기 등급을 이름으로 가리키므로 둘을 함께 나누면\n * 구성원이 먼저 도착하고 등급이 나중에 오는 순간이 생긴다 — 그 순간 받는 쪽은 없는 등급을\n * 가리키는 구성원을 갖는다. 등급은 수십 단위라 매 쪽에 실어도 크지 않다.\n */\n const [classes, effective] = await Promise.all([\n getRepository(MaterialClass).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } }),\n listEffectiveMaterialDefinitions(getRepository(MaterialDefinition).manager, domain, {\n filters: [...(after ? [{ name: 'name', operator: 'gt', value: after }] : []),\n { name: 'revisionNumber', operator: 'is_not_null', value: true }],\n sortings: [{ name: 'name', desc: false }], pagination: { limit: takeFor(request), page: 1 }\n } as any)\n ])\n const page = pageOf(effective.items, request, row => row.name)\n\n /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */\n refuseIfTooLarge('품목', request, page)\n\n const materials = page.rows\n\n const master = buildMaterialMaster(classes, materials)\n\n return {\n materialClasses: JSON.stringify(master.materialClasses),\n materialDefinitions: JSON.stringify(master.materialDefinitions),\n hasMore: page.hasMore,\n ...(page.next ? { next: page.next } : {})\n }\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import { EntityManager } from 'typeorm';
2
+ /** Preserve the recursive ResourceProperty shape; never stringify a structured value silently. */
3
+ export declare function materialDraftContent(input: any, stored?: boolean): {
4
+ description: any;
5
+ materialClassIds: string[];
6
+ properties: Record<string, unknown>[];
7
+ testSpecificationIds: string[];
8
+ };
9
+ /** These are standard business-name references, not revision IDs or approval of the referenced contents. */
10
+ export declare function validateMaterialReferences(manager: EntityManager, domainId: string, input: ReturnType<typeof materialDraftContent>): Promise<void>;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.materialDraftContent = materialDraftContent;
4
+ exports.validateMaterialReferences = validateMaterialReferences;
5
+ const typeorm_1 = require("typeorm");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const material_class_1 = require("./material-class");
8
+ const test_specification_1 = require("../test-spec/test-specification");
9
+ function invalid(field) {
10
+ throw new shell_1.Refusal('INVALID_MATERIAL_CONTENT', { field }, `Invalid material definition: ${field}`);
11
+ }
12
+ function names(value, field) {
13
+ if (value == null)
14
+ return [];
15
+ if (!Array.isArray(value) || value.length > 1000)
16
+ invalid(field);
17
+ const result = value.map(name => {
18
+ if (typeof name !== 'string' || !name.trim())
19
+ invalid(field);
20
+ return name.trim();
21
+ });
22
+ if (new Set(result).size !== result.length)
23
+ invalid(`${field}.duplicate`);
24
+ return result;
25
+ }
26
+ /** Preserve the recursive ResourceProperty shape; never stringify a structured value silently. */
27
+ function materialDraftContent(input, stored = false) {
28
+ if (!input || typeof input !== 'object' || Array.isArray(input))
29
+ invalid('content');
30
+ if (!stored) {
31
+ const allowed = new Set(['description', 'materialClassIds', 'properties', 'testSpecificationIds', 'effectiveFrom', 'reason']);
32
+ for (const field of Object.keys(input))
33
+ if (!allowed.has(field))
34
+ invalid(field);
35
+ }
36
+ if (input.description != null && typeof input.description !== 'string')
37
+ invalid('description');
38
+ let count = 0;
39
+ const properties = (rows, depth = 0) => {
40
+ if (rows == null)
41
+ return [];
42
+ if (!Array.isArray(rows) || depth > 32)
43
+ invalid('properties');
44
+ const keys = new Set();
45
+ return rows.map(row => {
46
+ if (++count > 1000 || !row || typeof row !== 'object' || Array.isArray(row))
47
+ invalid('properties');
48
+ const allowed = new Set(['id', 'description', 'value', 'dataType', 'uom', 'children', 'classPropertyId', 'source']);
49
+ for (const field of Object.keys(row))
50
+ if (!allowed.has(field))
51
+ invalid(`properties.${field}`);
52
+ if (typeof row.id !== 'string' || !row.id.trim())
53
+ invalid('properties.id');
54
+ for (const field of ['description', 'dataType', 'uom', 'classPropertyId'])
55
+ if (row[field] != null && typeof row[field] !== 'string')
56
+ invalid(`properties.${field}`);
57
+ if (row.value != null && typeof row.value !== 'string' && (typeof row.value !== 'number' || !Number.isFinite(row.value)))
58
+ invalid('properties.value');
59
+ if (row.source != null && !['local', 'reference'].includes(row.source))
60
+ invalid('properties.source');
61
+ const id = row.id.trim();
62
+ // Several perBaseUnit declarations are allowed, one for each target unit.
63
+ const key = JSON.stringify([id, row.uom ?? '']);
64
+ if (keys.has(key))
65
+ invalid('properties.duplicate');
66
+ keys.add(key);
67
+ if (id === 'perBaseUnit' && (typeof row.uom !== 'string' || !row.uom.trim() || !['string', 'number'].includes(typeof row.value) || !Number.isFinite(Number(row.value)) || Number(row.value) <= 0))
68
+ invalid('properties.perBaseUnit');
69
+ return { ...row, id, ...(row.children != null ? { children: properties(row.children, depth + 1) } : {}) };
70
+ });
71
+ };
72
+ return {
73
+ description: input.description ?? null,
74
+ materialClassIds: names(input.materialClassIds, 'materialClassIds'),
75
+ properties: properties(input.properties),
76
+ testSpecificationIds: names(input.testSpecificationIds, 'testSpecificationIds')
77
+ };
78
+ }
79
+ /** These are standard business-name references, not revision IDs or approval of the referenced contents. */
80
+ async function validateMaterialReferences(manager, domainId, input) {
81
+ for (const [entity, values, field] of [
82
+ [material_class_1.MaterialClass, input.materialClassIds, 'materialClassIds'],
83
+ [test_specification_1.TestSpecification, input.testSpecificationIds, 'testSpecificationIds']
84
+ ]) {
85
+ if (!values.length)
86
+ continue;
87
+ const found = await manager.getRepository(entity).count({ where: { domain: { id: domainId }, name: (0, typeorm_1.In)(values) } });
88
+ if (found !== values.length)
89
+ throw new shell_1.Refusal('INVALID_MATERIAL_REFERENCE', { field }, 'A referenced master does not exist in this domain.');
90
+ }
91
+ }
92
+ //# sourceMappingURL=material-revision-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material-revision-content.js","sourceRoot":"","sources":["../../../server/service/material/material-revision-content.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\nimport { Refusal } from '@things-factory/shell'\nimport { MaterialClass } from './material-class'\nimport { TestSpecification } from '../test-spec/test-specification'\n\nfunction invalid(field: string): never {\n throw new Refusal('INVALID_MATERIAL_CONTENT', { field }, `Invalid material definition: ${field}`)\n}\nfunction names(value: unknown, field: string): string[] {\n if (value == null) return []\n if (!Array.isArray(value) || value.length > 1000) invalid(field)\n const result = value.map(name => {\n if (typeof name !== 'string' || !name.trim()) invalid(field)\n return name.trim()\n })\n if (new Set(result).size !== result.length) invalid(`${field}.duplicate`)\n return result\n}\n\n/** Preserve the recursive ResourceProperty shape; never stringify a structured value silently. */\nexport function materialDraftContent(input: any, stored = false) {\n if (!input || typeof input !== 'object' || Array.isArray(input)) invalid('content')\n if (!stored) {\n const allowed = new Set(['description', 'materialClassIds', 'properties', 'testSpecificationIds', 'effectiveFrom', 'reason'])\n for (const field of Object.keys(input)) if (!allowed.has(field)) invalid(field)\n }\n if (input.description != null && typeof input.description !== 'string') invalid('description')\n let count = 0\n const properties = (rows: unknown, depth = 0): Record<string, unknown>[] => {\n if (rows == null) return []\n if (!Array.isArray(rows) || depth > 32) invalid('properties')\n const keys = new Set<string>()\n return rows.map(row => {\n if (++count > 1000 || !row || typeof row !== 'object' || Array.isArray(row)) invalid('properties')\n const allowed = new Set(['id', 'description', 'value', 'dataType', 'uom', 'children', 'classPropertyId', 'source'])\n for (const field of Object.keys(row)) if (!allowed.has(field)) invalid(`properties.${field}`)\n if (typeof row.id !== 'string' || !row.id.trim()) invalid('properties.id')\n for (const field of ['description', 'dataType', 'uom', 'classPropertyId'])\n if (row[field] != null && typeof row[field] !== 'string') invalid(`properties.${field}`)\n if (row.value != null && typeof row.value !== 'string' && (typeof row.value !== 'number' || !Number.isFinite(row.value))) invalid('properties.value')\n if (row.source != null && !['local', 'reference'].includes(row.source)) invalid('properties.source')\n const id = row.id.trim()\n // Several perBaseUnit declarations are allowed, one for each target unit.\n const key = JSON.stringify([id, row.uom ?? ''])\n if (keys.has(key)) invalid('properties.duplicate')\n keys.add(key)\n if (id === 'perBaseUnit' && (typeof row.uom !== 'string' || !row.uom.trim() || !['string', 'number'].includes(typeof row.value) || !Number.isFinite(Number(row.value)) || Number(row.value) <= 0)) invalid('properties.perBaseUnit')\n return { ...row, id, ...(row.children != null ? { children: properties(row.children, depth + 1) } : {}) }\n })\n }\n return {\n description: input.description ?? null,\n materialClassIds: names(input.materialClassIds, 'materialClassIds'),\n properties: properties(input.properties),\n testSpecificationIds: names(input.testSpecificationIds, 'testSpecificationIds')\n }\n}\n\n/** These are standard business-name references, not revision IDs or approval of the referenced contents. */\nexport async function validateMaterialReferences(manager: EntityManager, domainId: string, input: ReturnType<typeof materialDraftContent>) {\n for (const [entity, values, field] of [\n [MaterialClass, input.materialClassIds, 'materialClassIds'],\n [TestSpecification, input.testSpecificationIds, 'testSpecificationIds']\n ] as const) {\n if (!values.length) continue\n const found = await manager.getRepository(entity).count({ where: { domain: { id: domainId }, name: In(values) } })\n if (found !== values.length) throw new Refusal('INVALID_MATERIAL_REFERENCE', { field }, 'A referenced master does not exist in this domain.')\n }\n}\n"]}
@@ -0,0 +1,54 @@
1
+ import { MaterialDefinition } from './material-definition';
2
+ import { MaterialDefinitionDraftContent } from './material-revision-service';
3
+ export declare class MaterialDefinitionRevisionResolver {
4
+ publishMaterialDefinitionDraft(revisionId: string, editGeneration: number, context: ResolverContext): Promise<import("./material-revision").MaterialDefinitionPlan>;
5
+ materialDefinitionRevisions(materialId: string, context: ResolverContext): Promise<{
6
+ materialId: string;
7
+ name: string;
8
+ generation: number;
9
+ at: string;
10
+ schedule: {
11
+ state: string;
12
+ id: string;
13
+ domain: import("@things-factory/shell").Domain;
14
+ domainId: string;
15
+ material: MaterialDefinition;
16
+ materialId: string;
17
+ plan: import("./material-revision").MaterialDefinitionPlan;
18
+ planId: string;
19
+ revision: import("./material-revision").MaterialDefinitionRevision;
20
+ revisionId: string;
21
+ validFrom: Date;
22
+ validTo: Date | null;
23
+ }[];
24
+ revisions: {
25
+ state: string;
26
+ revisionNumber: number;
27
+ policy: import("..").MasterChangePolicy;
28
+ policyId: string;
29
+ id: string;
30
+ domain: import("@things-factory/shell").Domain;
31
+ domainId: string;
32
+ material: MaterialDefinition;
33
+ materialId: string;
34
+ baseRevision: import("./material-revision").MaterialDefinitionRevision | null;
35
+ baseRevisionId: string | null;
36
+ baseGeneration: number;
37
+ editGeneration: number;
38
+ description: string | null;
39
+ materialClassIds: string[];
40
+ properties: Record<string, unknown>[];
41
+ testSpecificationIds: string[];
42
+ effectiveFrom: Date;
43
+ reason: string;
44
+ creator: import("@things-factory/auth-base").User;
45
+ creatorId: string;
46
+ createdAt: Date;
47
+ frozenAt: Date | null;
48
+ contentHash: string | null;
49
+ activityInstanceId: string | null;
50
+ }[];
51
+ }>;
52
+ createMaterialDefinitionDraft(content: MaterialDefinitionDraftContent, materialId: string, name: string, baseRevisionId: string, context: ResolverContext): Promise<import("./material-revision").MaterialDefinitionRevision>;
53
+ editMaterialDefinitionDraft(revisionId: string, editGeneration: number, content: MaterialDefinitionDraftContent, context: ResolverContext): Promise<import("./material-revision").MaterialDefinitionRevision>;
54
+ }