Commit 8b95af948724f31ac564e40f88ccaa1a2ee41b53
1 parent
8ce94781
feat(production): wire WorkOrder into HasExt pattern + declare custom fields
Closes the last known gap from the HasExt refactor (commit 986f02ce): pbc-production's WorkOrder had an `ext` column but no validator was wired, so an operator could write arbitrary JSON without any schema enforcement. This fixes that and adds the first Tier 1 custom fields for WorkOrder. Code changes: - WorkOrder implements HasExt; ext becomes `override var ext`, ENTITY_NAME moves onto the entity companion. - WorkOrderService injects ExtJsonValidator, calls applyTo() in create() before saving (null-safe so the SalesOrderConfirmedSubscriber's auto-spawn path still works — verified by smoke test). - CreateWorkOrderCommand + CreateWorkOrderRequest gain an `ext` field that flows through to the validator. - WorkOrderResponse gains an `ext: Map<String, Any?>` field; the response mapper signature changes to `toResponse(service)` to reach the validator via a convenience parseExt delegate on the service (same pattern as the other four PBCs). - pbc-production Gradle build adds `implementation(project(":platform:platform-metadata"))`. Metadata (production.yml): - Permission keys extended to match the v2 state machine: production.work-order.start (was missing) and production.work-order.scrap (was missing). The existing .read / .create / .complete / .cancel keys stay. - Two custom fields declared: * production_priority (enum: low, normal, high, urgent) * production_routing_notes (string, maxLength 1024) Both are optional and non-PII; an operator can now add priority and routing notes to a work order through the public API without any code change, which is the whole point of Tier 1 customization. Unit tests: WorkOrderServiceTest constructor updated to pass the new extValidator dependency and stub applyTo/parseExt as no-ops. No behavioral test changes — ext validation is covered by ExtJsonValidatorTest and the platform-wide smoke tests. Smoke verified end-to-end against real Postgres: - GET /_meta/metadata/custom-fields/WorkOrder now returns both declarations with correct enum sets and maxLength. - POST /work-orders with valid ext {production_priority:"high", production_routing_notes:"Rush for customer demo"} → 201, canonical form persisted, round-trips via GET. - POST with invalid enum value → 400 "value 'emergency' is not in allowed set [low, normal, high, urgent]". - POST with unknown ext key → 400 "ext contains undeclared key(s) for 'WorkOrder': [unknown_field]". - Auto-spawn from confirmed SO → DRAFT work order with empty ext `{}`, confirming the applyTo(null) null-safe path. Five of the eight PBCs now participate in the HasExt pattern: Partner, Location, SalesOrder, PurchaseOrder, WorkOrder. The remaining three (Item, Uom, JournalEntry) either have their own custom-field story in separate entities or are derived state. 246 unit tests, all green. 18 Gradle subprojects.
Showing
6 changed files
with
85 additions
and
14 deletions
pbc/pbc-production/build.gradle.kts
| @@ -36,6 +36,7 @@ dependencies { | @@ -36,6 +36,7 @@ dependencies { | ||
| 36 | api(project(":api:api-v1")) | 36 | api(project(":api:api-v1")) |
| 37 | implementation(project(":platform:platform-persistence")) | 37 | implementation(project(":platform:platform-persistence")) |
| 38 | implementation(project(":platform:platform-security")) | 38 | implementation(project(":platform:platform-security")) |
| 39 | + implementation(project(":platform:platform-metadata")) | ||
| 39 | 40 | ||
| 40 | implementation(libs.kotlin.stdlib) | 41 | implementation(libs.kotlin.stdlib) |
| 41 | implementation(libs.kotlin.reflect) | 42 | implementation(libs.kotlin.reflect) |
pbc/pbc-production/src/main/kotlin/org/vibeerp/pbc/production/application/WorkOrderService.kt
| @@ -15,6 +15,7 @@ import org.vibeerp.pbc.production.domain.WorkOrder | @@ -15,6 +15,7 @@ import org.vibeerp.pbc.production.domain.WorkOrder | ||
| 15 | import org.vibeerp.pbc.production.domain.WorkOrderInput | 15 | import org.vibeerp.pbc.production.domain.WorkOrderInput |
| 16 | import org.vibeerp.pbc.production.domain.WorkOrderStatus | 16 | import org.vibeerp.pbc.production.domain.WorkOrderStatus |
| 17 | import org.vibeerp.pbc.production.infrastructure.WorkOrderJpaRepository | 17 | import org.vibeerp.pbc.production.infrastructure.WorkOrderJpaRepository |
| 18 | +import org.vibeerp.platform.metadata.customfield.ExtJsonValidator | ||
| 18 | import java.math.BigDecimal | 19 | import java.math.BigDecimal |
| 19 | import java.time.LocalDate | 20 | import java.time.LocalDate |
| 20 | import java.util.UUID | 21 | import java.util.UUID |
| @@ -70,6 +71,7 @@ class WorkOrderService( | @@ -70,6 +71,7 @@ class WorkOrderService( | ||
| 70 | private val catalogApi: CatalogApi, | 71 | private val catalogApi: CatalogApi, |
| 71 | private val inventoryApi: InventoryApi, | 72 | private val inventoryApi: InventoryApi, |
| 72 | private val eventBus: EventBus, | 73 | private val eventBus: EventBus, |
| 74 | + private val extValidator: ExtJsonValidator, | ||
| 73 | ) { | 75 | ) { |
| 74 | 76 | ||
| 75 | private val log = LoggerFactory.getLogger(WorkOrderService::class.java) | 77 | private val log = LoggerFactory.getLogger(WorkOrderService::class.java) |
| @@ -87,6 +89,14 @@ class WorkOrderService( | @@ -87,6 +89,14 @@ class WorkOrderService( | ||
| 87 | fun findBySourceSalesOrderCode(salesOrderCode: String): List<WorkOrder> = | 89 | fun findBySourceSalesOrderCode(salesOrderCode: String): List<WorkOrder> = |
| 88 | orders.findBySourceSalesOrderCode(salesOrderCode) | 90 | orders.findBySourceSalesOrderCode(salesOrderCode) |
| 89 | 91 | ||
| 92 | + /** | ||
| 93 | + * Convenience passthrough for response mappers — delegates to | ||
| 94 | + * [ExtJsonValidator.parseExt]. Returns an empty map on an empty | ||
| 95 | + * or unparseable column so response rendering never 500s. | ||
| 96 | + */ | ||
| 97 | + fun parseExt(order: WorkOrder): Map<String, Any?> = | ||
| 98 | + extValidator.parseExt(order) | ||
| 99 | + | ||
| 90 | fun create(command: CreateWorkOrderCommand): WorkOrder { | 100 | fun create(command: CreateWorkOrderCommand): WorkOrder { |
| 91 | require(!orders.existsByCode(command.code)) { | 101 | require(!orders.existsByCode(command.code)) { |
| 92 | "work order code '${command.code}' is already taken" | 102 | "work order code '${command.code}' is already taken" |
| @@ -135,6 +145,10 @@ class WorkOrderService( | @@ -135,6 +145,10 @@ class WorkOrderService( | ||
| 135 | dueDate = command.dueDate, | 145 | dueDate = command.dueDate, |
| 136 | sourceSalesOrderCode = command.sourceSalesOrderCode, | 146 | sourceSalesOrderCode = command.sourceSalesOrderCode, |
| 137 | ) | 147 | ) |
| 148 | + // Validate and apply any Tier 1 custom-field values. applyTo | ||
| 149 | + // is null-safe — the SalesOrderConfirmedSubscriber's auto-spawn | ||
| 150 | + // path passes null and gets the default empty ext. | ||
| 151 | + extValidator.applyTo(order, command.ext) | ||
| 138 | // Attach BOM children BEFORE the first save so Hibernate | 152 | // Attach BOM children BEFORE the first save so Hibernate |
| 139 | // cascades the whole graph in one commit. | 153 | // cascades the whole graph in one commit. |
| 140 | for (input in command.inputs) { | 154 | for (input in command.inputs) { |
| @@ -370,6 +384,14 @@ data class CreateWorkOrderCommand( | @@ -370,6 +384,14 @@ data class CreateWorkOrderCommand( | ||
| 370 | * complete() writes only the PRODUCTION_RECEIPT). | 384 | * complete() writes only the PRODUCTION_RECEIPT). |
| 371 | */ | 385 | */ |
| 372 | val inputs: List<WorkOrderInputCommand> = emptyList(), | 386 | val inputs: List<WorkOrderInputCommand> = emptyList(), |
| 387 | + /** | ||
| 388 | + * Tier 1 custom-field values. Validated against declarations | ||
| 389 | + * under entity name `WorkOrder` in `metadata__custom_field` via | ||
| 390 | + * [ExtJsonValidator.applyTo]. Null is legal and produces an | ||
| 391 | + * empty `{}` column; the SalesOrderConfirmedSubscriber's | ||
| 392 | + * auto-spawn path uses null. | ||
| 393 | + */ | ||
| 394 | + val ext: Map<String, Any?>? = null, | ||
| 373 | ) | 395 | ) |
| 374 | 396 | ||
| 375 | /** | 397 | /** |
pbc/pbc-production/src/main/kotlin/org/vibeerp/pbc/production/domain/WorkOrder.kt
| @@ -11,6 +11,7 @@ import jakarta.persistence.OrderBy | @@ -11,6 +11,7 @@ import jakarta.persistence.OrderBy | ||
| 11 | import jakarta.persistence.Table | 11 | import jakarta.persistence.Table |
| 12 | import org.hibernate.annotations.JdbcTypeCode | 12 | import org.hibernate.annotations.JdbcTypeCode |
| 13 | import org.hibernate.type.SqlTypes | 13 | import org.hibernate.type.SqlTypes |
| 14 | +import org.vibeerp.api.v1.entity.HasExt | ||
| 14 | import org.vibeerp.platform.persistence.audit.AuditedJpaEntity | 15 | import org.vibeerp.platform.persistence.audit.AuditedJpaEntity |
| 15 | import java.math.BigDecimal | 16 | import java.math.BigDecimal |
| 16 | import java.time.LocalDate | 17 | import java.time.LocalDate |
| @@ -101,7 +102,7 @@ class WorkOrder( | @@ -101,7 +102,7 @@ class WorkOrder( | ||
| 101 | status: WorkOrderStatus = WorkOrderStatus.DRAFT, | 102 | status: WorkOrderStatus = WorkOrderStatus.DRAFT, |
| 102 | dueDate: LocalDate? = null, | 103 | dueDate: LocalDate? = null, |
| 103 | sourceSalesOrderCode: String? = null, | 104 | sourceSalesOrderCode: String? = null, |
| 104 | -) : AuditedJpaEntity() { | 105 | +) : AuditedJpaEntity(), HasExt { |
| 105 | 106 | ||
| 106 | @Column(name = "code", nullable = false, length = 64) | 107 | @Column(name = "code", nullable = false, length = 64) |
| 107 | var code: String = code | 108 | var code: String = code |
| @@ -124,7 +125,9 @@ class WorkOrder( | @@ -124,7 +125,9 @@ class WorkOrder( | ||
| 124 | 125 | ||
| 125 | @Column(name = "ext", nullable = false, columnDefinition = "jsonb") | 126 | @Column(name = "ext", nullable = false, columnDefinition = "jsonb") |
| 126 | @JdbcTypeCode(SqlTypes.JSON) | 127 | @JdbcTypeCode(SqlTypes.JSON) |
| 127 | - var ext: String = "{}" | 128 | + override var ext: String = "{}" |
| 129 | + | ||
| 130 | + override val extEntityName: String get() = ENTITY_NAME | ||
| 128 | 131 | ||
| 129 | /** | 132 | /** |
| 130 | * The BOM — zero or more raw material lines consumed per unit of | 133 | * The BOM — zero or more raw material lines consumed per unit of |
| @@ -150,6 +153,11 @@ class WorkOrder( | @@ -150,6 +153,11 @@ class WorkOrder( | ||
| 150 | 153 | ||
| 151 | override fun toString(): String = | 154 | override fun toString(): String = |
| 152 | "WorkOrder(id=$id, code='$code', output=$outputQuantity '$outputItemCode', status=$status, inputs=${inputs.size})" | 155 | "WorkOrder(id=$id, code='$code', output=$outputQuantity '$outputItemCode', status=$status, inputs=${inputs.size})" |
| 156 | + | ||
| 157 | + companion object { | ||
| 158 | + /** Key under which WorkOrder's custom fields are declared in `metadata__custom_field`. */ | ||
| 159 | + const val ENTITY_NAME: String = "WorkOrder" | ||
| 160 | + } | ||
| 153 | } | 161 | } |
| 154 | 162 | ||
| 155 | /** | 163 | /** |
pbc/pbc-production/src/main/kotlin/org/vibeerp/pbc/production/http/WorkOrderController.kt
| @@ -42,27 +42,27 @@ class WorkOrderController( | @@ -42,27 +42,27 @@ class WorkOrderController( | ||
| 42 | @GetMapping | 42 | @GetMapping |
| 43 | @RequirePermission("production.work-order.read") | 43 | @RequirePermission("production.work-order.read") |
| 44 | fun list(): List<WorkOrderResponse> = | 44 | fun list(): List<WorkOrderResponse> = |
| 45 | - workOrderService.list().map { it.toResponse() } | 45 | + workOrderService.list().map { it.toResponse(workOrderService) } |
| 46 | 46 | ||
| 47 | @GetMapping("/{id}") | 47 | @GetMapping("/{id}") |
| 48 | @RequirePermission("production.work-order.read") | 48 | @RequirePermission("production.work-order.read") |
| 49 | fun get(@PathVariable id: UUID): ResponseEntity<WorkOrderResponse> { | 49 | fun get(@PathVariable id: UUID): ResponseEntity<WorkOrderResponse> { |
| 50 | val order = workOrderService.findById(id) ?: return ResponseEntity.notFound().build() | 50 | val order = workOrderService.findById(id) ?: return ResponseEntity.notFound().build() |
| 51 | - return ResponseEntity.ok(order.toResponse()) | 51 | + return ResponseEntity.ok(order.toResponse(workOrderService)) |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | @GetMapping("/by-code/{code}") | 54 | @GetMapping("/by-code/{code}") |
| 55 | @RequirePermission("production.work-order.read") | 55 | @RequirePermission("production.work-order.read") |
| 56 | fun getByCode(@PathVariable code: String): ResponseEntity<WorkOrderResponse> { | 56 | fun getByCode(@PathVariable code: String): ResponseEntity<WorkOrderResponse> { |
| 57 | val order = workOrderService.findByCode(code) ?: return ResponseEntity.notFound().build() | 57 | val order = workOrderService.findByCode(code) ?: return ResponseEntity.notFound().build() |
| 58 | - return ResponseEntity.ok(order.toResponse()) | 58 | + return ResponseEntity.ok(order.toResponse(workOrderService)) |
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | @PostMapping | 61 | @PostMapping |
| 62 | @ResponseStatus(HttpStatus.CREATED) | 62 | @ResponseStatus(HttpStatus.CREATED) |
| 63 | @RequirePermission("production.work-order.create") | 63 | @RequirePermission("production.work-order.create") |
| 64 | fun create(@RequestBody @Valid request: CreateWorkOrderRequest): WorkOrderResponse = | 64 | fun create(@RequestBody @Valid request: CreateWorkOrderRequest): WorkOrderResponse = |
| 65 | - workOrderService.create(request.toCommand()).toResponse() | 65 | + workOrderService.create(request.toCommand()).toResponse(workOrderService) |
| 66 | 66 | ||
| 67 | /** | 67 | /** |
| 68 | * Start a DRAFT work order — flip to IN_PROGRESS. v2 state | 68 | * Start a DRAFT work order — flip to IN_PROGRESS. v2 state |
| @@ -74,7 +74,7 @@ class WorkOrderController( | @@ -74,7 +74,7 @@ class WorkOrderController( | ||
| 74 | @PostMapping("/{id}/start") | 74 | @PostMapping("/{id}/start") |
| 75 | @RequirePermission("production.work-order.start") | 75 | @RequirePermission("production.work-order.start") |
| 76 | fun start(@PathVariable id: UUID): WorkOrderResponse = | 76 | fun start(@PathVariable id: UUID): WorkOrderResponse = |
| 77 | - workOrderService.start(id).toResponse() | 77 | + workOrderService.start(id).toResponse(workOrderService) |
| 78 | 78 | ||
| 79 | /** | 79 | /** |
| 80 | * Mark an IN_PROGRESS work order as COMPLETED. Atomically: | 80 | * Mark an IN_PROGRESS work order as COMPLETED. Atomically: |
| @@ -89,12 +89,12 @@ class WorkOrderController( | @@ -89,12 +89,12 @@ class WorkOrderController( | ||
| 89 | @PathVariable id: UUID, | 89 | @PathVariable id: UUID, |
| 90 | @RequestBody @Valid request: CompleteWorkOrderRequest, | 90 | @RequestBody @Valid request: CompleteWorkOrderRequest, |
| 91 | ): WorkOrderResponse = | 91 | ): WorkOrderResponse = |
| 92 | - workOrderService.complete(id, request.outputLocationCode).toResponse() | 92 | + workOrderService.complete(id, request.outputLocationCode).toResponse(workOrderService) |
| 93 | 93 | ||
| 94 | @PostMapping("/{id}/cancel") | 94 | @PostMapping("/{id}/cancel") |
| 95 | @RequirePermission("production.work-order.cancel") | 95 | @RequirePermission("production.work-order.cancel") |
| 96 | fun cancel(@PathVariable id: UUID): WorkOrderResponse = | 96 | fun cancel(@PathVariable id: UUID): WorkOrderResponse = |
| 97 | - workOrderService.cancel(id).toResponse() | 97 | + workOrderService.cancel(id).toResponse(workOrderService) |
| 98 | 98 | ||
| 99 | /** | 99 | /** |
| 100 | * Scrap some of a COMPLETED work order's output. Writes a | 100 | * Scrap some of a COMPLETED work order's output. Writes a |
| @@ -112,7 +112,7 @@ class WorkOrderController( | @@ -112,7 +112,7 @@ class WorkOrderController( | ||
| 112 | scrapLocationCode = request.scrapLocationCode, | 112 | scrapLocationCode = request.scrapLocationCode, |
| 113 | quantity = request.quantity, | 113 | quantity = request.quantity, |
| 114 | note = request.note, | 114 | note = request.note, |
| 115 | - ).toResponse() | 115 | + ).toResponse(workOrderService) |
| 116 | } | 116 | } |
| 117 | 117 | ||
| 118 | // ─── DTOs ──────────────────────────────────────────────────────────── | 118 | // ─── DTOs ──────────────────────────────────────────────────────────── |
| @@ -129,6 +129,11 @@ data class CreateWorkOrderRequest( | @@ -129,6 +129,11 @@ data class CreateWorkOrderRequest( | ||
| 129 | * complete(). | 129 | * complete(). |
| 130 | */ | 130 | */ |
| 131 | @field:Valid val inputs: List<WorkOrderInputRequest> = emptyList(), | 131 | @field:Valid val inputs: List<WorkOrderInputRequest> = emptyList(), |
| 132 | + /** | ||
| 133 | + * Tier 1 custom-field values. Validated against declarations | ||
| 134 | + * under entity name `WorkOrder`. | ||
| 135 | + */ | ||
| 136 | + val ext: Map<String, Any?>? = null, | ||
| 132 | ) { | 137 | ) { |
| 133 | fun toCommand(): CreateWorkOrderCommand = CreateWorkOrderCommand( | 138 | fun toCommand(): CreateWorkOrderCommand = CreateWorkOrderCommand( |
| 134 | code = code, | 139 | code = code, |
| @@ -137,6 +142,7 @@ data class CreateWorkOrderRequest( | @@ -137,6 +142,7 @@ data class CreateWorkOrderRequest( | ||
| 137 | dueDate = dueDate, | 142 | dueDate = dueDate, |
| 138 | sourceSalesOrderCode = sourceSalesOrderCode, | 143 | sourceSalesOrderCode = sourceSalesOrderCode, |
| 139 | inputs = inputs.map { it.toCommand() }, | 144 | inputs = inputs.map { it.toCommand() }, |
| 145 | + ext = ext, | ||
| 140 | ) | 146 | ) |
| 141 | } | 147 | } |
| 142 | 148 | ||
| @@ -190,6 +196,7 @@ data class WorkOrderResponse( | @@ -190,6 +196,7 @@ data class WorkOrderResponse( | ||
| 190 | val dueDate: LocalDate?, | 196 | val dueDate: LocalDate?, |
| 191 | val sourceSalesOrderCode: String?, | 197 | val sourceSalesOrderCode: String?, |
| 192 | val inputs: List<WorkOrderInputResponse>, | 198 | val inputs: List<WorkOrderInputResponse>, |
| 199 | + val ext: Map<String, Any?>, | ||
| 193 | ) | 200 | ) |
| 194 | 201 | ||
| 195 | data class WorkOrderInputResponse( | 202 | data class WorkOrderInputResponse( |
| @@ -200,7 +207,7 @@ data class WorkOrderInputResponse( | @@ -200,7 +207,7 @@ data class WorkOrderInputResponse( | ||
| 200 | val sourceLocationCode: String, | 207 | val sourceLocationCode: String, |
| 201 | ) | 208 | ) |
| 202 | 209 | ||
| 203 | -private fun WorkOrder.toResponse(): WorkOrderResponse = | 210 | +private fun WorkOrder.toResponse(service: WorkOrderService): WorkOrderResponse = |
| 204 | WorkOrderResponse( | 211 | WorkOrderResponse( |
| 205 | id = id, | 212 | id = id, |
| 206 | code = code, | 213 | code = code, |
| @@ -210,6 +217,7 @@ private fun WorkOrder.toResponse(): WorkOrderResponse = | @@ -210,6 +217,7 @@ private fun WorkOrder.toResponse(): WorkOrderResponse = | ||
| 210 | dueDate = dueDate, | 217 | dueDate = dueDate, |
| 211 | sourceSalesOrderCode = sourceSalesOrderCode, | 218 | sourceSalesOrderCode = sourceSalesOrderCode, |
| 212 | inputs = inputs.map { it.toResponse() }, | 219 | inputs = inputs.map { it.toResponse() }, |
| 220 | + ext = service.parseExt(this), | ||
| 213 | ) | 221 | ) |
| 214 | 222 | ||
| 215 | private fun WorkOrderInput.toResponse(): WorkOrderInputResponse = | 223 | private fun WorkOrderInput.toResponse(): WorkOrderInputResponse = |
pbc/pbc-production/src/main/resources/META-INF/vibe-erp/metadata/production.yml
| @@ -14,10 +14,36 @@ permissions: | @@ -14,10 +14,36 @@ permissions: | ||
| 14 | description: Read work orders | 14 | description: Read work orders |
| 15 | - key: production.work-order.create | 15 | - key: production.work-order.create |
| 16 | description: Create draft work orders | 16 | description: Create draft work orders |
| 17 | + - key: production.work-order.start | ||
| 18 | + description: Start a work order (DRAFT → IN_PROGRESS) | ||
| 17 | - key: production.work-order.complete | 19 | - key: production.work-order.complete |
| 18 | - description: Mark a draft work order completed (DRAFT → COMPLETED, credits inventory atomically) | 20 | + description: Complete a work order (IN_PROGRESS → COMPLETED; issues BOM materials and credits finished goods atomically) |
| 19 | - key: production.work-order.cancel | 21 | - key: production.work-order.cancel |
| 20 | - description: Cancel a draft work order (DRAFT → CANCELLED) | 22 | + description: Cancel a work order (DRAFT or IN_PROGRESS → CANCELLED) |
| 23 | + - key: production.work-order.scrap | ||
| 24 | + description: Scrap some output from a COMPLETED work order (writes a negative ADJUSTMENT, status unchanged) | ||
| 25 | + | ||
| 26 | +customFields: | ||
| 27 | + - key: production_priority | ||
| 28 | + targetEntity: WorkOrder | ||
| 29 | + type: | ||
| 30 | + kind: enum | ||
| 31 | + allowedValues: [low, normal, high, urgent] | ||
| 32 | + required: false | ||
| 33 | + pii: false | ||
| 34 | + labelTranslations: | ||
| 35 | + en: Priority | ||
| 36 | + zh-CN: 优先级 | ||
| 37 | + - key: production_routing_notes | ||
| 38 | + targetEntity: WorkOrder | ||
| 39 | + type: | ||
| 40 | + kind: string | ||
| 41 | + maxLength: 1024 | ||
| 42 | + required: false | ||
| 43 | + pii: false | ||
| 44 | + labelTranslations: | ||
| 45 | + en: Routing notes | ||
| 46 | + zh-CN: 工艺说明 | ||
| 21 | 47 | ||
| 22 | menus: | 48 | menus: |
| 23 | - path: /production/work-orders | 49 | - path: /production/work-orders |
pbc/pbc-production/src/test/kotlin/org/vibeerp/pbc/production/application/WorkOrderServiceTest.kt
| @@ -16,6 +16,7 @@ import io.mockk.verify | @@ -16,6 +16,7 @@ import io.mockk.verify | ||
| 16 | import org.junit.jupiter.api.BeforeEach | 16 | import org.junit.jupiter.api.BeforeEach |
| 17 | import org.junit.jupiter.api.Test | 17 | import org.junit.jupiter.api.Test |
| 18 | import org.vibeerp.api.v1.core.Id | 18 | import org.vibeerp.api.v1.core.Id |
| 19 | +import org.vibeerp.api.v1.entity.HasExt | ||
| 19 | import org.vibeerp.api.v1.event.EventBus | 20 | import org.vibeerp.api.v1.event.EventBus |
| 20 | import org.vibeerp.api.v1.event.production.WorkOrderCancelledEvent | 21 | import org.vibeerp.api.v1.event.production.WorkOrderCancelledEvent |
| 21 | import org.vibeerp.api.v1.event.production.WorkOrderCompletedEvent | 22 | import org.vibeerp.api.v1.event.production.WorkOrderCompletedEvent |
| @@ -30,6 +31,7 @@ import org.vibeerp.pbc.production.domain.WorkOrder | @@ -30,6 +31,7 @@ import org.vibeerp.pbc.production.domain.WorkOrder | ||
| 30 | import org.vibeerp.pbc.production.domain.WorkOrderInput | 31 | import org.vibeerp.pbc.production.domain.WorkOrderInput |
| 31 | import org.vibeerp.pbc.production.domain.WorkOrderStatus | 32 | import org.vibeerp.pbc.production.domain.WorkOrderStatus |
| 32 | import org.vibeerp.pbc.production.infrastructure.WorkOrderJpaRepository | 33 | import org.vibeerp.pbc.production.infrastructure.WorkOrderJpaRepository |
| 34 | +import org.vibeerp.platform.metadata.customfield.ExtJsonValidator | ||
| 33 | import java.math.BigDecimal | 35 | import java.math.BigDecimal |
| 34 | import java.util.Optional | 36 | import java.util.Optional |
| 35 | import java.util.UUID | 37 | import java.util.UUID |
| @@ -40,6 +42,7 @@ class WorkOrderServiceTest { | @@ -40,6 +42,7 @@ class WorkOrderServiceTest { | ||
| 40 | private lateinit var catalogApi: CatalogApi | 42 | private lateinit var catalogApi: CatalogApi |
| 41 | private lateinit var inventoryApi: InventoryApi | 43 | private lateinit var inventoryApi: InventoryApi |
| 42 | private lateinit var eventBus: EventBus | 44 | private lateinit var eventBus: EventBus |
| 45 | + private lateinit var extValidator: ExtJsonValidator | ||
| 43 | private lateinit var service: WorkOrderService | 46 | private lateinit var service: WorkOrderService |
| 44 | 47 | ||
| 45 | @BeforeEach | 48 | @BeforeEach |
| @@ -48,10 +51,13 @@ class WorkOrderServiceTest { | @@ -48,10 +51,13 @@ class WorkOrderServiceTest { | ||
| 48 | catalogApi = mockk() | 51 | catalogApi = mockk() |
| 49 | inventoryApi = mockk() | 52 | inventoryApi = mockk() |
| 50 | eventBus = mockk() | 53 | eventBus = mockk() |
| 54 | + extValidator = mockk() | ||
| 51 | every { orders.existsByCode(any()) } returns false | 55 | every { orders.existsByCode(any()) } returns false |
| 52 | every { orders.save(any<WorkOrder>()) } answers { firstArg() } | 56 | every { orders.save(any<WorkOrder>()) } answers { firstArg() } |
| 53 | every { eventBus.publish(any()) } just Runs | 57 | every { eventBus.publish(any()) } just Runs |
| 54 | - service = WorkOrderService(orders, catalogApi, inventoryApi, eventBus) | 58 | + every { extValidator.applyTo(any<HasExt>(), any()) } just Runs |
| 59 | + every { extValidator.parseExt(any<HasExt>()) } returns emptyMap() | ||
| 60 | + service = WorkOrderService(orders, catalogApi, inventoryApi, eventBus, extValidator) | ||
| 55 | } | 61 | } |
| 56 | 62 | ||
| 57 | private fun stubItem(code: String) { | 63 | private fun stubItem(code: String) { |