@things-factory/ops-master 10.0.18 → 10.0.19

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 (55) hide show
  1. package/dist-client/pages/master-axes.js +20 -7
  2. package/dist-client/pages/master-axes.js.map +1 -1
  3. package/dist-client/pages/master-list.d.ts +3 -3
  4. package/dist-client/pages/master-list.js +10 -38
  5. package/dist-client/pages/master-list.js.map +1 -1
  6. package/dist-client/tsconfig.tsbuildinfo +1 -1
  7. package/dist-server/service/index.d.ts +9 -2
  8. package/dist-server/service/index.js +11 -0
  9. package/dist-server/service/index.js.map +1 -1
  10. package/dist-server/service/master-axis.d.ts +1 -1
  11. package/dist-server/service/master-axis.js +16 -1
  12. package/dist-server/service/master-axis.js.map +1 -1
  13. package/dist-server/service/operation/index.d.ts +2 -1
  14. package/dist-server/service/operation/index.js +2 -1
  15. package/dist-server/service/operation/index.js.map +1 -1
  16. package/dist-server/service/operation/operation-readiness-resolver.d.ts +39 -0
  17. package/dist-server/service/operation/operation-readiness-resolver.js +111 -0
  18. package/dist-server/service/operation/operation-readiness-resolver.js.map +1 -0
  19. package/dist-server/service/operation/operation-readiness.d.ts +34 -0
  20. package/dist-server/service/operation/operation-readiness.js +133 -0
  21. package/dist-server/service/operation/operation-readiness.js.map +1 -0
  22. package/dist-server/service/personnel/personnel-class-readiness.d.ts +22 -0
  23. package/dist-server/service/personnel/personnel-class-readiness.js +98 -0
  24. package/dist-server/service/personnel/personnel-class-readiness.js.map +1 -0
  25. package/dist-server/service/personnel/qualification.d.ts +7 -0
  26. package/dist-server/service/personnel/qualification.js +25 -0
  27. package/dist-server/service/personnel/qualification.js.map +1 -1
  28. package/dist-server/service/recipe/index.d.ts +9 -0
  29. package/dist-server/service/recipe/index.js +14 -0
  30. package/dist-server/service/recipe/index.js.map +1 -0
  31. package/dist-server/service/recipe/recipe-definition.d.ts +71 -0
  32. package/dist-server/service/recipe/recipe-definition.js +130 -0
  33. package/dist-server/service/recipe/recipe-definition.js.map +1 -0
  34. package/dist-server/service/recipe/recipe-master-resolver.d.ts +28 -0
  35. package/dist-server/service/recipe/recipe-master-resolver.js +124 -0
  36. package/dist-server/service/recipe/recipe-master-resolver.js.map +1 -0
  37. package/dist-server/service/recipe/recipe-master.d.ts +37 -0
  38. package/dist-server/service/recipe/recipe-master.js +47 -0
  39. package/dist-server/service/recipe/recipe-master.js.map +1 -0
  40. package/dist-server/service/recipe/recipe-query.d.ts +13 -0
  41. package/dist-server/service/recipe/recipe-query.js +54 -0
  42. package/dist-server/service/recipe/recipe-query.js.map +1 -0
  43. package/dist-server/service/recipe/route-definition.d.ts +51 -0
  44. package/dist-server/service/recipe/route-definition.js +113 -0
  45. package/dist-server/service/recipe/route-definition.js.map +1 -0
  46. package/dist-server/service/status/master-status.d.ts +0 -7
  47. package/dist-server/service/status/master-status.js +5 -6
  48. package/dist-server/service/status/master-status.js.map +1 -1
  49. package/dist-server/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +5 -5
  51. package/translations/en.json +3 -3
  52. package/translations/ja.json +5 -1
  53. package/translations/ko.json +3 -3
  54. package/translations/ms.json +5 -1
  55. package/translations/zh.json +5 -1
@@ -5,6 +5,7 @@ export * from './asset/asset-master-resolver';
5
5
  export * from './material/material-master-resolver';
6
6
  export * from './location/location-master-resolver';
7
7
  export * from './operation/operation-master-resolver';
8
+ export * from './recipe/recipe-master-resolver';
8
9
  export * from './test-spec/test-spec-master-resolver';
9
10
  export * from './master-crud';
10
11
  export * from './master-axis';
@@ -35,12 +36,18 @@ export * from './material/material-query';
35
36
  export * from './operation/operation-definition';
36
37
  export * from './operation/operation-master';
37
38
  export * from './operation/operation-query';
39
+ export * from './operation/operation-readiness';
40
+ export * from './operation/operation-readiness-resolver';
41
+ export * from './recipe/recipe-definition';
42
+ export * from './recipe/route-definition';
43
+ export * from './recipe/recipe-master';
44
+ export * from './recipe/recipe-query';
38
45
  export * from './test-spec/test-specification';
39
46
  export * from './test-spec/test-spec-master';
40
47
  export * from './test-spec/test-spec-query';
41
48
  export * from './status/axis-stamp';
42
49
  export * from './status/master-status';
43
- export declare const entities: (typeof import("./equipment/equipment").Equipment | typeof import("./equipment/equipment-class").EquipmentClass | typeof import("./equipment/stop-reason").StopReason | typeof import("./material/material-definition").MaterialDefinition | typeof import("./operation/operation-definition").OperationDefinition | typeof import("./location/operational-location").OperationalLocation | typeof import("./personnel/person").Person | typeof import("./asset/physical-asset").PhysicalAsset | typeof import("./test-spec/test-specification").TestSpecification)[];
50
+ export declare const entities: (typeof import("./equipment/equipment").Equipment | typeof import("./equipment/equipment-class").EquipmentClass | typeof import("./equipment/stop-reason").StopReason | typeof import("./material/material-definition").MaterialDefinition | typeof import("./operation/operation-definition").OperationDefinition | typeof import("./location/operational-location").OperationalLocation | typeof import("./personnel/person").Person | typeof import("./asset/physical-asset").PhysicalAsset | typeof import("./recipe").RecipeDefinition | typeof import("./recipe").RouteDefinition | typeof import("./test-spec/test-specification").TestSpecification)[];
44
51
  export declare const schema: {
45
- resolverClasses: (typeof import("./personnel/personnel-master-resolver").PersonnelClassResolver | typeof import("./personnel/personnel-master-resolver").PersonResolver | typeof import("./equipment/equipment-master-resolver").EquipmentClassResolver | typeof import("./equipment/equipment-master-resolver").EquipmentResolver | typeof import("./equipment/equipment-master-resolver").StopReasonResolver | typeof import("./asset/asset-master-resolver").PhysicalAssetClassResolver | typeof import("./asset/asset-master-resolver").PhysicalAssetResolver | typeof import("./material/material-master-resolver").MaterialClassResolver | typeof import("./material/material-master-resolver").MaterialDefinitionResolver | typeof import("./location/location-master-resolver").OperationalLocationResolver | typeof import("./operation/operation-master-resolver").OperationDefinitionResolver | typeof import("./test-spec/test-spec-master-resolver").TestSpecificationResolver | typeof import("./personnel/personnel-query").PersonnelQuery | typeof import("./equipment/equipment-query").EquipmentQuery | typeof import("./asset/asset-query").AssetQuery | typeof import("./location/location-query").LocationQuery | typeof import("./material/material-query").MaterialQuery | typeof import("./operation/operation-query").OperationQuery | typeof import("./test-spec/test-spec-query").TestSpecificationQuery | typeof import("./status/master-status").OpsMasterStatusQuery | typeof import("./equipment/stop-reason-query").StopReasonQuery)[];
52
+ resolverClasses: (typeof import("./personnel/personnel-master-resolver").PersonnelClassResolver | typeof import("./personnel/personnel-master-resolver").PersonResolver | typeof import("./equipment/equipment-master-resolver").EquipmentClassResolver | typeof import("./equipment/equipment-master-resolver").EquipmentResolver | typeof import("./equipment/equipment-master-resolver").StopReasonResolver | typeof import("./asset/asset-master-resolver").PhysicalAssetClassResolver | typeof import("./asset/asset-master-resolver").PhysicalAssetResolver | typeof import("./material/material-master-resolver").MaterialClassResolver | typeof import("./material/material-master-resolver").MaterialDefinitionResolver | typeof import("./location/location-master-resolver").OperationalLocationResolver | typeof import("./operation/operation-master-resolver").OperationDefinitionResolver | typeof import("./recipe/recipe-master-resolver").RecipeDefinitionResolver | typeof import("./recipe/recipe-master-resolver").RouteDefinitionResolver | typeof import("./test-spec/test-spec-master-resolver").TestSpecificationResolver | typeof import("./personnel/personnel-query").PersonnelQuery | typeof import("./equipment/equipment-query").EquipmentQuery | typeof import("./asset/asset-query").AssetQuery | typeof import("./location/location-query").LocationQuery | typeof import("./material/material-query").MaterialQuery | typeof import("./operation/operation-query").OperationQuery | typeof import("./operation/operation-readiness-resolver").OperationReadinessResolver | typeof import("./recipe/recipe-query").RecipeQuery | typeof import("./test-spec/test-spec-query").TestSpecificationQuery | typeof import("./status/master-status").OpsMasterStatusQuery | typeof import("./equipment/stop-reason-query").StopReasonQuery)[];
46
53
  };
@@ -10,6 +10,7 @@ tslib_1.__exportStar(require("./asset/asset-master-resolver"), exports);
10
10
  tslib_1.__exportStar(require("./material/material-master-resolver"), exports);
11
11
  tslib_1.__exportStar(require("./location/location-master-resolver"), exports);
12
12
  tslib_1.__exportStar(require("./operation/operation-master-resolver"), exports);
13
+ tslib_1.__exportStar(require("./recipe/recipe-master-resolver"), exports);
13
14
  tslib_1.__exportStar(require("./test-spec/test-spec-master-resolver"), exports);
14
15
  tslib_1.__exportStar(require("./master-crud"), exports);
15
16
  tslib_1.__exportStar(require("./master-axis"), exports);
@@ -40,6 +41,12 @@ tslib_1.__exportStar(require("./material/material-query"), exports);
40
41
  tslib_1.__exportStar(require("./operation/operation-definition"), exports);
41
42
  tslib_1.__exportStar(require("./operation/operation-master"), exports);
42
43
  tslib_1.__exportStar(require("./operation/operation-query"), exports);
44
+ tslib_1.__exportStar(require("./operation/operation-readiness"), exports);
45
+ tslib_1.__exportStar(require("./operation/operation-readiness-resolver"), exports);
46
+ tslib_1.__exportStar(require("./recipe/recipe-definition"), exports);
47
+ tslib_1.__exportStar(require("./recipe/route-definition"), exports);
48
+ tslib_1.__exportStar(require("./recipe/recipe-master"), exports);
49
+ tslib_1.__exportStar(require("./recipe/recipe-query"), exports);
43
50
  tslib_1.__exportStar(require("./test-spec/test-specification"), exports);
44
51
  tslib_1.__exportStar(require("./test-spec/test-spec-master"), exports);
45
52
  tslib_1.__exportStar(require("./test-spec/test-spec-query"), exports);
@@ -52,6 +59,8 @@ const location_1 = require("./location");
52
59
  const material_1 = require("./material");
53
60
  const operation_1 = require("./operation");
54
61
  const personnel_1 = require("./personnel");
62
+ /* 라우트·레시피 — 품목→지시서를 이으려면 그 사이에 이 둘이 있어야 한다(§`recipe-definition.ts`). */
63
+ const recipe_1 = require("./recipe");
55
64
  const status_1 = require("./status");
56
65
  const test_spec_1 = require("./test-spec");
57
66
  exports.entities = [
@@ -62,6 +71,7 @@ exports.entities = [
62
71
  ...location_1.entities,
63
72
  ...material_1.entities,
64
73
  ...operation_1.entities,
74
+ ...recipe_1.entities,
65
75
  ...test_spec_1.entities
66
76
  ];
67
77
  exports.schema = {
@@ -73,6 +83,7 @@ exports.schema = {
73
83
  ...location_1.resolvers,
74
84
  ...material_1.resolvers,
75
85
  ...operation_1.resolvers,
86
+ ...recipe_1.resolvers,
76
87
  ...test_spec_1.resolvers,
77
88
  ...status_1.resolvers
78
89
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,oEAAyC;AACzC,gFAAqD;AACrD,gFAAqD;AACrD,wEAA6C;AAC7C,8EAAmD;AACnD,8EAAmD;AACnD,gFAAqD;AACrD,gFAAqD;AACrD,wDAA6B;AAC7B,wDAA6B;AAC7B,wDAA6B;AAC7B,6DAAkC;AAClC,sEAA2C;AAC3C,uEAA4C;AAC5C,sEAA2C;AAC3C,oEAAyC;AACzC,gEAAqC;AACrC,sEAA2C;AAC3C,yEAA8C;AAC9C,kEAAuC;AACvC,uEAA4C;AAC5C,sEAA2C;AAC3C,kEAAuC;AACvC,iEAAsC;AACtC,uEAA4C;AAC5C,+DAAoC;AACpC,8DAAmC;AACnC,0EAA+C;AAC/C,qEAA0C;AAC1C,oEAAyC;AACzC,oEAAyC;AACzC,yEAA8C;AAC9C,qEAA0C;AAC1C,oEAAyC;AACzC,2EAAgD;AAChD,uEAA4C;AAC5C,sEAA2C;AAC3C,yEAA8C;AAC9C,uEAA4C;AAC5C,sEAA2C;AAC3C,8DAAmC;AACnC,iEAAsC;AAEtC,mCAAmC;AACnC,mCAAgF;AAChF,2CAA4F;AAC5F,yCAAyF;AACzF,yCAAyF;AACzF,2CAA4F;AAC5F,2CAA4F;AAC5F,qCAAuD;AACvD,2CAA0F;AAE7E,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,oBAAiB;IACpB,GAAG,oBAAiB;IACpB,GAAG,gBAAa;IAChB,GAAG,mBAAgB;IACnB,GAAG,mBAAgB;IACnB,GAAG,oBAAiB;IACpB,GAAG,oBAAgB;CACpB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,qBAAkB;QACrB,GAAG,qBAAkB;QACrB,GAAG,iBAAc;QACjB,GAAG,oBAAiB;QACpB,GAAG,oBAAiB;QACpB,GAAG,qBAAkB;QACrB,GAAG,qBAAiB;QACpB,GAAG,kBAAe;KACnB;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './resource/resource-class'\nexport * from './personnel/personnel-master-resolver'\nexport * from './equipment/equipment-master-resolver'\nexport * from './asset/asset-master-resolver'\nexport * from './material/material-master-resolver'\nexport * from './location/location-master-resolver'\nexport * from './operation/operation-master-resolver'\nexport * from './test-spec/test-spec-master-resolver'\nexport * from './master-crud'\nexport * from './master-axis'\nexport * from './master-list'\nexport * from './personnel/person'\nexport * from './personnel/personnel-class'\nexport * from './personnel/personnel-master'\nexport * from './personnel/personnel-query'\nexport * from './personnel/qualification'\nexport * from './equipment/equipment'\nexport * from './equipment/equipment-class'\nexport * from './equipment/state-period-rules'\nexport * from './equipment/stop-reason'\nexport * from './equipment/equipment-master'\nexport * from './equipment/equipment-query'\nexport * from './asset/asset-lifecycle'\nexport * from './asset/physical-asset'\nexport * from './asset/physical-asset-class'\nexport * from './asset/asset-master'\nexport * from './asset/asset-query'\nexport * from './location/operational-location'\nexport * from './location/location-master'\nexport * from './location/location-query'\nexport * from './material/material-class'\nexport * from './material/material-definition'\nexport * from './material/material-master'\nexport * from './material/material-query'\nexport * from './operation/operation-definition'\nexport * from './operation/operation-master'\nexport * from './operation/operation-query'\nexport * from './test-spec/test-specification'\nexport * from './test-spec/test-spec-master'\nexport * from './test-spec/test-spec-query'\nexport * from './status/axis-stamp'\nexport * from './status/master-status'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as AssetEntities, resolvers as AssetResolvers } from './asset'\nimport { entities as EquipmentEntities, resolvers as EquipmentResolvers } from './equipment'\nimport { entities as LocationEntities, resolvers as LocationResolvers } from './location'\nimport { entities as MaterialEntities, resolvers as MaterialResolvers } from './material'\nimport { entities as OperationEntities, resolvers as OperationResolvers } from './operation'\nimport { entities as PersonnelEntities, resolvers as PersonnelResolvers } from './personnel'\nimport { resolvers as StatusResolvers } from './status'\nimport { entities as TestSpecEntities, resolvers as TestSpecResolvers } from './test-spec'\n\nexport const entities = [\n /* ENTITIES */\n ...PersonnelEntities,\n ...EquipmentEntities,\n ...AssetEntities,\n ...LocationEntities,\n ...MaterialEntities,\n ...OperationEntities,\n ...TestSpecEntities\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...PersonnelResolvers,\n ...EquipmentResolvers,\n ...AssetResolvers,\n ...LocationResolvers,\n ...MaterialResolvers,\n ...OperationResolvers,\n ...TestSpecResolvers,\n ...StatusResolvers\n ]\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,oEAAyC;AACzC,gFAAqD;AACrD,gFAAqD;AACrD,wEAA6C;AAC7C,8EAAmD;AACnD,8EAAmD;AACnD,gFAAqD;AACrD,0EAA+C;AAC/C,gFAAqD;AACrD,wDAA6B;AAC7B,wDAA6B;AAC7B,wDAA6B;AAC7B,6DAAkC;AAClC,sEAA2C;AAC3C,uEAA4C;AAC5C,sEAA2C;AAC3C,oEAAyC;AACzC,gEAAqC;AACrC,sEAA2C;AAC3C,yEAA8C;AAC9C,kEAAuC;AACvC,uEAA4C;AAC5C,sEAA2C;AAC3C,kEAAuC;AACvC,iEAAsC;AACtC,uEAA4C;AAC5C,+DAAoC;AACpC,8DAAmC;AACnC,0EAA+C;AAC/C,qEAA0C;AAC1C,oEAAyC;AACzC,oEAAyC;AACzC,yEAA8C;AAC9C,qEAA0C;AAC1C,oEAAyC;AACzC,2EAAgD;AAChD,uEAA4C;AAC5C,sEAA2C;AAC3C,0EAA+C;AAC/C,mFAAwD;AACxD,qEAA0C;AAC1C,oEAAyC;AACzC,iEAAsC;AACtC,gEAAqC;AACrC,yEAA8C;AAC9C,uEAA4C;AAC5C,sEAA2C;AAC3C,8DAAmC;AACnC,iEAAsC;AAEtC,mCAAmC;AACnC,mCAAgF;AAChF,2CAA4F;AAC5F,yCAAyF;AACzF,yCAAyF;AACzF,2CAA4F;AAC5F,2CAA4F;AAC5F,wEAAwE;AACxE,qCAAmF;AACnF,qCAAuD;AACvD,2CAA0F;AAE7E,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,oBAAiB;IACpB,GAAG,oBAAiB;IACpB,GAAG,gBAAa;IAChB,GAAG,mBAAgB;IACnB,GAAG,mBAAgB;IACnB,GAAG,oBAAiB;IACpB,GAAG,iBAAc;IACjB,GAAG,oBAAgB;CACpB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,qBAAkB;QACrB,GAAG,qBAAkB;QACrB,GAAG,iBAAc;QACjB,GAAG,oBAAiB;QACpB,GAAG,oBAAiB;QACpB,GAAG,qBAAkB;QACrB,GAAG,kBAAe;QAClB,GAAG,qBAAiB;QACpB,GAAG,kBAAe;KACnB;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './resource/resource-class'\nexport * from './personnel/personnel-master-resolver'\nexport * from './equipment/equipment-master-resolver'\nexport * from './asset/asset-master-resolver'\nexport * from './material/material-master-resolver'\nexport * from './location/location-master-resolver'\nexport * from './operation/operation-master-resolver'\nexport * from './recipe/recipe-master-resolver'\nexport * from './test-spec/test-spec-master-resolver'\nexport * from './master-crud'\nexport * from './master-axis'\nexport * from './master-list'\nexport * from './personnel/person'\nexport * from './personnel/personnel-class'\nexport * from './personnel/personnel-master'\nexport * from './personnel/personnel-query'\nexport * from './personnel/qualification'\nexport * from './equipment/equipment'\nexport * from './equipment/equipment-class'\nexport * from './equipment/state-period-rules'\nexport * from './equipment/stop-reason'\nexport * from './equipment/equipment-master'\nexport * from './equipment/equipment-query'\nexport * from './asset/asset-lifecycle'\nexport * from './asset/physical-asset'\nexport * from './asset/physical-asset-class'\nexport * from './asset/asset-master'\nexport * from './asset/asset-query'\nexport * from './location/operational-location'\nexport * from './location/location-master'\nexport * from './location/location-query'\nexport * from './material/material-class'\nexport * from './material/material-definition'\nexport * from './material/material-master'\nexport * from './material/material-query'\nexport * from './operation/operation-definition'\nexport * from './operation/operation-master'\nexport * from './operation/operation-query'\nexport * from './operation/operation-readiness'\nexport * from './operation/operation-readiness-resolver'\nexport * from './recipe/recipe-definition'\nexport * from './recipe/route-definition'\nexport * from './recipe/recipe-master'\nexport * from './recipe/recipe-query'\nexport * from './test-spec/test-specification'\nexport * from './test-spec/test-spec-master'\nexport * from './test-spec/test-spec-query'\nexport * from './status/axis-stamp'\nexport * from './status/master-status'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as AssetEntities, resolvers as AssetResolvers } from './asset'\nimport { entities as EquipmentEntities, resolvers as EquipmentResolvers } from './equipment'\nimport { entities as LocationEntities, resolvers as LocationResolvers } from './location'\nimport { entities as MaterialEntities, resolvers as MaterialResolvers } from './material'\nimport { entities as OperationEntities, resolvers as OperationResolvers } from './operation'\nimport { entities as PersonnelEntities, resolvers as PersonnelResolvers } from './personnel'\n/* 라우트·레시피 — 품목→지시서를 이으려면 그 사이에 이 둘이 있어야 한다(§`recipe-definition.ts`). */\nimport { entities as RecipeEntities, resolvers as RecipeResolvers } from './recipe'\nimport { resolvers as StatusResolvers } from './status'\nimport { entities as TestSpecEntities, resolvers as TestSpecResolvers } from './test-spec'\n\nexport const entities = [\n /* ENTITIES */\n ...PersonnelEntities,\n ...EquipmentEntities,\n ...AssetEntities,\n ...LocationEntities,\n ...MaterialEntities,\n ...OperationEntities,\n ...RecipeEntities,\n ...TestSpecEntities\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...PersonnelResolvers,\n ...EquipmentResolvers,\n ...AssetResolvers,\n ...LocationResolvers,\n ...MaterialResolvers,\n ...OperationResolvers,\n ...RecipeResolvers,\n ...TestSpecResolvers,\n ...StatusResolvers\n ]\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { ObjectLiteral } from 'typeorm';
2
2
  /**
3
- * 마스터 열두 축의 **한 줄짜리 선언** — 축마다 다른 것만 적는다.
3
+ * 마스터 축의 **한 줄짜리 선언** — 축마다 다른 것만 적는다.
4
4
  *
5
5
  * ── 왜 표로 두나 ───────────────────────────────────────────────────────────
6
6
  * 축이 열둘인데 조회와 저장은 모양이 같다. 축마다 리졸버를 통째로 베끼면 같은 코드가 열두 벌이 되고,
@@ -13,6 +13,8 @@ const person_1 = require("./personnel/person");
13
13
  const personnel_class_1 = require("./personnel/personnel-class");
14
14
  const physical_asset_1 = require("./asset/physical-asset");
15
15
  const physical_asset_class_1 = require("./asset/physical-asset-class");
16
+ const recipe_definition_1 = require("./recipe/recipe-definition");
17
+ const route_definition_1 = require("./recipe/route-definition");
16
18
  const test_specification_1 = require("./test-spec/test-specification");
17
19
  exports.MASTER_AXES = [
18
20
  { key: 'personnelClass', entity: personnel_class_1.PersonnelClass, searchables: ['name', 'description'], hasEffectivePeriod: true },
@@ -26,7 +28,20 @@ exports.MASTER_AXES = [
26
28
  { key: 'materialDefinition', entity: material_definition_1.MaterialDefinition, searchables: ['name', 'description', 'identity'], hasEffectivePeriod: true },
27
29
  { key: 'operationalLocation', entity: operational_location_1.OperationalLocation, searchables: ['name', 'description'], hasEffectivePeriod: true },
28
30
  { key: 'operationDefinition', entity: operation_definition_1.OperationDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true },
29
- { key: 'testSpecification', entity: test_specification_1.TestSpecification, searchables: ['name', 'description'], hasEffectivePeriod: true }
31
+ { key: 'testSpecification', entity: test_specification_1.TestSpecification, searchables: ['name', 'description'], hasEffectivePeriod: true },
32
+ /*
33
+ * 라우트와 레시피 (2026-08-31) — 커널에 `RouteDef`·`RecipeDef` 가 있고 `JobOrder.routeKey` 가 그것을
34
+ * 가리키는데 **그 값이 가리킬 표가 없었다.** 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은
35
+ * 작업지시에 라우트를 자동으로 붙일 수 없었다.
36
+ */
37
+ { key: 'routeDefinition', entity: route_definition_1.RouteDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true },
38
+ {
39
+ key: 'recipeDefinition',
40
+ entity: recipe_definition_1.RecipeDefinition,
41
+ /* 만드는 품목으로도 찾는다 — 「이 품목을 만드는 레시피」가 MES 가 지시마다 묻는 물음이다. */
42
+ searchables: ['name', 'description', 'materialDefinitionName'],
43
+ hasEffectivePeriod: true
44
+ }
30
45
  ];
31
46
  function masterAxisOf(key) {
32
47
  const axis = exports.MASTER_AXES.find(candidate => candidate.key === key);
@@ -1 +1 @@
1
- {"version":3,"file":"master-axis.js","sourceRoot":"","sources":["../../server/service/master-axis.ts"],"names":[],"mappings":";;;AAwDA,oCASC;AA/DD,qDAAiD;AACjD,iEAA4D;AAC5D,yDAAoD;AACpD,8DAAyD;AACzD,wEAAmE;AACnE,2EAAsE;AACtE,0EAAqE;AACrE,+CAA2C;AAC3C,iEAA4D;AAC5D,2DAAsD;AACtD,uEAAiE;AACjE,uEAAkE;AA4BrD,QAAA,WAAW,GAA0B;IAChD,EAAE,GAAG,EAAE,gBAAgB,EAAE,MAAM,EAAE,gCAAc,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IACjH,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAM,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IACjG,EAAE,GAAG,EAAE,gBAAgB,EAAE,MAAM,EAAE,gCAAc,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IACjH,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,qBAAS,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IACvG,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,wBAAU,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IACzG,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,EAAE,yCAAkB,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IACzH,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,8BAAa,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IAC3H,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,8BAAa,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IAC/G,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,EAAE,wCAAkB,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IACrI,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,0CAAmB,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IAC3H,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,0CAAmB,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;IAC3H,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,EAAE,sCAAiB,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE;CACxH,CAAA;AAED,SAAgB,YAAY,CAAC,GAAW;IACtC,MAAM,IAAI,GAAG,mBAAW,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,GAAG,CAAC,CAAA;IAEjE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,2DAA2D;QAC3D,MAAM,IAAI,KAAK,CAAC,sCAAsC,GAAG,GAAG,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { ObjectLiteral } from 'typeorm'\n\nimport { Equipment } from './equipment/equipment'\nimport { EquipmentClass } from './equipment/equipment-class'\nimport { StopReason } from './equipment/stop-reason'\nimport { MaterialClass } from './material/material-class'\nimport { MaterialDefinition } from './material/material-definition'\nimport { OperationDefinition } from './operation/operation-definition'\nimport { OperationalLocation } from './location/operational-location'\nimport { Person } from './personnel/person'\nimport { PersonnelClass } from './personnel/personnel-class'\nimport { PhysicalAsset } from './asset/physical-asset'\nimport { PhysicalAssetClass } from './asset/physical-asset-class'\nimport { TestSpecification } from './test-spec/test-specification'\n\n/**\n * 마스터 열두 축의 **한 줄짜리 선언** — 축마다 다른 것만 적는다.\n *\n * ── 왜 표로 두나 ───────────────────────────────────────────────────────────\n * 축이 열둘인데 조회와 저장은 모양이 같다. 축마다 리졸버를 통째로 베끼면 같은 코드가 열두 벌이 되고,\n * 규칙 하나를 고칠 때 열두 곳을 고쳐야 한다. 그러다 한 곳을 빠뜨리면 그 축만 다르게 동작하는데\n * 오류는 나지 않는다.\n *\n * 그래서 **다른 것만** 적는다 — 어느 표인가, 무엇으로 찾는가, 없앨 때 지우는가 끝을 채우는가.\n *\n * ── 왜 유효 기간 여부가 여기 있나 ──────────────────────────────────────────\n * 지난 기록이 가리키는 축은 지우면 안 된다. 그것이 축마다 다르므로 축의 성질로 적는다. 정지 사유는\n * 유효 기간이 있고, 등급도 있다. 지금 열둘 모두 유효 기간을 갖는다 — 그래도 칸을 둔다. 없는 축이\n * 생기면 그때 이 표만 고치면 된다.\n */\n\nexport interface MasterAxis {\n /** GraphQL 이름의 바탕. `person` → `persons` · `updateMultiplePerson`. */\n key: string\n entity: { new (): ObjectLiteral }\n /** 검색어가 걸리는 칸들. */\n searchables: string[]\n /** 없앨 때 행을 지우지 않고 `effectiveEnd` 를 채우나. */\n hasEffectivePeriod: boolean\n}\n\nexport const MASTER_AXES: readonly MasterAxis[] = [\n { key: 'personnelClass', entity: PersonnelClass, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'person', entity: Person, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'equipmentClass', entity: EquipmentClass, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'equipment', entity: Equipment, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'stopReason', entity: StopReason, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'physicalAssetClass', entity: PhysicalAssetClass, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'physicalAsset', entity: PhysicalAsset, searchables: ['name', 'description', 'identity'], hasEffectivePeriod: true },\n { key: 'materialClass', entity: MaterialClass, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'materialDefinition', entity: MaterialDefinition, searchables: ['name', 'description', 'identity'], hasEffectivePeriod: true },\n { key: 'operationalLocation', entity: OperationalLocation, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'operationDefinition', entity: OperationDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'testSpecification', entity: TestSpecification, searchables: ['name', 'description'], hasEffectivePeriod: true }\n]\n\nexport function masterAxisOf(key: string): MasterAxis {\n const axis = MASTER_AXES.find(candidate => candidate.key === key)\n\n if (!axis) {\n /* 없는 축을 조용히 지나치지 않는다 — 지나치면 그 축의 화면이 빈 목록을 보이고 아무도 모른다. */\n throw new Error(`masterAxisOf: unknown master axis \"${key}\"`)\n }\n\n return axis\n}\n"]}
1
+ {"version":3,"file":"master-axis.js","sourceRoot":"","sources":["../../server/service/master-axis.ts"],"names":[],"mappings":"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","sourcesContent":["import { ObjectLiteral } from 'typeorm'\n\nimport { Equipment } from './equipment/equipment'\nimport { EquipmentClass } from './equipment/equipment-class'\nimport { StopReason } from './equipment/stop-reason'\nimport { MaterialClass } from './material/material-class'\nimport { MaterialDefinition } from './material/material-definition'\nimport { OperationDefinition } from './operation/operation-definition'\nimport { OperationalLocation } from './location/operational-location'\nimport { Person } from './personnel/person'\nimport { PersonnelClass } from './personnel/personnel-class'\nimport { PhysicalAsset } from './asset/physical-asset'\nimport { PhysicalAssetClass } from './asset/physical-asset-class'\nimport { RecipeDefinition } from './recipe/recipe-definition'\nimport { RouteDefinition } from './recipe/route-definition'\nimport { TestSpecification } from './test-spec/test-specification'\n\n/**\n * 마스터 축의 **한 줄짜리 선언** — 축마다 다른 것만 적는다.\n *\n * ── 왜 표로 두나 ───────────────────────────────────────────────────────────\n * 축이 열둘인데 조회와 저장은 모양이 같다. 축마다 리졸버를 통째로 베끼면 같은 코드가 열두 벌이 되고,\n * 규칙 하나를 고칠 때 열두 곳을 고쳐야 한다. 그러다 한 곳을 빠뜨리면 그 축만 다르게 동작하는데\n * 오류는 나지 않는다.\n *\n * 그래서 **다른 것만** 적는다 — 어느 표인가, 무엇으로 찾는가, 없앨 때 지우는가 끝을 채우는가.\n *\n * ── 왜 유효 기간 여부가 여기 있나 ──────────────────────────────────────────\n * 지난 기록이 가리키는 축은 지우면 안 된다. 그것이 축마다 다르므로 축의 성질로 적는다. 정지 사유는\n * 유효 기간이 있고, 등급도 있다. 지금 열둘 모두 유효 기간을 갖는다 — 그래도 칸을 둔다. 없는 축이\n * 생기면 그때 이 표만 고치면 된다.\n */\n\nexport interface MasterAxis {\n /** GraphQL 이름의 바탕. `person` → `persons` · `updateMultiplePerson`. */\n key: string\n entity: { new (): ObjectLiteral }\n /** 검색어가 걸리는 칸들. */\n searchables: string[]\n /** 없앨 때 행을 지우지 않고 `effectiveEnd` 를 채우나. */\n hasEffectivePeriod: boolean\n}\n\nexport const MASTER_AXES: readonly MasterAxis[] = [\n { key: 'personnelClass', entity: PersonnelClass, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'person', entity: Person, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'equipmentClass', entity: EquipmentClass, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'equipment', entity: Equipment, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'stopReason', entity: StopReason, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'physicalAssetClass', entity: PhysicalAssetClass, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'physicalAsset', entity: PhysicalAsset, searchables: ['name', 'description', 'identity'], hasEffectivePeriod: true },\n { key: 'materialClass', entity: MaterialClass, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'materialDefinition', entity: MaterialDefinition, searchables: ['name', 'description', 'identity'], hasEffectivePeriod: true },\n { key: 'operationalLocation', entity: OperationalLocation, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'operationDefinition', entity: OperationDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true },\n { key: 'testSpecification', entity: TestSpecification, searchables: ['name', 'description'], hasEffectivePeriod: true },\n /*\n * 라우트와 레시피 (2026-08-31) — 커널에 `RouteDef`·`RecipeDef` 가 있고 `JobOrder.routeKey` 가 그것을\n * 가리키는데 **그 값이 가리킬 표가 없었다.** 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은\n * 작업지시에 라우트를 자동으로 붙일 수 없었다.\n */\n { key: 'routeDefinition', entity: RouteDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true },\n {\n key: 'recipeDefinition',\n entity: RecipeDefinition,\n /* 만드는 품목으로도 찾는다 — 「이 품목을 만드는 레시피」가 MES 가 지시마다 묻는 물음이다. */\n searchables: ['name', 'description', 'materialDefinitionName'],\n hasEffectivePeriod: true\n }\n]\n\nexport function masterAxisOf(key: string): MasterAxis {\n const axis = MASTER_AXES.find(candidate => candidate.key === key)\n\n if (!axis) {\n /* 없는 축을 조용히 지나치지 않는다 — 지나치면 그 축의 화면이 빈 목록을 보이고 아무도 모른다. */\n throw new Error(`masterAxisOf: unknown master axis \"${key}\"`)\n }\n\n return axis\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { OperationDefinition } from './operation-definition';
2
2
  import { OperationQuery } from './operation-query';
3
3
  import { OperationDefinitionResolver } from './operation-master-resolver';
4
+ import { OperationReadinessResolver } from './operation-readiness-resolver';
4
5
  export declare const entities: (typeof OperationDefinition)[];
5
- export declare const resolvers: (typeof OperationDefinitionResolver | typeof OperationQuery)[];
6
+ export declare const resolvers: (typeof OperationDefinitionResolver | typeof OperationQuery | typeof OperationReadinessResolver)[];
@@ -4,6 +4,7 @@ exports.resolvers = exports.entities = void 0;
4
4
  const operation_definition_1 = require("./operation-definition");
5
5
  const operation_query_1 = require("./operation-query");
6
6
  const operation_master_resolver_1 = require("./operation-master-resolver");
7
+ const operation_readiness_resolver_1 = require("./operation-readiness-resolver");
7
8
  exports.entities = [operation_definition_1.OperationDefinition];
8
- exports.resolvers = [operation_query_1.OperationQuery, operation_master_resolver_1.OperationDefinitionResolver];
9
+ exports.resolvers = [operation_query_1.OperationQuery, operation_master_resolver_1.OperationDefinitionResolver, operation_readiness_resolver_1.OperationReadinessResolver];
9
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/operation/index.ts"],"names":[],"mappings":";;;AAAA,iEAA4D;AAC5D,uDAAkD;AAClD,2EAAyE;AAE5D,QAAA,QAAQ,GAAG,CAAC,0CAAmB,CAAC,CAAA;AAChC,QAAA,SAAS,GAAG,CAAC,gCAAc,EAAE,uDAA2B,CAAC,CAAA","sourcesContent":["import { OperationDefinition } from './operation-definition'\nimport { OperationQuery } from './operation-query'\nimport { OperationDefinitionResolver } from './operation-master-resolver'\n\nexport const entities = [OperationDefinition]\nexport const resolvers = [OperationQuery, OperationDefinitionResolver]\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/operation/index.ts"],"names":[],"mappings":";;;AAAA,iEAA4D;AAC5D,uDAAkD;AAClD,2EAAyE;AACzE,iFAA2E;AAE9D,QAAA,QAAQ,GAAG,CAAC,0CAAmB,CAAC,CAAA;AAChC,QAAA,SAAS,GAAG,CAAC,gCAAc,EAAE,uDAA2B,EAAE,yDAA0B,CAAC,CAAA","sourcesContent":["import { OperationDefinition } from './operation-definition'\nimport { OperationQuery } from './operation-query'\nimport { OperationDefinitionResolver } from './operation-master-resolver'\nimport { OperationReadinessResolver } from './operation-readiness-resolver'\n\nexport const entities = [OperationDefinition]\nexport const resolvers = [OperationQuery, OperationDefinitionResolver, OperationReadinessResolver]\n"]}
@@ -0,0 +1,39 @@
1
+ import { OperationDefinition } from './operation-definition';
2
+ export declare class PersonnelReadinessType {
3
+ classId: string;
4
+ required: number;
5
+ qualified: number;
6
+ /** 곧 끊기는 것을 따로 센다 — 합쳐 세면 만료가 닥쳐서야 알게 된다. */
7
+ expiringSoon: number;
8
+ sample: string[];
9
+ }
10
+ export declare class AssetReadinessType {
11
+ classId: string;
12
+ required: number;
13
+ /** 수명과 교정은 세지 않는다 — 이 화면이 답하는 물음은 「채울 자산이 있나」다. */
14
+ registered: number;
15
+ }
16
+ export declare class OperationReadinessType {
17
+ operation: OperationDefinition;
18
+ personnel: PersonnelReadinessType[];
19
+ assets: AssetReadinessType[];
20
+ /** 품목당 표준 시간(분). 선언되지 않았으면 없다 — 0 으로 두면 「즉시 끝난다」가 된다. */
21
+ standardMinutes?: number;
22
+ peopleTotal: number;
23
+ /**
24
+ * 그중 자격을 실제로 확인한 수.
25
+ *
26
+ * `peopleTotal` 보다 작으면 잘린 것이다. 화면이 그 사실을 말해야 한다 — 자른 것을 말하지 않으면
27
+ * 「자격자 3명」이 실제로는 더 많을 수 있다.
28
+ */
29
+ peopleScanned: number;
30
+ }
31
+ /**
32
+ * 선언 옆에 현실을 놓는다.
33
+ *
34
+ * 「용접 자격자 2명이 필요하다」는 선언이고, 그 선언이 지금 채워지는지는 다른 곳에 있었다. 두 화면을
35
+ * 오가며 사람이 머릿속으로 맞춰야 했고, 맞춰 보지 않으면 아무도 몰랐다.
36
+ */
37
+ export declare class OperationReadinessResolver {
38
+ operationReadiness(name: string, context: ResolverContext): Promise<OperationReadinessType | undefined>;
39
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OperationReadinessResolver = exports.OperationReadinessType = exports.AssetReadinessType = exports.PersonnelReadinessType = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const operation_definition_1 = require("./operation-definition");
8
+ const operation_readiness_1 = require("./operation-readiness");
9
+ let PersonnelReadinessType = class PersonnelReadinessType {
10
+ };
11
+ exports.PersonnelReadinessType = PersonnelReadinessType;
12
+ tslib_1.__decorate([
13
+ (0, type_graphql_1.Field)(),
14
+ tslib_1.__metadata("design:type", String)
15
+ ], PersonnelReadinessType.prototype, "classId", void 0);
16
+ tslib_1.__decorate([
17
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'how many the operation declares it needs' }),
18
+ tslib_1.__metadata("design:type", Number)
19
+ ], PersonnelReadinessType.prototype, "required", void 0);
20
+ tslib_1.__decorate([
21
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'how many people hold that qualification right now' }),
22
+ tslib_1.__metadata("design:type", Number)
23
+ ], PersonnelReadinessType.prototype, "qualified", void 0);
24
+ tslib_1.__decorate([
25
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'of those, how many lose it within 30 days' }),
26
+ tslib_1.__metadata("design:type", Number)
27
+ ], PersonnelReadinessType.prototype, "expiringSoon", void 0);
28
+ tslib_1.__decorate([
29
+ (0, type_graphql_1.Field)(type => [String], { description: 'a few names — not the whole list' }),
30
+ tslib_1.__metadata("design:type", Array)
31
+ ], PersonnelReadinessType.prototype, "sample", void 0);
32
+ exports.PersonnelReadinessType = PersonnelReadinessType = tslib_1.__decorate([
33
+ (0, type_graphql_1.ObjectType)({ description: 'A required personnel class, and how well it is covered right now' })
34
+ ], PersonnelReadinessType);
35
+ let AssetReadinessType = class AssetReadinessType {
36
+ };
37
+ exports.AssetReadinessType = AssetReadinessType;
38
+ tslib_1.__decorate([
39
+ (0, type_graphql_1.Field)(),
40
+ tslib_1.__metadata("design:type", String)
41
+ ], AssetReadinessType.prototype, "classId", void 0);
42
+ tslib_1.__decorate([
43
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int),
44
+ tslib_1.__metadata("design:type", Number)
45
+ ], AssetReadinessType.prototype, "required", void 0);
46
+ tslib_1.__decorate([
47
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'how many are registered and still in service — lifecycle is not counted here' }),
48
+ tslib_1.__metadata("design:type", Number)
49
+ ], AssetReadinessType.prototype, "registered", void 0);
50
+ exports.AssetReadinessType = AssetReadinessType = tslib_1.__decorate([
51
+ (0, type_graphql_1.ObjectType)({ description: 'A required asset class, and how many are registered' })
52
+ ], AssetReadinessType);
53
+ let OperationReadinessType = class OperationReadinessType {
54
+ };
55
+ exports.OperationReadinessType = OperationReadinessType;
56
+ tslib_1.__decorate([
57
+ (0, type_graphql_1.Field)(type => operation_definition_1.OperationDefinition),
58
+ tslib_1.__metadata("design:type", operation_definition_1.OperationDefinition)
59
+ ], OperationReadinessType.prototype, "operation", void 0);
60
+ tslib_1.__decorate([
61
+ (0, type_graphql_1.Field)(type => [PersonnelReadinessType]),
62
+ tslib_1.__metadata("design:type", Array)
63
+ ], OperationReadinessType.prototype, "personnel", void 0);
64
+ tslib_1.__decorate([
65
+ (0, type_graphql_1.Field)(type => [AssetReadinessType]),
66
+ tslib_1.__metadata("design:type", Array)
67
+ ], OperationReadinessType.prototype, "assets", void 0);
68
+ tslib_1.__decorate([
69
+ (0, type_graphql_1.Field)(type => type_graphql_1.Float, { nullable: true, description: 'standard time per item in minutes — absent when not declared' }),
70
+ tslib_1.__metadata("design:type", Number)
71
+ ], OperationReadinessType.prototype, "standardMinutes", void 0);
72
+ tslib_1.__decorate([
73
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'people registered in this domain' }),
74
+ tslib_1.__metadata("design:type", Number)
75
+ ], OperationReadinessType.prototype, "peopleTotal", void 0);
76
+ tslib_1.__decorate([
77
+ (0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'how many of them were actually checked' }),
78
+ tslib_1.__metadata("design:type", Number)
79
+ ], OperationReadinessType.prototype, "peopleScanned", void 0);
80
+ exports.OperationReadinessType = OperationReadinessType = tslib_1.__decorate([
81
+ (0, type_graphql_1.ObjectType)({ description: 'What one operation declares it needs, and whether that is met today' })
82
+ ], OperationReadinessType);
83
+ /**
84
+ * 선언 옆에 현실을 놓는다.
85
+ *
86
+ * 「용접 자격자 2명이 필요하다」는 선언이고, 그 선언이 지금 채워지는지는 다른 곳에 있었다. 두 화면을
87
+ * 오가며 사람이 머릿속으로 맞춰야 했고, 맞춰 보지 않으면 아무도 몰랐다.
88
+ */
89
+ let OperationReadinessResolver = class OperationReadinessResolver {
90
+ async operationReadiness(name, context) {
91
+ const { domain } = context.state;
92
+ return (0, operation_readiness_1.operationReadiness)((0, shell_1.getRepository)(operation_definition_1.OperationDefinition).manager, domain, name);
93
+ }
94
+ };
95
+ exports.OperationReadinessResolver = OperationReadinessResolver;
96
+ tslib_1.__decorate([
97
+ (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
98
+ (0, type_graphql_1.Query)(returns => OperationReadinessType, {
99
+ nullable: true,
100
+ description: 'To read what an operation requires and whether that is met today'
101
+ }),
102
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('name')),
103
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
104
+ tslib_1.__metadata("design:type", Function),
105
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
106
+ tslib_1.__metadata("design:returntype", Promise)
107
+ ], OperationReadinessResolver.prototype, "operationReadiness", null);
108
+ exports.OperationReadinessResolver = OperationReadinessResolver = tslib_1.__decorate([
109
+ (0, type_graphql_1.Resolver)()
110
+ ], OperationReadinessResolver);
111
+ //# sourceMappingURL=operation-readiness-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation-readiness-resolver.js","sourceRoot":"","sources":["../../../server/service/operation/operation-readiness-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Field, Float, Int, ObjectType, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { OperationDefinition } from './operation-definition'\nimport { operationReadiness } from './operation-readiness'\n\n@ObjectType({ description: 'A required personnel class, and how well it is covered right now' })\nexport class PersonnelReadinessType {\n @Field()\n classId: string\n\n @Field(type => Int, { description: 'how many the operation declares it needs' })\n required: number\n\n @Field(type => Int, { description: 'how many people hold that qualification right now' })\n qualified: number\n\n /** 곧 끊기는 것을 따로 센다 — 합쳐 세면 만료가 닥쳐서야 알게 된다. */\n @Field(type => Int, { description: 'of those, how many lose it within 30 days' })\n expiringSoon: number\n\n @Field(type => [String], { description: 'a few names — not the whole list' })\n sample: string[]\n}\n\n@ObjectType({ description: 'A required asset class, and how many are registered' })\nexport class AssetReadinessType {\n @Field()\n classId: string\n\n @Field(type => Int)\n required: number\n\n /** 수명과 교정은 세지 않는다 — 이 화면이 답하는 물음은 「채울 자산이 있나」다. */\n @Field(type => Int, { description: 'how many are registered and still in service — lifecycle is not counted here' })\n registered: number\n}\n\n@ObjectType({ description: 'What one operation declares it needs, and whether that is met today' })\nexport class OperationReadinessType {\n @Field(type => OperationDefinition)\n operation: OperationDefinition\n\n @Field(type => [PersonnelReadinessType])\n personnel: PersonnelReadinessType[]\n\n @Field(type => [AssetReadinessType])\n assets: AssetReadinessType[]\n\n /** 품목당 표준 시간(분). 선언되지 않았으면 없다 — 0 으로 두면 「즉시 끝난다」가 된다. */\n @Field(type => Float, { nullable: true, description: 'standard time per item in minutes — absent when not declared' })\n standardMinutes?: number\n\n @Field(type => Int, { description: 'people registered in this domain' })\n peopleTotal: number\n\n /**\n * 그중 자격을 실제로 확인한 수.\n *\n * `peopleTotal` 보다 작으면 잘린 것이다. 화면이 그 사실을 말해야 한다 — 자른 것을 말하지 않으면\n * 「자격자 3명」이 실제로는 더 많을 수 있다.\n */\n @Field(type => Int, { description: 'how many of them were actually checked' })\n peopleScanned: number\n}\n\n/**\n * 선언 옆에 현실을 놓는다.\n *\n * 「용접 자격자 2명이 필요하다」는 선언이고, 그 선언이 지금 채워지는지는 다른 곳에 있었다. 두 화면을\n * 오가며 사람이 머릿속으로 맞춰야 했고, 맞춰 보지 않으면 아무도 몰랐다.\n */\n@Resolver()\nexport class OperationReadinessResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => OperationReadinessType, {\n nullable: true,\n description: 'To read what an operation requires and whether that is met today'\n })\n async operationReadiness(\n @Arg('name') name: string,\n @Ctx() context: ResolverContext\n ): Promise<OperationReadinessType | undefined> {\n const { domain } = context.state\n\n return operationReadiness(getRepository(OperationDefinition).manager, domain, name)\n }\n}\n"]}
@@ -0,0 +1,34 @@
1
+ import { EntityManager } from 'typeorm';
2
+ import { Domain } from '@things-factory/shell';
3
+ import { OperationDefinition } from './operation-definition';
4
+ export interface PersonnelReadiness {
5
+ classId: string;
6
+ required: number;
7
+ /** 지금 이 등급의 자격을 갖춘 인원 수. */
8
+ qualified: number;
9
+ /** 그중 곧 자격이 끊기는 인원 수. */
10
+ expiringSoon: number;
11
+ /** 보여 줄 이름 몇 개. 전부 내려보내지 않는다. */
12
+ sample: string[];
13
+ }
14
+ export interface AssetReadiness {
15
+ classId: string;
16
+ required: number;
17
+ /**
18
+ * 이 등급으로 등록된 자산 수.
19
+ *
20
+ * **수명과 교정은 세지 않는다.** 그것을 세려면 자산마다 사용 횟수를 물어야 하고, 이 화면이 답하는
21
+ * 물음은 「선언을 채울 자산이 있나」이지 「지금 쓸 수 있나」가 아니다. 화면이 그 차이를 밝힌다.
22
+ */
23
+ registered: number;
24
+ }
25
+ export interface OperationReadiness {
26
+ operation: OperationDefinition;
27
+ personnel: PersonnelReadiness[];
28
+ assets: AssetReadiness[];
29
+ /** 품목당 표준 시간(분). 선언되지 않았으면 없다. */
30
+ standardMinutes?: number;
31
+ peopleTotal: number;
32
+ peopleScanned: number;
33
+ }
34
+ export declare function operationReadiness(manager: EntityManager, domain: Domain, operationName: string, now?: Date): Promise<OperationReadiness | undefined>;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.operationReadiness = operationReadiness;
4
+ const ops_contract_1 = require("@operato/ops-contract");
5
+ const physical_asset_1 = require("../asset/physical-asset");
6
+ const physical_asset_class_1 = require("../asset/physical-asset-class");
7
+ const person_1 = require("../personnel/person");
8
+ const personnel_class_1 = require("../personnel/personnel-class");
9
+ const qualification_1 = require("../personnel/qualification");
10
+ const resource_class_1 = require("../resource/resource-class");
11
+ const operation_definition_1 = require("./operation-definition");
12
+ /**
13
+ * 공정 하나의 **선언과 현재 채움 상태**.
14
+ *
15
+ * ── 왜 둘을 한 화면에서 보나 ───────────────────────────────────────────────
16
+ * 「용접 자격자 2명이 필요하다」는 선언이다. 그 선언이 지금 채워지는지는 다른 화면에 있다. 표로 나눠
17
+ * 두면 사람이 두 화면을 오가며 머릿속으로 맞춰야 하고, 맞춰 보지 않으면 아무도 모른다.
18
+ *
19
+ * 선언을 적는 자리에서 **그 선언이 지금 성립하는지** 함께 보인다.
20
+ *
21
+ * ── 곧 끊기는 것을 따로 센다 ───────────────────────────────────────────────
22
+ * 「자격자 3명」과 「자격자 3명인데 그중 1명이 다음 주 만료」는 다른 상황이다. 합쳐 세면 만료가
23
+ * 닥쳐서야 알게 된다.
24
+ *
25
+ * ── 조용히 자르지 않는다 ───────────────────────────────────────────────────
26
+ * 자격은 사람마다 계산해야 한다(등급 상속과 검사 만료를 봐야 하므로). 인원이 아주 많으면 상한까지만
27
+ * 보고, **몇 명 중 몇 명을 봤는지 함께 낸다.**
28
+ */
29
+ /** 한 번에 자격을 확인하는 인원 수의 상한. */
30
+ const MAX_PEOPLE_SCANNED = 2000;
31
+ /** 이 기간 안에 끊기는 자격을 「곧 만료」로 센다. */
32
+ const EXPIRING_SOON_DAYS = 30;
33
+ async function operationReadiness(manager, domain, operationName, now = new Date()) {
34
+ const operation = await manager.findOne(operation_definition_1.OperationDefinition, {
35
+ where: { domain: { id: domain.id }, name: operationName }
36
+ });
37
+ if (!operation) {
38
+ return undefined;
39
+ }
40
+ const at = now.toISOString();
41
+ const spec = (0, qualification_1.personnelSpecificationOf)(operation.personnelSpecification);
42
+ const [personnelClasses, people] = await Promise.all([
43
+ manager.find(personnel_class_1.PersonnelClass, { where: { domain: { id: domain.id } } }),
44
+ spec.length
45
+ ? manager.find(person_1.Person, { where: { domain: { id: domain.id } }, take: MAX_PEOPLE_SCANNED })
46
+ : Promise.resolve([])
47
+ ]);
48
+ const peopleTotal = spec.length ? await manager.count(person_1.Person, { where: { domain: { id: domain.id } } }) : 0;
49
+ const classDefs = personnelClasses.map(resource_class_1.toResourceClassDef);
50
+ const soonMs = now.getTime() + EXPIRING_SOON_DAYS * 24 * 3_600_000;
51
+ const personnel = spec.map(({ personnelClassId, quantity }) => {
52
+ const qualified = [];
53
+ let expiringSoon = 0;
54
+ for (const person of people) {
55
+ const judgeable = {
56
+ name: person.name,
57
+ personnelClassIds: person.personnelClassIds,
58
+ testResults: person.testResults,
59
+ effectiveStart: person.effectiveStart,
60
+ effectiveEnd: person.effectiveEnd
61
+ };
62
+ if ((0, qualification_1.qualificationGapsFor)(judgeable, personnelClassId, classDefs, at).length) {
63
+ continue;
64
+ }
65
+ qualified.push(person.name);
66
+ const expiry = (0, qualification_1.nearestQualificationExpiry)(judgeable, personnelClassId, classDefs, at);
67
+ if (expiry && Date.parse(expiry) <= soonMs) {
68
+ expiringSoon += 1;
69
+ }
70
+ }
71
+ return {
72
+ classId: personnelClassId,
73
+ required: quantity ?? 1,
74
+ qualified: qualified.length,
75
+ expiringSoon,
76
+ sample: qualified.slice(0, 5)
77
+ };
78
+ });
79
+ return {
80
+ operation,
81
+ personnel,
82
+ assets: await assetReadiness(manager, domain, operation, at),
83
+ standardMinutes: minutesOf(operation.duration),
84
+ peopleTotal,
85
+ peopleScanned: people.length
86
+ };
87
+ }
88
+ /** 선언된 요구 자산을 읽는다. 등급 이름이 없는 줄은 걸러 낸다 — 아무도 못 채우는 요구가 된다. */
89
+ function assetSpecificationOf(raw) {
90
+ const entries = [];
91
+ for (const row of raw ?? []) {
92
+ const classId = row?.physicalAssetClassId ?? row?.assetClassId ?? row?.classId;
93
+ if (typeof classId !== 'string' || !classId) {
94
+ continue;
95
+ }
96
+ const quantity = Number(row?.quantity);
97
+ entries.push({ classId, quantity: Number.isFinite(quantity) && quantity > 0 ? quantity : 1 });
98
+ }
99
+ return entries;
100
+ }
101
+ async function assetReadiness(manager, domain, operation, at) {
102
+ const spec = assetSpecificationOf(operation.physicalAssetSpecification);
103
+ if (!spec.length) {
104
+ return [];
105
+ }
106
+ const [classes, assets] = await Promise.all([
107
+ manager.find(physical_asset_class_1.PhysicalAssetClass, { where: { domain: { id: domain.id } } }),
108
+ manager.find(physical_asset_1.PhysicalAsset, { where: { domain: { id: domain.id } } })
109
+ ]);
110
+ const classDefs = classes.map(resource_class_1.toResourceClassDef);
111
+ return spec.map(({ classId, quantity }) => {
112
+ let registered = 0;
113
+ for (const asset of assets) {
114
+ const period = { effectiveStart: iso(asset.effectiveStart), effectiveEnd: iso(asset.effectiveEnd) };
115
+ if ((0, ops_contract_1.effectivityAt)(period, at) !== undefined) {
116
+ continue;
117
+ }
118
+ if ((0, ops_contract_1.classClosure)(asset.assetClassIds, classDefs, at).has(classId)) {
119
+ registered += 1;
120
+ }
121
+ }
122
+ return { classId, required: quantity, registered };
123
+ });
124
+ }
125
+ /** ISO 8601 기간을 분으로. 해석할 수 없으면 없다 — 0 분으로 두면 「즉시 끝난다」가 된다. */
126
+ function minutesOf(duration) {
127
+ const ms = (0, ops_contract_1.parseIsoDuration)(duration);
128
+ return ms == null ? undefined : Math.round((ms / 60_000) * 10) / 10;
129
+ }
130
+ function iso(at) {
131
+ return at ? new Date(at).toISOString() : undefined;
132
+ }
133
+ //# sourceMappingURL=operation-readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation-readiness.js","sourceRoot":"","sources":["../../../server/service/operation/operation-readiness.ts"],"names":[],"mappings":";;AAuEA,gDAsEC;AA1ID,wDAAqF;AAGrF,4DAAuD;AACvD,wEAAkE;AAClE,gDAA4C;AAC5C,kEAA6D;AAC7D,8DAAuH;AACvH,+DAA+D;AAC/D,iEAA4D;AAE5D;;;;;;;;;;;;;;;;GAgBG;AAEH,8BAA8B;AAC9B,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAE/B,kCAAkC;AAClC,MAAM,kBAAkB,GAAG,EAAE,CAAA;AAmCtB,KAAK,UAAU,kBAAkB,CACtC,OAAsB,EACtB,MAAc,EACd,aAAqB,EACrB,GAAG,GAAG,IAAI,IAAI,EAAE;IAEhB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,0CAAmB,EAAE;QAC3D,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;KAC1D,CAAC,CAAA;IAEF,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;IAC5B,MAAM,IAAI,GAAG,IAAA,wCAAwB,EAAC,SAAS,CAAC,sBAAsB,CAAC,CAAA;IAEvE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,gCAAc,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC;QACtE,IAAI,CAAC,MAAM;YACT,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,eAAM,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;YAC1F,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAc,CAAC;KACpC,CAAC,CAAA;IAEF,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,eAAM,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3G,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,mCAAkB,CAAC,CAAA;IAC1D,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,kBAAkB,GAAG,EAAE,GAAG,SAAS,CAAA;IAElE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC5D,MAAM,SAAS,GAAa,EAAE,CAAA;QAC9B,IAAI,YAAY,GAAG,CAAC,CAAA;QAEpB,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG;gBAChB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;gBAC3C,WAAW,EAAE,MAAM,CAAC,WAAkD;gBACtE,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,YAAY,EAAE,MAAM,CAAC,YAAY;aAClC,CAAA;YAED,IAAI,IAAA,oCAAoB,EAAC,SAAS,EAAE,gBAAgB,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;gBAC5E,SAAQ;YACV,CAAC;YAED,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAE3B,MAAM,MAAM,GAAG,IAAA,0CAA0B,EAAC,SAAS,EAAE,gBAAgB,EAAE,SAAS,EAAE,EAAE,CAAC,CAAA;YACrF,IAAI,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;gBAC3C,YAAY,IAAI,CAAC,CAAA;YACnB,CAAC;QACH,CAAC;QAED,OAAO;YACL,OAAO,EAAE,gBAAgB;YACzB,QAAQ,EAAE,QAAQ,IAAI,CAAC;YACvB,SAAS,EAAE,SAAS,CAAC,MAAM;YAC3B,YAAY;YACZ,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SAC9B,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,OAAO;QACL,SAAS;QACT,SAAS;QACT,MAAM,EAAE,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;QAC5D,eAAe,EAAE,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC;QAC9C,WAAW;QACX,aAAa,EAAE,MAAM,CAAC,MAAM;KAC7B,CAAA;AACH,CAAC;AAED,6DAA6D;AAC7D,SAAS,oBAAoB,CAAC,GAAmD;IAC/E,MAAM,OAAO,GAA4C,EAAE,CAAA;IAE3D,KAAK,MAAM,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,GAAG,EAAE,oBAAoB,IAAI,GAAG,EAAE,YAAY,IAAI,GAAG,EAAE,OAAO,CAAA;QAC9E,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;YAC5C,SAAQ;QACV,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QACtC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC/F,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,OAAsB,EACtB,MAAc,EACd,SAA8B,EAC9B,EAAU;IAEV,MAAM,IAAI,GAAG,oBAAoB,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAA;IACvE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,yCAAkB,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,8BAAa,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC;KACtE,CAAC,CAAA;IAEF,MAAM,SAAS,GAAuB,OAAO,CAAC,GAAG,CAAC,mCAAkB,CAAC,CAAA;IAErE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;QACxC,IAAI,UAAU,GAAG,CAAC,CAAA;QAElB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,EAAE,cAAc,EAAE,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAA;YACnG,IAAI,IAAA,4BAAa,EAAC,MAAM,EAAE,EAAE,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC5C,SAAQ;YACV,CAAC;YACD,IAAI,IAAA,2BAAY,EAAC,KAAK,CAAC,aAAa,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClE,UAAU,IAAI,CAAC,CAAA;YACjB,CAAC;QACH,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;IACpD,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,8DAA8D;AAC9D,SAAS,SAAS,CAAC,QAA4B;IAC7C,MAAM,EAAE,GAAG,IAAA,+BAAgB,EAAC,QAAQ,CAAC,CAAA;IAErC,OAAO,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAA;AACrE,CAAC;AAED,SAAS,GAAG,CAAC,EAAoB;IAC/B,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AACpD,CAAC","sourcesContent":["import { EntityManager } from 'typeorm'\n\nimport { Domain } from '@things-factory/shell'\nimport { classClosure, effectivityAt, parseIsoDuration } from '@operato/ops-contract'\nimport type { ResourceClassDef, TestResult } from '@operato/ops-contract'\n\nimport { PhysicalAsset } from '../asset/physical-asset'\nimport { PhysicalAssetClass } from '../asset/physical-asset-class'\nimport { Person } from '../personnel/person'\nimport { PersonnelClass } from '../personnel/personnel-class'\nimport { nearestQualificationExpiry, personnelSpecificationOf, qualificationGapsFor } from '../personnel/qualification'\nimport { toResourceClassDef } from '../resource/resource-class'\nimport { OperationDefinition } from './operation-definition'\n\n/**\n * 공정 하나의 **선언과 현재 채움 상태**.\n *\n * ── 왜 둘을 한 화면에서 보나 ───────────────────────────────────────────────\n * 「용접 자격자 2명이 필요하다」는 선언이다. 그 선언이 지금 채워지는지는 다른 화면에 있다. 표로 나눠\n * 두면 사람이 두 화면을 오가며 머릿속으로 맞춰야 하고, 맞춰 보지 않으면 아무도 모른다.\n *\n * 선언을 적는 자리에서 **그 선언이 지금 성립하는지** 함께 보인다.\n *\n * ── 곧 끊기는 것을 따로 센다 ───────────────────────────────────────────────\n * 「자격자 3명」과 「자격자 3명인데 그중 1명이 다음 주 만료」는 다른 상황이다. 합쳐 세면 만료가\n * 닥쳐서야 알게 된다.\n *\n * ── 조용히 자르지 않는다 ───────────────────────────────────────────────────\n * 자격은 사람마다 계산해야 한다(등급 상속과 검사 만료를 봐야 하므로). 인원이 아주 많으면 상한까지만\n * 보고, **몇 명 중 몇 명을 봤는지 함께 낸다.**\n */\n\n/** 한 번에 자격을 확인하는 인원 수의 상한. */\nconst MAX_PEOPLE_SCANNED = 2000\n\n/** 이 기간 안에 끊기는 자격을 「곧 만료」로 센다. */\nconst EXPIRING_SOON_DAYS = 30\n\nexport interface PersonnelReadiness {\n classId: string\n required: number\n /** 지금 이 등급의 자격을 갖춘 인원 수. */\n qualified: number\n /** 그중 곧 자격이 끊기는 인원 수. */\n expiringSoon: number\n /** 보여 줄 이름 몇 개. 전부 내려보내지 않는다. */\n sample: string[]\n}\n\nexport interface AssetReadiness {\n classId: string\n required: number\n /**\n * 이 등급으로 등록된 자산 수.\n *\n * **수명과 교정은 세지 않는다.** 그것을 세려면 자산마다 사용 횟수를 물어야 하고, 이 화면이 답하는\n * 물음은 「선언을 채울 자산이 있나」이지 「지금 쓸 수 있나」가 아니다. 화면이 그 차이를 밝힌다.\n */\n registered: number\n}\n\nexport interface OperationReadiness {\n operation: OperationDefinition\n personnel: PersonnelReadiness[]\n assets: AssetReadiness[]\n /** 품목당 표준 시간(분). 선언되지 않았으면 없다. */\n standardMinutes?: number\n peopleTotal: number\n peopleScanned: number\n}\n\nexport async function operationReadiness(\n manager: EntityManager,\n domain: Domain,\n operationName: string,\n now = new Date()\n): Promise<OperationReadiness | undefined> {\n const operation = await manager.findOne(OperationDefinition, {\n where: { domain: { id: domain.id }, name: operationName }\n })\n\n if (!operation) {\n return undefined\n }\n\n const at = now.toISOString()\n const spec = personnelSpecificationOf(operation.personnelSpecification)\n\n const [personnelClasses, people] = await Promise.all([\n manager.find(PersonnelClass, { where: { domain: { id: domain.id } } }),\n spec.length\n ? manager.find(Person, { where: { domain: { id: domain.id } }, take: MAX_PEOPLE_SCANNED })\n : Promise.resolve([] as Person[])\n ])\n\n const peopleTotal = spec.length ? await manager.count(Person, { where: { domain: { id: domain.id } } }) : 0\n const classDefs = personnelClasses.map(toResourceClassDef)\n const soonMs = now.getTime() + EXPIRING_SOON_DAYS * 24 * 3_600_000\n\n const personnel = spec.map(({ personnelClassId, quantity }) => {\n const qualified: string[] = []\n let expiringSoon = 0\n\n for (const person of people) {\n const judgeable = {\n name: person.name,\n personnelClassIds: person.personnelClassIds,\n testResults: person.testResults as unknown as TestResult[] | undefined,\n effectiveStart: person.effectiveStart,\n effectiveEnd: person.effectiveEnd\n }\n\n if (qualificationGapsFor(judgeable, personnelClassId, classDefs, at).length) {\n continue\n }\n\n qualified.push(person.name)\n\n const expiry = nearestQualificationExpiry(judgeable, personnelClassId, classDefs, at)\n if (expiry && Date.parse(expiry) <= soonMs) {\n expiringSoon += 1\n }\n }\n\n return {\n classId: personnelClassId,\n required: quantity ?? 1,\n qualified: qualified.length,\n expiringSoon,\n sample: qualified.slice(0, 5)\n }\n })\n\n return {\n operation,\n personnel,\n assets: await assetReadiness(manager, domain, operation, at),\n standardMinutes: minutesOf(operation.duration),\n peopleTotal,\n peopleScanned: people.length\n }\n}\n\n/** 선언된 요구 자산을 읽는다. 등급 이름이 없는 줄은 걸러 낸다 — 아무도 못 채우는 요구가 된다. */\nfunction assetSpecificationOf(raw: readonly Record<string, unknown>[] | undefined) {\n const entries: { classId: string; quantity: number }[] = []\n\n for (const row of raw ?? []) {\n const classId = row?.physicalAssetClassId ?? row?.assetClassId ?? row?.classId\n if (typeof classId !== 'string' || !classId) {\n continue\n }\n const quantity = Number(row?.quantity)\n entries.push({ classId, quantity: Number.isFinite(quantity) && quantity > 0 ? quantity : 1 })\n }\n\n return entries\n}\n\nasync function assetReadiness(\n manager: EntityManager,\n domain: Domain,\n operation: OperationDefinition,\n at: string\n): Promise<AssetReadiness[]> {\n const spec = assetSpecificationOf(operation.physicalAssetSpecification)\n if (!spec.length) {\n return []\n }\n\n const [classes, assets] = await Promise.all([\n manager.find(PhysicalAssetClass, { where: { domain: { id: domain.id } } }),\n manager.find(PhysicalAsset, { where: { domain: { id: domain.id } } })\n ])\n\n const classDefs: ResourceClassDef[] = classes.map(toResourceClassDef)\n\n return spec.map(({ classId, quantity }) => {\n let registered = 0\n\n for (const asset of assets) {\n const period = { effectiveStart: iso(asset.effectiveStart), effectiveEnd: iso(asset.effectiveEnd) }\n if (effectivityAt(period, at) !== undefined) {\n continue\n }\n if (classClosure(asset.assetClassIds, classDefs, at).has(classId)) {\n registered += 1\n }\n }\n\n return { classId, required: quantity, registered }\n })\n}\n\n/** ISO 8601 기간을 분으로. 해석할 수 없으면 없다 — 0 분으로 두면 「즉시 끝난다」가 된다. */\nfunction minutesOf(duration: string | undefined): number | undefined {\n const ms = parseIsoDuration(duration)\n\n return ms == null ? undefined : Math.round((ms / 60_000) * 10) / 10\n}\n\nfunction iso(at: Date | undefined): string | undefined {\n return at ? new Date(at).toISOString() : undefined\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import { EntityManager } from 'typeorm';
2
+ import { Domain } from '@things-factory/shell';
3
+ import { PersonnelClass } from './personnel-class';
4
+ export interface RequiringOperation {
5
+ operationName: string;
6
+ /** 그 공정이 이 등급으로 몇 명을 요구하나. */
7
+ required: number;
8
+ }
9
+ export interface PersonnelClassReadiness {
10
+ personnelClass: PersonnelClass;
11
+ /** 이 등급을 상속하는 하위 등급들 — 이들의 자격자도 이 등급을 만족시킨다. */
12
+ descendants: string[];
13
+ /** 지금 이 자격을 갖춘 인원. */
14
+ holders: string[];
15
+ /** 그중 곧 끊기는 인원 수. */
16
+ expiringSoon: number;
17
+ /** 이 등급을 요구하는 공정들 — 역방향이다. */
18
+ requiredBy: RequiringOperation[];
19
+ peopleTotal: number;
20
+ peopleScanned: number;
21
+ }
22
+ export declare function personnelClassReadiness(manager: EntityManager, domain: Domain, name: string, now?: Date): Promise<PersonnelClassReadiness | undefined>;