From 98d7032b56b688b3e7903a98ca508b87da34253d Mon Sep 17 00:00:00 2001 From: zichun Date: Thu, 9 Apr 2026 10:43:50 +0800 Subject: [PATCH] feat(catalog): wire Item into HasExt pattern + restore plug-in Item fields --- pbc/pbc-catalog/build.gradle.kts | 1 + pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/application/ItemService.kt | 30 +++++++++++++++++++++--------- pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/domain/Item.kt | 12 ++++++++++-- pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/http/ItemController.kt | 18 ++++++++++++------ pbc/pbc-catalog/src/test/kotlin/org/vibeerp/pbc/catalog/application/ItemServiceTest.kt | 10 +++++++++- reference-customer/plugin-printing-shop/src/main/resources/META-INF/vibe-erp/metadata/printing-shop.yml | 24 ++++++++++++++++++++++++ 6 files changed, 77 insertions(+), 18 deletions(-) diff --git a/pbc/pbc-catalog/build.gradle.kts b/pbc/pbc-catalog/build.gradle.kts index d1d180a..de53668 100644 --- a/pbc/pbc-catalog/build.gradle.kts +++ b/pbc/pbc-catalog/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { api(project(":api:api-v1")) implementation(project(":platform:platform-persistence")) implementation(project(":platform:platform-security")) + implementation(project(":platform:platform-metadata")) implementation(libs.kotlin.stdlib) implementation(libs.kotlin.reflect) diff --git a/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/application/ItemService.kt b/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/application/ItemService.kt index c739b0f..5187585 100644 --- a/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/application/ItemService.kt +++ b/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/application/ItemService.kt @@ -6,6 +6,7 @@ import org.vibeerp.pbc.catalog.domain.Item import org.vibeerp.pbc.catalog.domain.ItemType import org.vibeerp.pbc.catalog.infrastructure.ItemJpaRepository import org.vibeerp.pbc.catalog.infrastructure.UomJpaRepository +import org.vibeerp.platform.metadata.customfield.ExtJsonValidator import java.util.UUID /** @@ -29,6 +30,7 @@ import java.util.UUID class ItemService( private val items: ItemJpaRepository, private val uoms: UomJpaRepository, + private val extValidator: ExtJsonValidator, ) { @Transactional(readOnly = true) @@ -47,16 +49,16 @@ class ItemService( require(uoms.existsByCode(command.baseUomCode)) { "base UoM '${command.baseUomCode}' is not in the catalog" } - return items.save( - Item( - code = command.code, - name = command.name, - description = command.description, - itemType = command.itemType, - baseUomCode = command.baseUomCode, - active = command.active, - ), + val item = Item( + code = command.code, + name = command.name, + description = command.description, + itemType = command.itemType, + baseUomCode = command.baseUomCode, + active = command.active, ) + extValidator.applyTo(item, command.ext) + return items.save(item) } fun update(id: UUID, command: UpdateItemCommand): Item { @@ -71,6 +73,7 @@ class ItemService( command.description?.let { item.description = it } command.itemType?.let { item.itemType = it } command.active?.let { item.active = it } + extValidator.applyTo(item, command.ext) return item } @@ -80,6 +83,13 @@ class ItemService( } item.active = false } + + /** + * Convenience passthrough for response mappers — delegates to + * [ExtJsonValidator.parseExt]. + */ + fun parseExt(item: Item): Map = + extValidator.parseExt(item) } data class CreateItemCommand( @@ -89,6 +99,7 @@ data class CreateItemCommand( val itemType: ItemType, val baseUomCode: String, val active: Boolean = true, + val ext: Map? = null, ) data class UpdateItemCommand( @@ -96,4 +107,5 @@ data class UpdateItemCommand( val description: String? = null, val itemType: ItemType? = null, val active: Boolean? = null, + val ext: Map? = null, ) diff --git a/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/domain/Item.kt b/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/domain/Item.kt index a366435..cf7661d 100644 --- a/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/domain/Item.kt +++ b/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/domain/Item.kt @@ -7,6 +7,7 @@ import jakarta.persistence.Enumerated import jakarta.persistence.Table import org.hibernate.annotations.JdbcTypeCode import org.hibernate.type.SqlTypes +import org.vibeerp.api.v1.entity.HasExt import org.vibeerp.platform.persistence.audit.AuditedJpaEntity /** @@ -42,7 +43,7 @@ class Item( itemType: ItemType, baseUomCode: String, active: Boolean = true, -) : AuditedJpaEntity() { +) : AuditedJpaEntity(), HasExt { @Column(name = "code", nullable = false, length = 64) var code: String = code @@ -65,10 +66,17 @@ class Item( @Column(name = "ext", nullable = false, columnDefinition = "jsonb") @JdbcTypeCode(SqlTypes.JSON) - var ext: String = "{}" + override var ext: String = "{}" + + override val extEntityName: String get() = ENTITY_NAME override fun toString(): String = "Item(id=$id, code='$code', type=$itemType, baseUom='$baseUomCode')" + + companion object { + /** Key under which Item's custom fields are declared in `metadata__custom_field`. */ + const val ENTITY_NAME: String = "Item" + } } /** diff --git a/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/http/ItemController.kt b/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/http/ItemController.kt index d7f5add..efae743 100644 --- a/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/http/ItemController.kt +++ b/pbc/pbc-catalog/src/main/kotlin/org/vibeerp/pbc/catalog/http/ItemController.kt @@ -43,20 +43,20 @@ class ItemController( @GetMapping @RequirePermission("catalog.item.read") fun list(): List = - itemService.list().map { it.toResponse() } + itemService.list().map { it.toResponse(itemService) } @GetMapping("/{id}") @RequirePermission("catalog.item.read") fun get(@PathVariable id: UUID): ResponseEntity { val item = itemService.findById(id) ?: return ResponseEntity.notFound().build() - return ResponseEntity.ok(item.toResponse()) + return ResponseEntity.ok(item.toResponse(itemService)) } @GetMapping("/by-code/{code}") @RequirePermission("catalog.item.read") fun getByCode(@PathVariable code: String): ResponseEntity { val item = itemService.findByCode(code) ?: return ResponseEntity.notFound().build() - return ResponseEntity.ok(item.toResponse()) + return ResponseEntity.ok(item.toResponse(itemService)) } @PostMapping @@ -71,8 +71,9 @@ class ItemController( itemType = request.itemType, baseUomCode = request.baseUomCode, active = request.active ?: true, + ext = request.ext, ), - ).toResponse() + ).toResponse(itemService) @PatchMapping("/{id}") @RequirePermission("catalog.item.update") @@ -87,8 +88,9 @@ class ItemController( description = request.description, itemType = request.itemType, active = request.active, + ext = request.ext, ), - ).toResponse() + ).toResponse(itemService) @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) @@ -107,6 +109,7 @@ data class CreateItemRequest( val itemType: ItemType, @field:NotBlank @field:Size(max = 16) val baseUomCode: String, val active: Boolean? = true, + val ext: Map? = null, ) data class UpdateItemRequest( @@ -114,6 +117,7 @@ data class UpdateItemRequest( val description: String? = null, val itemType: ItemType? = null, val active: Boolean? = null, + val ext: Map? = null, ) data class ItemResponse( @@ -124,9 +128,10 @@ data class ItemResponse( val itemType: ItemType, val baseUomCode: String, val active: Boolean, + val ext: Map, ) -private fun Item.toResponse() = ItemResponse( +private fun Item.toResponse(service: ItemService) = ItemResponse( id = this.id, code = this.code, name = this.name, @@ -134,4 +139,5 @@ private fun Item.toResponse() = ItemResponse( itemType = this.itemType, baseUomCode = this.baseUomCode, active = this.active, + ext = service.parseExt(this), ) diff --git a/pbc/pbc-catalog/src/test/kotlin/org/vibeerp/pbc/catalog/application/ItemServiceTest.kt b/pbc/pbc-catalog/src/test/kotlin/org/vibeerp/pbc/catalog/application/ItemServiceTest.kt index 01b258f..ac523db 100644 --- a/pbc/pbc-catalog/src/test/kotlin/org/vibeerp/pbc/catalog/application/ItemServiceTest.kt +++ b/pbc/pbc-catalog/src/test/kotlin/org/vibeerp/pbc/catalog/application/ItemServiceTest.kt @@ -5,16 +5,20 @@ import assertk.assertThat import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf +import io.mockk.Runs import io.mockk.every +import io.mockk.just import io.mockk.mockk import io.mockk.slot import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.vibeerp.api.v1.entity.HasExt import org.vibeerp.pbc.catalog.domain.Item import org.vibeerp.pbc.catalog.domain.ItemType import org.vibeerp.pbc.catalog.infrastructure.ItemJpaRepository import org.vibeerp.pbc.catalog.infrastructure.UomJpaRepository +import org.vibeerp.platform.metadata.customfield.ExtJsonValidator import java.util.Optional import java.util.UUID @@ -22,13 +26,17 @@ class ItemServiceTest { private lateinit var items: ItemJpaRepository private lateinit var uoms: UomJpaRepository + private lateinit var extValidator: ExtJsonValidator private lateinit var service: ItemService @BeforeEach fun setUp() { items = mockk() uoms = mockk() - service = ItemService(items, uoms) + extValidator = mockk() + every { extValidator.applyTo(any(), any()) } just Runs + every { extValidator.parseExt(any()) } returns emptyMap() + service = ItemService(items, uoms, extValidator) } @Test diff --git a/reference-customer/plugin-printing-shop/src/main/resources/META-INF/vibe-erp/metadata/printing-shop.yml b/reference-customer/plugin-printing-shop/src/main/resources/META-INF/vibe-erp/metadata/printing-shop.yml index 3e6c1fd..c30b87e 100644 --- a/reference-customer/plugin-printing-shop/src/main/resources/META-INF/vibe-erp/metadata/printing-shop.yml +++ b/reference-customer/plugin-printing-shop/src/main/resources/META-INF/vibe-erp/metadata/printing-shop.yml @@ -81,6 +81,30 @@ customFields: en: Customer segment zh-CN: 客户细分 + # ── Item: every printing item has a color count (1-color black-only + # job, 4-color CMYK, 5-color CMYK + spot, etc.) and a paper weight + # (grams per square metre). Neither belongs in the generic catalog + # core — they're printing-specific. + - key: printing_shop_color_count + targetEntity: Item + type: + kind: integer + required: false + pii: false + labelTranslations: + en: Color count + zh-CN: 颜色数量 + + - key: printing_shop_paper_gsm + targetEntity: Item + type: + kind: integer + required: false + pii: false + labelTranslations: + en: Paper weight (gsm) + zh-CN: 纸张克重 + # ── SalesOrder: a printing shop sales rep tracks a quote number # that originated in a pre-ERP quoting tool. Storing it as a # free-form string on the order keeps the lineage traceable without -- libgit2 0.22.2