@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
@@ -1 +1 @@
1
- {"version":3,"file":"recipe-definition.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-definition.ts"],"names":[],"mappings":"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","sourcesContent":["import { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/**\n * 레시피 — **무엇으로 무엇을 만드나**. 커널 `RecipeDef` 와 같은 모양이다.\n *\n * ── 이 자리가 답하는 것 ──────────────────────────────────────────────────\n * 「이 품목은 무엇으로 만드나」와 「이 품목을 만드는 것은 어느 레시피인가」다. 둘째가 MES 가 물어야 했던\n * 것인데 물을 곳이 없었다.\n *\n * ── 품목에 지시서를 달지 않는 이유 ───────────────────────────────────────\n * ISA-95 에는 자재 명세가 붙는 자리가 둘이고 뜻이 다르다(§`RecipePart.operation`).\n *\n * · `ProcessSegment` — 그 자리가 **할 수 있는 일**. 품목에 매이지 않는다 → `OperationDefinition`\n * · `OperationsSegment` — 그 **품목을 만드는 절차**의 한 단계. 품목에 매인다 → **레시피가 그 범위다**\n *\n * 그래서 품목→지시서를 이으려면 그 사이에 레시피가 있어야 한다. 공정에 품목을 붙이면 범주 오류이고,\n * 실 데이터가 그 크기를 보였다 — 「배합」에 품목별 BOM 을 붙이면 배합을 지나는 **모든** 로트가 자재\n * 631종 36톤을 먹는다.\n *\n * ── 같은 품목에 레시피가 둘일 수 있다 ────────────────────────────────────\n * 대체 레시피가 정상이다(첫 실 연동에서 라우팅이 둘 이상인 레시피가 306/471). 그래서 **품목이 레시피를\n * 가리키지 않고 레시피가 품목을 가리킨다** — 품목은 하나를 고를 수 없다.\n */\n@Entity({ name: 'ops_recipe_definition' })\n@Index('ix_ops_recipe_definition_0', (recipe: RecipeDefinition) => [recipe.domain, recipe.name], { unique: true })\n/* 「이 품목을 만드는 레시피」로 묻는 질의가 이 축으로 온다 — MES 가 지시를 받을 때마다 부른다. */\n@Index('ix_ops_recipe_definition_1', (recipe: RecipeDefinition) => [recipe.domain, recipe.materialDefinitionName])\n@ObjectType({ description: 'Recipe — what a material is made of and through which route (kernel RecipeDef).' })\nexport class RecipeDefinition {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((recipe: RecipeDefinition) => recipe.domain)\n domainId?: string\n\n /** 레시피 키 — 오더의 `recipeKey` 가 이 값을 가리킨다(커널 `RecipeDef.key`). */\n @Column()\n @Field()\n name: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n /**\n * **무엇을 만드나** — `MaterialDefinition.name`.\n *\n * `outputs` 안에도 있지만 이 칸을 따로 둔다. 「이 품목을 만드는 레시피」를 JSON 안을 뒤져 찾으면\n * 다섯 드라이버에서 색인이 서지 않고, 그 질의는 MES 가 지시를 받을 때마다 부른다.\n *\n * 산출이 여럿인 레시피(부산물)에서는 **주 산출**을 적는다. 나머지는 `outputs` 가 든다.\n */\n @Column()\n @Field()\n materialDefinitionName: string\n\n /**\n * 투입 — 커널 `RecipePart[]`(`material` · `qty` · `operation?`).\n *\n * `operation` 은 **이 투입이 들어가는 공정**이다. 없으면 오더 착수에 확보한다. 그 태그가 여기 있는\n * 이유는 품목별 소요가 사는 자리가 레시피이기 때문이다.\n */\n @Column({ type: 'simple-json' })\n @Field(type => ScalarObject, { description: 'RecipePart[] — material · qty · operation?' })\n inputs: Record<string, unknown>[]\n\n /** 산출 — 커널 `RecipePart[]`. 부산물이 있으면 여기 함께 든다. */\n @Column({ type: 'simple-json' })\n @Field(type => ScalarObject, { description: 'RecipePart[]' })\n outputs: Record<string, unknown>[]\n\n /**\n * 이 산출물을 만드는 공정 라우팅 — `RouteDefinition.name`(커널 `RecipeDef.route`).\n *\n * 순서는 라우팅이 소유하고 레시피는 그것을 가리킨다. 레시피에 단계 목록을 또 두면 두 목록이 어긋날 수\n * 있고, 어긋났을 때 어느 쪽이 맞는지 말할 근거가 없다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true })\n route?: string\n\n /** 언제부터 유효한가 — 표준 `EffectiveStartDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveStart?: Date\n\n /**\n * 언제까지 유효한가 — 표준 `EffectiveEndDate`.\n *\n * **개정할 때 행을 고치지 않고 이 칸을 채우고 새 행을 세운다.** 고치면 그 레시피로 만든 지난 실적이\n * 어느 배합으로 만든 것인지 잃는다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveEnd?: Date\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((recipe: RecipeDefinition) => recipe.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((recipe: RecipeDefinition) => recipe.updater)\n updaterId?: string\n}\n"]}
1
+ {"version":3,"file":"recipe-definition.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-definition.ts"],"names":[],"mappings":"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","sourcesContent":["import { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\nimport { Field, ID, Int, ObjectType } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/**\n * 레시피 — **무엇으로 무엇을 만드나**. 커널 `RecipeDef` 와 같은 모양이다.\n *\n * ── 이 자리가 답하는 것 ──────────────────────────────────────────────────\n * 「이 품목은 무엇으로 만드나」와 「이 품목을 만드는 것은 어느 레시피인가」다. 둘째가 MES 가 물어야 했던\n * 것인데 물을 곳이 없었다.\n *\n * ── 품목에 지시서를 달지 않는 이유 ───────────────────────────────────────\n * ISA-95 에는 자재 명세가 붙는 자리가 둘이고 뜻이 다르다(§`RecipePart.operation`).\n *\n * · `ProcessSegment` — 그 자리가 **할 수 있는 일**. 품목에 매이지 않는다 → `OperationDefinition`\n * · `OperationsSegment` — 그 **품목을 만드는 절차**의 한 단계. 품목에 매인다 → **레시피가 그 범위다**\n *\n * 그래서 품목→지시서를 이으려면 그 사이에 레시피가 있어야 한다. 공정에 품목을 붙이면 범주 오류이고,\n * 실 데이터가 그 크기를 보였다 — 「배합」에 품목별 BOM 을 붙이면 배합을 지나는 **모든** 로트가 자재\n * 631종 36톤을 먹는다.\n *\n * ── 같은 품목에 레시피가 둘일 수 있다 ────────────────────────────────────\n * 대체 레시피가 정상이다(첫 실 연동에서 라우팅이 둘 이상인 레시피가 306/471). 그래서 **품목이 레시피를\n * 가리키지 않고 레시피가 품목을 가리킨다** — 품목은 하나를 고를 수 없다.\n */\n@Entity({ name: 'ops_recipe_definition' })\n@Index('ix_ops_recipe_domain_identity', ['domain', 'id'], { unique: true })\n@Index('ix_ops_recipe_definition_0', (recipe: RecipeDefinition) => [recipe.domain, recipe.name], { unique: true })\n/* 「이 품목을 만드는 레시피」로 묻는 질의가 이 축으로 온다 — MES 가 지시를 받을 때마다 부른다. */\n@Index('ix_ops_recipe_definition_1', (recipe: RecipeDefinition) => [recipe.domain, recipe.materialDefinitionName])\n@ObjectType({ description: 'Recipe — what a material is made of and through which route (kernel RecipeDef).' })\nexport class RecipeDefinition {\n /** Read projection of the effective revision, not a mutable identity column. */\n @Field(() => Int, { nullable: true })\n revisionNumber?: number\n @Column('int', { default: 0 }) planGeneration: number\n @Column('int', { default: 0 }) revisionLock: number\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((recipe: RecipeDefinition) => recipe.domain)\n domainId?: string\n\n /** 레시피 키 — 오더의 `recipeKey` 가 이 값을 가리킨다(커널 `RecipeDef.key`). */\n @Column()\n @Field()\n name: string\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n description?: string\n\n /**\n * **무엇을 만드나** — `MaterialDefinition.name`.\n *\n * `outputs` 안에도 있지만 이 칸을 따로 둔다. 「이 품목을 만드는 레시피」를 JSON 안을 뒤져 찾으면\n * 다섯 드라이버에서 색인이 서지 않고, 그 질의는 MES 가 지시를 받을 때마다 부른다.\n *\n * 산출이 여럿인 레시피(부산물)에서는 **주 산출**을 적는다. 나머지는 `outputs` 가 든다.\n */\n @Column()\n @Field({ nullable: true })\n materialDefinitionName: string\n\n /**\n * 투입 — 커널 `RecipePart[]`(`material` · `qty` · `operation?`).\n *\n * `operation` 은 **이 투입이 들어가는 공정**이다. 없으면 오더 착수에 확보한다. 그 태그가 여기 있는\n * 이유는 품목별 소요가 사는 자리가 레시피이기 때문이다.\n */\n @Column({ type: 'simple-json' })\n @Field(type => ScalarObject, { description: 'RecipePart[] — material · qty · operation?' })\n inputs: Record<string, unknown>[]\n\n /** 산출 — 커널 `RecipePart[]`. 부산물이 있으면 여기 함께 든다. */\n @Column({ type: 'simple-json' })\n @Field(type => ScalarObject, { description: 'RecipePart[]' })\n outputs: Record<string, unknown>[]\n\n /**\n * 이 산출물을 만드는 공정 라우팅 — `RouteDefinition.name`(커널 `RecipeDef.route`).\n *\n * 순서는 라우팅이 소유하고 레시피는 그것을 가리킨다. 레시피에 단계 목록을 또 두면 두 목록이 어긋날 수\n * 있고, 어긋났을 때 어느 쪽이 맞는지 말할 근거가 없다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true })\n route?: string\n\n /** 언제부터 유효한가 — 표준 `EffectiveStartDate`. */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveStart?: Date\n\n /**\n * 언제까지 유효한가 — 표준 `EffectiveEndDate`.\n *\n * **개정할 때 행을 고치지 않고 이 칸을 채우고 새 행을 세운다.** 고치면 그 레시피로 만든 지난 실적이\n * 어느 배합으로 만든 것인지 잃는다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true })\n effectiveEnd?: Date\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((recipe: RecipeDefinition) => recipe.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((recipe: RecipeDefinition) => recipe.updater)\n updaterId?: string\n}\n"]}
@@ -0,0 +1,49 @@
1
+ import { EntityManager } from 'typeorm';
2
+ import { Domain, ListParam } from '@things-factory/shell';
3
+ import { RecipeDefinition } from './recipe-definition';
4
+ /** Filter, sort, paginate and display the same time projection. No per-row resolution queries. */
5
+ export declare function listEffectiveRecipes(manager: EntityManager, domain: Domain, params: ListParam, at?: Date): Promise<{
6
+ items: (RecipeDefinition & {
7
+ revisionNumber?: number;
8
+ planGeneration: number;
9
+ revisionLock: number;
10
+ id: string;
11
+ domain?: Domain;
12
+ domainId?: string;
13
+ name: string;
14
+ description?: string;
15
+ materialDefinitionName: string;
16
+ inputs: Record<string, unknown>[];
17
+ outputs: Record<string, unknown>[];
18
+ route?: string;
19
+ effectiveStart?: Date;
20
+ effectiveEnd?: Date;
21
+ createdAt?: Date;
22
+ updatedAt?: Date;
23
+ creator?: import("@things-factory/auth-base").User;
24
+ creatorId?: string;
25
+ updater?: import("@things-factory/auth-base").User;
26
+ updaterId?: string;
27
+ } & {
28
+ description: string;
29
+ revisionNumber: number;
30
+ materialDefinitionName: string;
31
+ route: string;
32
+ inputs: Record<string, unknown>[];
33
+ outputs: Record<string, unknown>[];
34
+ effectiveStart: Date;
35
+ effectiveEnd: Date;
36
+ })[];
37
+ total: number;
38
+ }>;
39
+ /** Candidate selection retains future/expired declarations so callers can explain why a choice is unavailable.
40
+ * One joined read; no per-order or per-recipe resolution queries. Payload rows are not loaded here.
41
+ */
42
+ export declare function recipeChoicesAt(manager: EntityManager, domainId: string, materialNames: string[], at?: Date): Promise<{
43
+ id: string;
44
+ name: string;
45
+ materialDefinitionName: string;
46
+ route: string | undefined;
47
+ effectiveStart: Date;
48
+ effectiveEnd: Date | undefined;
49
+ }[]>;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listEffectiveRecipes = listEffectiveRecipes;
4
+ exports.recipeChoicesAt = recipeChoicesAt;
5
+ const typeorm_1 = require("typeorm");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const recipe_definition_1 = require("./recipe-definition");
8
+ const recipe_revision_1 = require("./recipe-revision");
9
+ const recipe_revision_line_1 = require("./recipe-revision-line");
10
+ /** Filter, sort, paginate and display the same time projection. No per-row resolution queries. */
11
+ async function listEffectiveRecipes(manager, domain, params, at = new Date()) {
12
+ const query = (0, shell_1.getQueryBuilderFromListParams)({
13
+ repository: manager.getRepository(recipe_definition_1.RecipeDefinition),
14
+ domain,
15
+ params,
16
+ alias: 'recipe',
17
+ searchables: ['name', 'description'],
18
+ sortables: ['id', 'name', 'revisionNumber', 'description', 'effectiveStart', 'effectiveEnd', 'createdAt', 'updatedAt'],
19
+ columnExpressions: {
20
+ revisionNumber: 'revision.revisionNumber',
21
+ description: 'revision.description',
22
+ materialDefinitionName: 'revision.materialDefinitionName',
23
+ route: 'revision.route',
24
+ effectiveStart: 'segment.validFrom',
25
+ effectiveEnd: 'segment.validTo'
26
+ }
27
+ });
28
+ query
29
+ .leftJoin(recipe_revision_1.RecipePlan, 'plan', 'plan.recipe_id = recipe.id AND plan.domain_id = recipe.domain_id AND plan.generation = recipe.plan_generation')
30
+ .leftJoinAndMapOne('recipe.selectedSegment', recipe_revision_1.RecipeSegment, 'segment', 'segment.plan_id = plan.id AND segment.validFrom <= :at AND (segment.validTo IS NULL OR segment.validTo > :at)', { at })
31
+ .leftJoinAndMapOne('recipe.selectedRevision', recipe_revision_1.RecipeRevision, 'revision', 'revision.id = segment.revision_id');
32
+ const [rows, total] = await query.getManyAndCount();
33
+ const revisionIds = rows.flatMap(row => row.selectedRevision ? [row.selectedRevision.id] : []);
34
+ const [inputs, outputs] = revisionIds.length ? await Promise.all([
35
+ manager.find(recipe_revision_line_1.RecipeRevisionInput, { where: { domain: { id: domain.id }, revision: { id: (0, typeorm_1.In)(revisionIds) } }, order: { position: 'ASC' } }),
36
+ manager.find(recipe_revision_line_1.RecipeRevisionOutput, { where: { domain: { id: domain.id }, revision: { id: (0, typeorm_1.In)(revisionIds) } }, order: { position: 'ASC' } })
37
+ ]) : [[], []];
38
+ const group = (lines) => {
39
+ const result = new Map();
40
+ for (const line of lines) {
41
+ const bucket = result.get(line.revisionId) ?? [];
42
+ bucket.push({ lineKey: line.lineKey, material: line.material, qty: line.qty,
43
+ ...('operation' in line && line.operation ? { operation: line.operation } : {}) });
44
+ result.set(line.revisionId, bucket);
45
+ }
46
+ return result;
47
+ };
48
+ const inputsByRevision = group(inputs), outputsByRevision = group(outputs);
49
+ const items = rows.map(row => {
50
+ const { selectedRevision, selectedSegment, ...identity } = row;
51
+ return Object.assign(new recipe_definition_1.RecipeDefinition(), identity, {
52
+ description: selectedRevision?.description ?? null,
53
+ revisionNumber: selectedRevision?.revisionNumber ?? null,
54
+ materialDefinitionName: selectedRevision?.materialDefinitionName ?? null,
55
+ route: selectedRevision?.route ?? null,
56
+ inputs: inputsByRevision.get(selectedRevision?.id) ?? [],
57
+ outputs: outputsByRevision.get(selectedRevision?.id) ?? [],
58
+ effectiveStart: selectedSegment?.validFrom ?? null,
59
+ effectiveEnd: selectedSegment?.validTo ?? null
60
+ });
61
+ });
62
+ return { items, total };
63
+ }
64
+ /** Candidate selection retains future/expired declarations so callers can explain why a choice is unavailable.
65
+ * One joined read; no per-order or per-recipe resolution queries. Payload rows are not loaded here.
66
+ */
67
+ async function recipeChoicesAt(manager, domainId, materialNames, at = new Date()) {
68
+ if (!materialNames.length)
69
+ return [];
70
+ const rows = await manager.createQueryBuilder(recipe_definition_1.RecipeDefinition, 'recipe')
71
+ .innerJoin(recipe_revision_1.RecipePlan, 'plan', 'plan.recipe_id = recipe.id AND plan.domain_id = recipe.domain_id AND plan.generation = recipe.plan_generation')
72
+ .innerJoin(recipe_revision_1.RecipeSegment, 'segment', 'segment.plan_id = plan.id')
73
+ .innerJoin(recipe_revision_1.RecipeRevision, 'revision', 'revision.id = segment.revision_id')
74
+ .where('recipe.domain_id = :domainId', { domainId })
75
+ .select('recipe.id', 'id').addSelect('recipe.name', 'name')
76
+ .addSelect('revision.materialDefinitionName', 'materialDefinitionName').addSelect('revision.route', 'route')
77
+ .addSelect('segment.validFrom', 'validFrom').addSelect('segment.validTo', 'validTo')
78
+ .getRawMany();
79
+ const byRecipe = new Map();
80
+ for (const row of rows) {
81
+ // Driver raw dates may be SQLite's UTC "YYYY-MM-DD HH:mm:ss.sss" representation.
82
+ const date = (value) => value instanceof Date ? value : new Date(/(?:Z|[+-]\d\d:\d\d)$/.test(value) ? value : value.replace(' ', 'T') + 'Z');
83
+ row.effectiveStart = date(row.validFrom);
84
+ row.effectiveEnd = row.validTo ? date(row.validTo) : undefined;
85
+ const group = byRecipe.get(row.id) ?? [];
86
+ group.push(row);
87
+ byRecipe.set(row.id, group);
88
+ }
89
+ const rank = (row) => row.effectiveStart > at ? 1 : row.effectiveEnd && row.effectiveEnd <= at ? 2 : 0;
90
+ return [...byRecipe.values()].map(group => group.sort((a, b) => rank(a) - rank(b) ||
91
+ (rank(a) === 1 ? a.effectiveStart - b.effectiveStart : b.effectiveStart - a.effectiveStart))[0])
92
+ .filter(row => materialNames.includes(row.materialDefinitionName))
93
+ .map(row => ({ id: row.id, name: row.name, materialDefinitionName: row.materialDefinitionName,
94
+ route: row.route, effectiveStart: row.effectiveStart, effectiveEnd: row.effectiveEnd }));
95
+ }
96
+ //# sourceMappingURL=recipe-effective-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-effective-list.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-effective-list.ts"],"names":[],"mappings":"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","sourcesContent":["import { EntityManager, In } from 'typeorm'\nimport { Domain, getQueryBuilderFromListParams, ListParam } from '@things-factory/shell'\nimport { RecipeDefinition } from './recipe-definition'\nimport { RecipePlan, RecipeRevision, RecipeSegment } from './recipe-revision'\n\nimport { RecipeRevisionInput, RecipeRevisionOutput } from './recipe-revision-line'\n\ntype EffectiveRow = RecipeDefinition & {\n selectedRevision?: RecipeRevision\n selectedSegment?: RecipeSegment\n}\n\n/** Filter, sort, paginate and display the same time projection. No per-row resolution queries. */\nexport async function listEffectiveRecipes(\n manager: EntityManager,\n domain: Domain,\n params: ListParam,\n at = new Date()\n) {\n const query = getQueryBuilderFromListParams({\n repository: manager.getRepository(RecipeDefinition),\n domain,\n params,\n alias: 'recipe',\n searchables: ['name', 'description'],\n sortables: ['id', 'name', 'revisionNumber', 'description', 'effectiveStart', 'effectiveEnd', 'createdAt', 'updatedAt'],\n columnExpressions: {\n revisionNumber: 'revision.revisionNumber',\n description: 'revision.description',\n materialDefinitionName: 'revision.materialDefinitionName',\n route: 'revision.route',\n effectiveStart: 'segment.validFrom',\n effectiveEnd: 'segment.validTo'\n }\n })\n query\n .leftJoin(\n RecipePlan,\n 'plan',\n 'plan.recipe_id = recipe.id AND plan.domain_id = recipe.domain_id AND plan.generation = recipe.plan_generation'\n )\n .leftJoinAndMapOne(\n 'recipe.selectedSegment',\n RecipeSegment,\n 'segment',\n 'segment.plan_id = plan.id AND segment.validFrom <= :at AND (segment.validTo IS NULL OR segment.validTo > :at)',\n { at }\n )\n .leftJoinAndMapOne(\n 'recipe.selectedRevision',\n RecipeRevision,\n 'revision',\n 'revision.id = segment.revision_id'\n )\n const [rows, total] = await query.getManyAndCount()\n const revisionIds = (rows as EffectiveRow[]).flatMap(row => row.selectedRevision ? [row.selectedRevision.id] : [])\n const [inputs, outputs] = revisionIds.length ? await Promise.all([\n manager.find(RecipeRevisionInput, { where: { domain: { id: domain.id }, revision: { id: In(revisionIds) } }, order: { position: 'ASC' } }),\n manager.find(RecipeRevisionOutput, { where: { domain: { id: domain.id }, revision: { id: In(revisionIds) } }, order: { position: 'ASC' } })\n ]) : [[], []]\n const group = (lines: (RecipeRevisionInput | RecipeRevisionOutput)[]) => {\n const result = new Map<string, Record<string, unknown>[]>()\n for (const line of lines) {\n const bucket = result.get(line.revisionId) ?? []\n bucket.push({ lineKey: line.lineKey, material: line.material, qty: line.qty,\n ...('operation' in line && line.operation ? { operation: line.operation } : {}) })\n result.set(line.revisionId, bucket)\n }\n return result\n }\n const inputsByRevision = group(inputs), outputsByRevision = group(outputs)\n const items = (rows as EffectiveRow[]).map(row => {\n const { selectedRevision, selectedSegment, ...identity } = row\n return Object.assign(new RecipeDefinition(), identity, {\n description: selectedRevision?.description ?? null,\n revisionNumber: selectedRevision?.revisionNumber ?? null,\n materialDefinitionName: selectedRevision?.materialDefinitionName ?? null,\n route: selectedRevision?.route ?? null,\n inputs: inputsByRevision.get(selectedRevision?.id) ?? [],\n outputs: outputsByRevision.get(selectedRevision?.id) ?? [],\n effectiveStart: selectedSegment?.validFrom ?? null,\n effectiveEnd: selectedSegment?.validTo ?? null\n })\n })\n return { items, total }\n}\n\n/** Candidate selection retains future/expired declarations so callers can explain why a choice is unavailable.\n * One joined read; no per-order or per-recipe resolution queries. Payload rows are not loaded here.\n */\nexport async function recipeChoicesAt(manager: EntityManager, domainId: string, materialNames: string[], at = new Date()) {\n if (!materialNames.length) return []\n const rows = await manager.createQueryBuilder(RecipeDefinition, 'recipe')\n .innerJoin(RecipePlan, 'plan', 'plan.recipe_id = recipe.id AND plan.domain_id = recipe.domain_id AND plan.generation = recipe.plan_generation')\n .innerJoin(RecipeSegment, 'segment', 'segment.plan_id = plan.id')\n .innerJoin(RecipeRevision, 'revision', 'revision.id = segment.revision_id')\n .where('recipe.domain_id = :domainId', { domainId })\n .select('recipe.id', 'id').addSelect('recipe.name', 'name')\n .addSelect('revision.materialDefinitionName', 'materialDefinitionName').addSelect('revision.route', 'route')\n .addSelect('segment.validFrom', 'validFrom').addSelect('segment.validTo', 'validTo')\n .getRawMany()\n const byRecipe = new Map<string, typeof rows>()\n for (const row of rows) {\n // Driver raw dates may be SQLite's UTC \"YYYY-MM-DD HH:mm:ss.sss\" representation.\n const date = (value: string | Date) => value instanceof Date ? value : new Date(/(?:Z|[+-]\\d\\d:\\d\\d)$/.test(value) ? value : value.replace(' ', 'T') + 'Z')\n row.effectiveStart = date(row.validFrom)\n row.effectiveEnd = row.validTo ? date(row.validTo) : undefined\n const group = byRecipe.get(row.id) ?? []\n group.push(row); byRecipe.set(row.id, group)\n }\n const rank = (row: any) => row.effectiveStart > at ? 1 : row.effectiveEnd && row.effectiveEnd <= at ? 2 : 0\n return [...byRecipe.values()].map(group => group.sort((a,b) => rank(a)-rank(b) ||\n (rank(a) === 1 ? a.effectiveStart-b.effectiveStart : b.effectiveStart-a.effectiveStart))[0])\n .filter(row => materialNames.includes(row.materialDefinitionName))\n .map(row => ({ id: row.id as string, name: row.name as string, materialDefinitionName: row.materialDefinitionName as string,\n route: row.route as string | undefined, effectiveStart: row.effectiveStart as Date, effectiveEnd: row.effectiveEnd as Date | undefined }))\n}\n"]}
@@ -4,6 +4,8 @@ exports.RouteDefinitionResolver = exports.RecipeDefinitionResolver = exports.Rou
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");
@@ -21,29 +23,29 @@ let RecipeDefinitionList = class RecipeDefinitionList {
21
23
  };
22
24
  exports.RecipeDefinitionList = RecipeDefinitionList;
23
25
  tslib_1.__decorate([
24
- (0, type_graphql_1.Field)(type => [recipe_definition_1.RecipeDefinition]),
26
+ (0, type_graphql_2.Field)(type => [recipe_definition_1.RecipeDefinition]),
25
27
  tslib_1.__metadata("design:type", Array)
26
28
  ], RecipeDefinitionList.prototype, "items", void 0);
27
29
  tslib_1.__decorate([
28
- (0, type_graphql_1.Field)(type => type_graphql_1.Int),
30
+ (0, type_graphql_2.Field)(type => type_graphql_2.Int),
29
31
  tslib_1.__metadata("design:type", Number)
30
32
  ], RecipeDefinitionList.prototype, "total", void 0);
31
33
  exports.RecipeDefinitionList = RecipeDefinitionList = tslib_1.__decorate([
32
- (0, type_graphql_1.ObjectType)({ description: 'A page of recipe definitions' })
34
+ (0, type_graphql_2.ObjectType)({ description: 'A page of recipe definitions' })
33
35
  ], RecipeDefinitionList);
34
36
  let RouteDefinitionList = class RouteDefinitionList {
35
37
  };
36
38
  exports.RouteDefinitionList = RouteDefinitionList;
37
39
  tslib_1.__decorate([
38
- (0, type_graphql_1.Field)(type => [route_definition_1.RouteDefinition]),
40
+ (0, type_graphql_2.Field)(type => [route_definition_1.RouteDefinition]),
39
41
  tslib_1.__metadata("design:type", Array)
40
42
  ], RouteDefinitionList.prototype, "items", void 0);
41
43
  tslib_1.__decorate([
42
- (0, type_graphql_1.Field)(type => type_graphql_1.Int),
44
+ (0, type_graphql_2.Field)(type => type_graphql_2.Int),
43
45
  tslib_1.__metadata("design:type", Number)
44
46
  ], RouteDefinitionList.prototype, "total", void 0);
45
47
  exports.RouteDefinitionList = RouteDefinitionList = tslib_1.__decorate([
46
- (0, type_graphql_1.ObjectType)({ description: 'A page of route definitions' })
48
+ (0, type_graphql_2.ObjectType)({ description: 'A page of route definitions' })
47
49
  ], RouteDefinitionList);
48
50
  let RecipeDefinitionResolver = class RecipeDefinitionResolver {
49
51
  async recipeDefinitions(params, context) {
@@ -62,26 +64,27 @@ let RecipeDefinitionResolver = class RecipeDefinitionResolver {
62
64
  };
63
65
  exports.RecipeDefinitionResolver = RecipeDefinitionResolver;
64
66
  tslib_1.__decorate([
65
- (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
66
- (0, type_graphql_1.Query)(returns => RecipeDefinitionList, { description: 'To fetch recipe definitions' }),
67
- tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
68
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
67
+ (0, type_graphql_2.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
68
+ (0, type_graphql_2.Query)(returns => RecipeDefinitionList, { description: 'To fetch recipe definitions' }),
69
+ tslib_1.__param(0, (0, type_graphql_2.Args)(type => shell_2.ListParam)),
70
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
69
71
  tslib_1.__metadata("design:type", Function),
70
- tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
72
+ tslib_1.__metadata("design:paramtypes", [shell_2.ListParam, Object]),
71
73
  tslib_1.__metadata("design:returntype", Promise)
72
74
  ], RecipeDefinitionResolver.prototype, "recipeDefinitions", null);
73
75
  tslib_1.__decorate([
74
- (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
75
- (0, type_graphql_1.Directive)('@transaction'),
76
- (0, type_graphql_1.Mutation)(returns => [recipe_definition_1.RecipeDefinition], { description: 'To create, change or retire recipe definitions' }),
77
- tslib_1.__param(0, (0, type_graphql_1.Arg)('patches', type => [shell_1.ScalarObject])),
78
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
76
+ (0, type_graphql_2.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
77
+ (0, type_graphql_2.Directive)('@transaction'),
78
+ (0, type_graphql_2.Mutation)(returns => [recipe_definition_1.RecipeDefinition], { description: 'To create, change or retire recipe definitions' }),
79
+ tslib_1.__param(0, (0, type_graphql_2.Arg)('patches', type => [shell_2.ScalarObject])),
80
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
79
81
  tslib_1.__metadata("design:type", Function),
80
82
  tslib_1.__metadata("design:paramtypes", [Array, Object]),
81
83
  tslib_1.__metadata("design:returntype", Promise)
82
84
  ], RecipeDefinitionResolver.prototype, "updateMultipleRecipeDefinition", null);
83
85
  exports.RecipeDefinitionResolver = RecipeDefinitionResolver = tslib_1.__decorate([
84
- (0, type_graphql_1.Resolver)(recipe_definition_1.RecipeDefinition)
86
+ (0, type_graphql_2.Resolver)(recipe_definition_1.RecipeDefinition),
87
+ (0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
85
88
  ], RecipeDefinitionResolver);
86
89
  let RouteDefinitionResolver = class RouteDefinitionResolver {
87
90
  async routeDefinitions(params, context) {
@@ -100,25 +103,26 @@ let RouteDefinitionResolver = class RouteDefinitionResolver {
100
103
  };
101
104
  exports.RouteDefinitionResolver = RouteDefinitionResolver;
102
105
  tslib_1.__decorate([
103
- (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
104
- (0, type_graphql_1.Query)(returns => RouteDefinitionList, { description: 'To fetch route definitions' }),
105
- tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
106
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
106
+ (0, type_graphql_2.Directive)('@privilege(category: "ops-master", privilege: "query", domainOwnerGranted: true)'),
107
+ (0, type_graphql_2.Query)(returns => RouteDefinitionList, { description: 'To fetch route definitions' }),
108
+ tslib_1.__param(0, (0, type_graphql_2.Args)(type => shell_2.ListParam)),
109
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
107
110
  tslib_1.__metadata("design:type", Function),
108
- tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
111
+ tslib_1.__metadata("design:paramtypes", [shell_2.ListParam, Object]),
109
112
  tslib_1.__metadata("design:returntype", Promise)
110
113
  ], RouteDefinitionResolver.prototype, "routeDefinitions", null);
111
114
  tslib_1.__decorate([
112
- (0, type_graphql_1.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
113
- (0, type_graphql_1.Directive)('@transaction'),
114
- (0, type_graphql_1.Mutation)(returns => [route_definition_1.RouteDefinition], { description: 'To create, change or retire route definitions' }),
115
- tslib_1.__param(0, (0, type_graphql_1.Arg)('patches', type => [shell_1.ScalarObject])),
116
- tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
115
+ (0, type_graphql_2.Directive)('@privilege(category: "ops-master", privilege: "mutation", domainOwnerGranted: true)'),
116
+ (0, type_graphql_2.Directive)('@transaction'),
117
+ (0, type_graphql_2.Mutation)(returns => [route_definition_1.RouteDefinition], { description: 'To create, change or retire route definitions' }),
118
+ tslib_1.__param(0, (0, type_graphql_2.Arg)('patches', type => [shell_2.ScalarObject])),
119
+ tslib_1.__param(1, (0, type_graphql_2.Ctx)()),
117
120
  tslib_1.__metadata("design:type", Function),
118
121
  tslib_1.__metadata("design:paramtypes", [Array, Object]),
119
122
  tslib_1.__metadata("design:returntype", Promise)
120
123
  ], RouteDefinitionResolver.prototype, "updateMultipleRouteDefinition", null);
121
124
  exports.RouteDefinitionResolver = RouteDefinitionResolver = tslib_1.__decorate([
122
- (0, type_graphql_1.Resolver)(route_definition_1.RouteDefinition)
125
+ (0, type_graphql_2.Resolver)(route_definition_1.RouteDefinition),
126
+ (0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
123
127
  ], RouteDefinitionResolver);
124
128
  //# sourceMappingURL=recipe-master-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"recipe-master-resolver.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-master-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Args, Arg, Ctx, Directive, Int, Mutation, ObjectType, Field, Query, Resolver } from 'type-graphql'\n\nimport { ListParam, ScalarObject } from '@things-factory/shell'\n\nimport { MasterPatch, saveMasterPatches } from '../master-crud'\nimport { masterAxisOf } from '../master-axis'\nimport { listMaster } from '../master-list'\nimport { RecipeDefinition } from './recipe-definition'\nimport { RouteDefinition } from './route-definition'\n\n/**\n * 레시피·라우팅 축을 채우고 고친다.\n *\n * 규칙은 `master-crud.ts` 한 곳에 있다. 여기는 축의 이름만 붙인다 — 다른 열셋과 같은 모양이다.\n *\n * **개정할 때 행을 고치지 않고 유효 기간의 끝을 채운다.** 고치면 그 레시피로 만든 지난 실적이 어느\n * 배합으로 만든 것인지 잃는다.\n */\n\n@ObjectType({ description: 'A page of recipe definitions' })\nexport class RecipeDefinitionList {\n @Field(type => [RecipeDefinition])\n items: RecipeDefinition[]\n\n @Field(type => Int)\n total: number\n}\n\n@ObjectType({ description: 'A page of route definitions' })\nexport class RouteDefinitionList {\n @Field(type => [RouteDefinition])\n items: RouteDefinition[]\n\n @Field(type => Int)\n total: number\n}\n\n@Resolver(RecipeDefinition)\nexport class RecipeDefinitionResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => RecipeDefinitionList, { description: 'To fetch recipe definitions' })\n async recipeDefinitions(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<RecipeDefinitionList> {\n const { domain } = context.state\n const { items, total } = await listMaster('recipeDefinition', params, domain)\n\n return { items: items as RecipeDefinition[], total }\n }\n\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [RecipeDefinition], { description: 'To create, change or retire recipe definitions' })\n async updateMultipleRecipeDefinition(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<RecipeDefinition[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('recipeDefinition')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as RecipeDefinition[]\n }\n}\n\n@Resolver(RouteDefinition)\nexport class RouteDefinitionResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => RouteDefinitionList, { description: 'To fetch route definitions' })\n async routeDefinitions(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<RouteDefinitionList> {\n const { domain } = context.state\n const { items, total } = await listMaster('routeDefinition', params, domain)\n\n return { items: items as RouteDefinition[], total }\n }\n\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [RouteDefinition], { description: 'To create, change or retire route definitions' })\n async updateMultipleRouteDefinition(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<RouteDefinition[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('routeDefinition')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as RouteDefinition[]\n }\n}\n"]}
1
+ {"version":3,"file":"recipe-master-resolver.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-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, Int, Mutation, ObjectType, Field, Query, Resolver } from 'type-graphql'\n\nimport { ListParam, ScalarObject } from '@things-factory/shell'\n\nimport { MasterPatch, saveMasterPatches } from '../master-crud'\nimport { masterAxisOf } from '../master-axis'\nimport { listMaster } from '../master-list'\nimport { RecipeDefinition } from './recipe-definition'\nimport { RouteDefinition } from './route-definition'\n\n/**\n * 레시피·라우팅 축을 채우고 고친다.\n *\n * 규칙은 `master-crud.ts` 한 곳에 있다. 여기는 축의 이름만 붙인다 — 다른 열셋과 같은 모양이다.\n *\n * **개정할 때 행을 고치지 않고 유효 기간의 끝을 채운다.** 고치면 그 레시피로 만든 지난 실적이 어느\n * 배합으로 만든 것인지 잃는다.\n */\n\n@ObjectType({ description: 'A page of recipe definitions' })\nexport class RecipeDefinitionList {\n @Field(type => [RecipeDefinition])\n items: RecipeDefinition[]\n\n @Field(type => Int)\n total: number\n}\n\n@ObjectType({ description: 'A page of route definitions' })\nexport class RouteDefinitionList {\n @Field(type => [RouteDefinition])\n items: RouteDefinition[]\n\n @Field(type => Int)\n total: number\n}\n\n@Resolver(RecipeDefinition)\n@UseMiddleware(SpeakRefusals)\nexport class RecipeDefinitionResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => RecipeDefinitionList, { description: 'To fetch recipe definitions' })\n async recipeDefinitions(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<RecipeDefinitionList> {\n const { domain } = context.state\n const { items, total } = await listMaster('recipeDefinition', params, domain)\n\n return { items: items as RecipeDefinition[], total }\n }\n\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [RecipeDefinition], { description: 'To create, change or retire recipe definitions' })\n async updateMultipleRecipeDefinition(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<RecipeDefinition[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('recipeDefinition')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as RecipeDefinition[]\n }\n}\n\n@Resolver(RouteDefinition)\n@UseMiddleware(SpeakRefusals)\nexport class RouteDefinitionResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => RouteDefinitionList, { description: 'To fetch route definitions' })\n async routeDefinitions(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<RouteDefinitionList> {\n const { domain } = context.state\n const { items, total } = await listMaster('routeDefinition', params, domain)\n\n return { items: items as RouteDefinition[], total }\n }\n\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [RouteDefinition], { description: 'To create, change or retire route definitions' })\n async updateMultipleRouteDefinition(\n @Arg('patches', type => [ScalarObject]) patches: MasterPatch[],\n @Ctx() context: ResolverContext\n ): Promise<RouteDefinition[]> {\n const { domain, user, tx } = context.state\n const axis = masterAxisOf('routeDefinition')\n\n const saved = await saveMasterPatches(tx, domain, axis.entity, patches, user, {\n hasEffectivePeriod: axis.hasEffectivePeriod\n })\n\n return saved as RouteDefinition[]\n }\n}\n"]}
@@ -8,6 +8,8 @@ const master_page_query_1 = require("../master-page-query");
8
8
  const recipe_definition_1 = require("./recipe-definition");
9
9
  const route_definition_1 = require("./route-definition");
10
10
  const recipe_master_1 = require("./recipe-master");
11
+ const recipe_effective_list_1 = require("./recipe-effective-list");
12
+ const master_page_1 = require("../master-page");
11
13
  let RecipeMaster = class RecipeMaster {
12
14
  };
13
15
  exports.RecipeMaster = RecipeMaster;
@@ -45,10 +47,15 @@ let RecipeQuery = class RecipeQuery {
45
47
  * 레시피가 먼저 도착하고 라우팅이 나중에 오는 순간이 생긴다 — 이 파일이 원래 「따로 물으면 레시피가
46
48
  * 없는 라우팅을 가리키는 순간이 생긴다」고 적어 둔 그 이유다.
47
49
  */
48
- const [page, routes] = await Promise.all([
49
- (0, master_page_query_1.pullPage)(recipe_definition_1.RecipeDefinition, domain, request),
50
+ const [effective, routes] = await Promise.all([
51
+ (0, recipe_effective_list_1.listEffectiveRecipes)((0, shell_1.getRepository)(recipe_definition_1.RecipeDefinition).manager, domain, {
52
+ filters: [...(after ? [{ name: 'name', operator: 'gt', value: after }] : []),
53
+ { name: 'materialDefinitionName', operator: 'is_not_null', value: true }],
54
+ sortings: [{ name: 'name', desc: false }], pagination: { limit: (0, master_page_1.takeFor)(request), page: 1 }
55
+ }),
50
56
  (0, shell_1.getRepository)(route_definition_1.RouteDefinition).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } })
51
57
  ]);
58
+ const page = (0, master_page_1.pageOf)(effective.items, request, row => row.name);
52
59
  /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */
53
60
  (0, master_page_query_1.refuseIfTooLarge)('레시피', request, page);
54
61
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"recipe-query.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAgF;AAEhF,iDAAqD;AAErD,4DAAiE;AAEjE,2DAAsD;AACtD,yDAAoD;AACpD,mDAA6D;AAGtD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAmBxB,CAAA;AAnBY,oCAAY;AAEvB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,0BAA0B,EAAE,CAAC;;6CACpD;AAGf;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;;4CACpD;AAQd;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,8DAA8D,EAAE,CAAC;;6CACvE;AAIhB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uDAAuD,EAAE,CAAC;;0CACnG;uBAjBF,YAAY;IADxB,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;GAC/D,YAAY,CAmBxB;AAGM,IAAM,WAAW,GAAjB,MAAM,WAAW;IACtB;;;;;OAKG;IAEG,AAAN,KAAK,CAAC,YAAY,CACqF,KAAyB,EACO,KAAyB,EACvJ,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;QAEhC;;;;WAIG;QACH,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACvC,IAAA,4BAAQ,EAAmB,oCAAgB,EAAE,MAAM,EAAE,OAAO,CAAC;YAC7D,IAAA,qBAAa,EAAC,kCAAe,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;SACtG,CAAC,CAAA;QAEF,0DAA0D;QAC1D,IAAA,oCAAgB,EAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;QAEtC,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,6BAAa,CAAC,CAAC;YACrD,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,4BAAY,CAAC,CAAC;YAChD,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,CAAA;IACH,CAAC;CACF,CAAA;AArCY,kCAAW;AAQhB;IADL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;IAExF,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oDAAoD,EAAE,CAAC,CAAA;IACnG,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uEAAuE,EAAE,CAAC,CAAA;IACnI,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;+CAyBP;sBApCU,WAAW;IADvB,IAAA,uBAAQ,EAAC,oCAAgB,CAAC;GACd,WAAW,CAqCvB","sourcesContent":["import { Arg, Ctx, Field, Int, ObjectType, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { pullPage, refuseIfTooLarge } from '../master-page-query'\n\nimport { RecipeDefinition } from './recipe-definition'\nimport { RouteDefinition } from './route-definition'\nimport { toRecipeEntry, toRouteEntry } from './recipe-master'\n\n@ObjectType({ description: 'Recipes and routes as the twin receives them' })\nexport class RecipeMaster {\n @Field(type => String, { description: 'RecipeDef[] as JSON text' })\n recipes: string\n\n @Field(type => String, { description: 'RouteDef[] as JSON text' })\n routes: string\n\n /**\n * 더 있나 — **이 값을 안 보고 쓰면 조용히 잘린 마스터로 트윈을 세우게 된다.**\n *\n * 거짓이 될 때까지 `next` 를 `after` 로 넘겨 이어 받는다.\n */\n @Field({ description: 'true when more rows remain — keep pulling with next as after' })\n hasMore: boolean\n\n /** 이어 받을 자리. 더 없으면 없다. */\n @Field(type => String, { nullable: true, description: 'cursor for the next page — the last name in this page' })\n next?: string\n\n}\n\n@Resolver(RecipeDefinition)\nexport class RecipeQuery {\n /**\n * 레시피와 라우팅을 **한 질의로** 낸다.\n *\n * 따로 물으면 그 사이에 마스터가 바뀌어 레시피가 없는 라우팅을 가리키는 순간이 생긴다. 구조는\n * 서로를 가리키므로 통째로 맞아야 한다 — 마스터를 당겨 가는 다른 축과 같은 규율이다.\n */\n @Query(returns => RecipeMaster, { description: 'Recipes and routes for the twin to pull' })\n async recipeMaster(\n @Arg('after', { nullable: true, description: 'resume after this name — the previous page\\'s next' }) after: string | undefined,\n @Arg('limit', type => Int, { nullable: true, description: 'rows per page — omit only when the axis is small enough to pull whole' }) limit: number | undefined,\n @Ctx() context: ResolverContext\n ): Promise<RecipeMaster> {\n const { domain } = context.state\n\n const request = { after, limit }\n\n /*\n * **라우팅은 통째로, 레시피만 나눈다.** 레시피가 라우팅을 이름으로 가리키므로, 둘을 함께 나누면\n * 레시피가 먼저 도착하고 라우팅이 나중에 오는 순간이 생긴다 — 이 파일이 원래 「따로 물으면 레시피가\n * 없는 라우팅을 가리키는 순간이 생긴다」고 적어 둔 그 이유다.\n */\n const [page, routes] = await Promise.all([\n pullPage<RecipeDefinition>(RecipeDefinition, domain, request),\n getRepository(RouteDefinition).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } })\n ])\n\n /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */\n refuseIfTooLarge('레시피', request, page)\n\n return {\n recipes: JSON.stringify(page.rows.map(toRecipeEntry)),\n routes: JSON.stringify(routes.map(toRouteEntry)),\n hasMore: page.hasMore,\n ...(page.next ? { next: page.next } : {})\n }\n }\n}\n"]}
1
+ {"version":3,"file":"recipe-query.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-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 { RecipeDefinition } from './recipe-definition'\nimport { RouteDefinition } from './route-definition'\nimport { toRecipeEntry, toRouteEntry } from './recipe-master'\nimport { listEffectiveRecipes } from './recipe-effective-list'\nimport { pageOf, takeFor } from '../master-page'\n\n@ObjectType({ description: 'Recipes and routes as the twin receives them' })\nexport class RecipeMaster {\n @Field(type => String, { description: 'RecipeDef[] as JSON text' })\n recipes: string\n\n @Field(type => String, { description: 'RouteDef[] as JSON text' })\n routes: string\n\n /**\n * 더 있나 — **이 값을 안 보고 쓰면 조용히 잘린 마스터로 트윈을 세우게 된다.**\n *\n * 거짓이 될 때까지 `next` 를 `after` 로 넘겨 이어 받는다.\n */\n @Field({ description: 'true when more rows remain — keep pulling with next as after' })\n hasMore: boolean\n\n /** 이어 받을 자리. 더 없으면 없다. */\n @Field(type => String, { nullable: true, description: 'cursor for the next page — the last name in this page' })\n next?: string\n\n}\n\n@Resolver(RecipeDefinition)\nexport class RecipeQuery {\n /**\n * 레시피와 라우팅을 **한 질의로** 낸다.\n *\n * 따로 물으면 그 사이에 마스터가 바뀌어 레시피가 없는 라우팅을 가리키는 순간이 생긴다. 구조는\n * 서로를 가리키므로 통째로 맞아야 한다 — 마스터를 당겨 가는 다른 축과 같은 규율이다.\n */\n @Query(returns => RecipeMaster, { description: 'Recipes and routes for the twin to pull' })\n async recipeMaster(\n @Arg('after', { nullable: true, description: 'resume after this name — the previous page\\'s next' }) after: string | undefined,\n @Arg('limit', type => Int, { nullable: true, description: 'rows per page — omit only when the axis is small enough to pull whole' }) limit: number | undefined,\n @Ctx() context: ResolverContext\n ): Promise<RecipeMaster> {\n const { domain } = context.state\n\n const request = { after, limit }\n\n /*\n * **라우팅은 통째로, 레시피만 나눈다.** 레시피가 라우팅을 이름으로 가리키므로, 둘을 함께 나누면\n * 레시피가 먼저 도착하고 라우팅이 나중에 오는 순간이 생긴다 — 이 파일이 원래 「따로 물으면 레시피가\n * 없는 라우팅을 가리키는 순간이 생긴다」고 적어 둔 그 이유다.\n */\n const [effective, routes] = await Promise.all([\n listEffectiveRecipes(getRepository(RecipeDefinition).manager, domain, {\n filters: [...(after ? [{ name: 'name', operator: 'gt', value: after }] : []),\n { name: 'materialDefinitionName', operator: 'is_not_null', value: true }],\n sortings: [{ name: 'name', desc: false }], pagination: { limit: takeFor(request), page: 1 }\n } as any),\n getRepository(RouteDefinition).find({ where: { domain: { id: domain.id } }, order: { name: 'ASC' } })\n ])\n const page = pageOf(effective.items, request, row => row.name)\n\n /* 통째로 달라고 했는데 한도를 넘으면 **자르지 않고 거절한다**(§`master-page`). */\n refuseIfTooLarge('레시피', request, page)\n\n return {\n recipes: JSON.stringify(page.rows.map(toRecipeEntry)),\n routes: JSON.stringify(routes.map(toRouteEntry)),\n hasMore: page.hasMore,\n ...(page.next ? { next: page.next } : {})\n }\n }\n}\n"]}
@@ -0,0 +1,32 @@
1
+ export interface RecipeLineContent {
2
+ lineKey: string;
3
+ material: string;
4
+ qty: number;
5
+ operation?: string | null;
6
+ }
7
+ export interface RecipeDraftContent {
8
+ materialDefinitionName: string;
9
+ description?: string | null;
10
+ route?: string | null;
11
+ inputs: RecipeLineContent[];
12
+ outputs: RecipeLineContent[];
13
+ effectiveFrom: Date | string;
14
+ reason: string;
15
+ }
16
+ /** Stable signed content excludes DB row IDs; array order is meaningful. */
17
+ export declare function recipeContentValues(input: Pick<RecipeDraftContent, 'materialDefinitionName' | 'description' | 'route' | 'inputs' | 'outputs'>): {
18
+ materialDefinitionName: string;
19
+ description: string;
20
+ route: string;
21
+ inputs: RecipeLineContent[];
22
+ outputs: RecipeLineContent[];
23
+ };
24
+ export declare function readRecipeContent(input: RecipeDraftContent): {
25
+ effectiveFrom: Date;
26
+ reason: string;
27
+ materialDefinitionName: string;
28
+ description: string;
29
+ route: string;
30
+ inputs: RecipeLineContent[];
31
+ outputs: RecipeLineContent[];
32
+ };
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.recipeContentValues = recipeContentValues;
4
+ exports.readRecipeContent = readRecipeContent;
5
+ const shell_1 = require("@things-factory/shell");
6
+ function invalid(field) {
7
+ throw new shell_1.Refusal('INVALID_RECIPE_CONTENT', { field }, `Invalid recipe content: ${field}`);
8
+ }
9
+ function required(value, field) {
10
+ if (typeof value !== 'string' || !value.trim())
11
+ invalid(field);
12
+ return value.trim();
13
+ }
14
+ function lines(value, side) {
15
+ if (!Array.isArray(value) || !value.length)
16
+ invalid(side);
17
+ const keys = new Set();
18
+ return value.map((line, index) => {
19
+ if (!line || typeof line !== 'object')
20
+ invalid(`${side}.${index}`);
21
+ const lineKey = required(line.lineKey, `${side}.${index}.lineKey`);
22
+ if (keys.has(lineKey))
23
+ invalid(`${side}.duplicateLineKey`);
24
+ keys.add(lineKey);
25
+ if (typeof line.qty !== 'number' || !Number.isFinite(line.qty) || line.qty <= 0)
26
+ invalid(`${side}.${index}.qty`);
27
+ if (side === 'outputs' && line.operation != null)
28
+ invalid('outputs.operation');
29
+ return {
30
+ lineKey,
31
+ material: required(line.material, `${side}.${index}.material`),
32
+ qty: line.qty,
33
+ ...(side === 'inputs' ? { operation: line.operation == null ? null : required(line.operation, 'operation') } : {})
34
+ };
35
+ });
36
+ }
37
+ /** Stable signed content excludes DB row IDs; array order is meaningful. */
38
+ function recipeContentValues(input) {
39
+ const materialDefinitionName = required(input.materialDefinitionName, 'materialDefinitionName');
40
+ if (input.description != null && typeof input.description !== 'string')
41
+ invalid('description');
42
+ const inputs = lines(input.inputs, 'inputs');
43
+ const outputs = lines(input.outputs, 'outputs');
44
+ if (!outputs.some(line => line.material === materialDefinitionName))
45
+ invalid('primaryOutputMissing');
46
+ return {
47
+ materialDefinitionName,
48
+ description: input.description ?? null,
49
+ route: input.route == null ? null : required(input.route, 'route'),
50
+ inputs,
51
+ outputs
52
+ };
53
+ }
54
+ function readRecipeContent(input) {
55
+ if (!input || typeof input !== 'object')
56
+ invalid('content');
57
+ const from = input.effectiveFrom;
58
+ if (!(from instanceof Date) && (typeof from !== 'string' || !/(Z|[+-]\d{2}:\d{2})$/.test(from)))
59
+ invalid('effectiveFrom');
60
+ const effectiveFrom = new Date(from);
61
+ if (!Number.isFinite(effectiveFrom.getTime()))
62
+ invalid('effectiveFrom');
63
+ return { ...recipeContentValues(input), effectiveFrom, reason: required(input.reason, 'reason') };
64
+ }
65
+ //# sourceMappingURL=recipe-revision-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-revision-content.js","sourceRoot":"","sources":["../../../server/service/recipe/recipe-revision-content.ts"],"names":[],"mappings":"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","sourcesContent":["import { Refusal } from '@things-factory/shell'\n\nexport interface RecipeLineContent {\n lineKey: string\n material: string\n qty: number\n operation?: string | null\n}\nexport interface RecipeDraftContent {\n materialDefinitionName: string\n description?: string | null\n route?: string | null\n inputs: RecipeLineContent[]\n outputs: RecipeLineContent[]\n effectiveFrom: Date | string\n reason: string\n}\nfunction invalid(field: string): never {\n throw new Refusal('INVALID_RECIPE_CONTENT', { field }, `Invalid recipe content: ${field}`)\n}\nfunction required(value: unknown, field: string): string {\n if (typeof value !== 'string' || !value.trim()) invalid(field)\n return value.trim()\n}\nfunction lines(value: unknown, side: 'inputs' | 'outputs'): RecipeLineContent[] {\n if (!Array.isArray(value) || !value.length) invalid(side)\n const keys = new Set<string>()\n return value.map((line, index) => {\n if (!line || typeof line !== 'object') invalid(`${side}.${index}`)\n const lineKey = required(line.lineKey, `${side}.${index}.lineKey`)\n if (keys.has(lineKey)) invalid(`${side}.duplicateLineKey`)\n keys.add(lineKey)\n if (typeof line.qty !== 'number' || !Number.isFinite(line.qty) || line.qty <= 0) invalid(`${side}.${index}.qty`)\n if (side === 'outputs' && line.operation != null) invalid('outputs.operation')\n return {\n lineKey,\n material: required(line.material, `${side}.${index}.material`),\n qty: line.qty,\n ...(side === 'inputs' ? { operation: line.operation == null ? null : required(line.operation, 'operation') } : {})\n }\n })\n}\n/** Stable signed content excludes DB row IDs; array order is meaningful. */\nexport function recipeContentValues(input: Pick<RecipeDraftContent, 'materialDefinitionName' | 'description' | 'route' | 'inputs' | 'outputs'>) {\n const materialDefinitionName = required(input.materialDefinitionName, 'materialDefinitionName')\n if (input.description != null && typeof input.description !== 'string') invalid('description')\n const inputs = lines(input.inputs, 'inputs')\n const outputs = lines(input.outputs, 'outputs')\n if (!outputs.some(line => line.material === materialDefinitionName)) invalid('primaryOutputMissing')\n return {\n materialDefinitionName,\n description: input.description ?? null,\n route: input.route == null ? null : required(input.route, 'route'),\n inputs,\n outputs\n }\n}\nexport function readRecipeContent(input: RecipeDraftContent) {\n if (!input || typeof input !== 'object') invalid('content')\n const from = input.effectiveFrom\n if (!(from instanceof Date) && (typeof from !== 'string' || !/(Z|[+-]\\d{2}:\\d{2})$/.test(from))) invalid('effectiveFrom')\n const effectiveFrom = new Date(from)\n if (!Number.isFinite(effectiveFrom.getTime())) invalid('effectiveFrom')\n return { ...recipeContentValues(input), effectiveFrom, reason: required(input.reason, 'reason') }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import { Domain } from '@things-factory/shell';
2
+ import type { MaterialDefinition } from '../material/material-definition';
3
+ import type { OperationDefinition } from '../operation/operation-definition';
4
+ import type { RecipeDefinition } from './recipe-definition';
5
+ import type { RecipeRevision } from './recipe-revision';
6
+ /** Owned rows, not independently publishable masters. Keys survive derivation for structural diffs. */
7
+ declare abstract class RecipeRevisionLine {
8
+ id: string;
9
+ domain: Domain;
10
+ domainId: string;
11
+ recipe: RecipeDefinition;
12
+ recipeId: string;
13
+ revision: RecipeRevision;
14
+ revisionId: string;
15
+ materialDefinition: MaterialDefinition;
16
+ materialDefinitionId: string;
17
+ /** Frozen business key: a subsequent master rename cannot rewrite signed content. */
18
+ material: string;
19
+ lineKey: string;
20
+ position: number;
21
+ qty: number;
22
+ }
23
+ export declare class RecipeRevisionInput extends RecipeRevisionLine {
24
+ operationDefinition: OperationDefinition | null;
25
+ operationDefinitionId: string | null;
26
+ operation: string | null;
27
+ }
28
+ export declare class RecipeRevisionOutput extends RecipeRevisionLine {
29
+ }
30
+ export {};