From 4f88c6d40933f7b463ecedf655984a39409e4b70 Mon Sep 17 00:00:00 2001 From: zichun Date: Fri, 10 Apr 2026 11:32:10 +0800 Subject: [PATCH] refactor: split demo-specific code out of main --- distribution/src/main/kotlin/org/vibeerp/demo/DemoSeedRunner.kt | 222 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ distribution/src/main/resources/application-dev.yaml | 8 -------- web/src/pages/DashboardPage.tsx | 52 +++++++++++++++++++++++----------------------------- 3 files changed, 23 insertions(+), 259 deletions(-) delete mode 100644 distribution/src/main/kotlin/org/vibeerp/demo/DemoSeedRunner.kt diff --git a/distribution/src/main/kotlin/org/vibeerp/demo/DemoSeedRunner.kt b/distribution/src/main/kotlin/org/vibeerp/demo/DemoSeedRunner.kt deleted file mode 100644 index 7b0a88c..0000000 --- a/distribution/src/main/kotlin/org/vibeerp/demo/DemoSeedRunner.kt +++ /dev/null @@ -1,222 +0,0 @@ -package org.vibeerp.demo - -import org.slf4j.LoggerFactory -import org.springframework.boot.CommandLineRunner -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Component -import org.springframework.transaction.annotation.Transactional -import org.vibeerp.pbc.catalog.application.CreateItemCommand -import org.vibeerp.pbc.catalog.application.ItemService -import org.vibeerp.pbc.catalog.domain.ItemType -import org.vibeerp.pbc.inventory.application.CreateLocationCommand -import org.vibeerp.pbc.inventory.application.LocationService -import org.vibeerp.pbc.inventory.application.StockBalanceService -import org.vibeerp.pbc.inventory.domain.LocationType -import org.vibeerp.pbc.orders.purchase.application.CreatePurchaseOrderCommand -import org.vibeerp.pbc.orders.purchase.application.PurchaseOrderLineCommand -import org.vibeerp.pbc.orders.purchase.application.PurchaseOrderService -import org.vibeerp.pbc.orders.sales.application.CreateSalesOrderCommand -import org.vibeerp.pbc.orders.sales.application.SalesOrderLineCommand -import org.vibeerp.pbc.orders.sales.application.SalesOrderService -import org.vibeerp.pbc.partners.application.CreatePartnerCommand -import org.vibeerp.pbc.partners.application.PartnerService -import org.vibeerp.pbc.partners.domain.PartnerType -import org.vibeerp.pbc.production.application.CreateWorkOrderCommand -import org.vibeerp.pbc.production.application.WorkOrderInputCommand -import org.vibeerp.pbc.production.application.WorkOrderOperationCommand -import org.vibeerp.pbc.production.application.WorkOrderService -import org.vibeerp.platform.persistence.security.PrincipalContext -import java.math.BigDecimal -import java.time.LocalDate - -/** - * One-shot demo data seeder matching the EBC-PP-001 work-order - * management process from the reference printing company (昆明五彩印务). - * - * Gated behind `vibeerp.demo.seed=true` (dev profile only). - * Idempotent via sentinel item check. - * - * The seeded data demonstrates the core flow from the doc: - * A-010 Sales order confirmed (SO already in DRAFT, ready to confirm) - * B-010 System auto-generates work orders from confirmed SO lines - * B-020 Work order with BOM + routing (pre-seeded WO-PRINT-0001) - * C-040 Material requisition (BOM inputs consumed on WO complete) - * C-050 Material picking from warehouse (inventory ledger writes) - * E-010 Shop floor tracking (shop-floor dashboard polls IN_PROGRESS WOs) - */ -@Component -@ConditionalOnProperty(prefix = "vibeerp.demo", name = ["seed"], havingValue = "true") -class DemoSeedRunner( - private val itemService: ItemService, - private val locationService: LocationService, - private val stockBalanceService: StockBalanceService, - private val partnerService: PartnerService, - private val salesOrderService: SalesOrderService, - private val purchaseOrderService: PurchaseOrderService, - private val workOrderService: WorkOrderService, -) : CommandLineRunner { - - private val log = LoggerFactory.getLogger(DemoSeedRunner::class.java) - - @Transactional - override fun run(vararg args: String?) { - if (itemService.findByCode(SENTINEL) != null) { - log.info("Demo seed: already present ({}); skipping", SENTINEL) - return - } - log.info("Demo seed: populating printing-company dataset...") - PrincipalContext.runAs("__demo_seed__") { - seedItems() - seedLocations() - seedPartners() - seedStock() - seedWorkOrder() - seedSalesOrders() - seedPurchaseOrder() - } - log.info("Demo seed: done") - } - - // ─── Catalog items (printing industry) ─────────────────────────── - - private fun seedItems() { - // Raw materials - item(SENTINEL, "80g A4 white card stock", ItemType.GOOD, "sheet") - item("INK-4C", "4-color offset ink set (CMYK)", ItemType.GOOD, "kg") - item("PLATE-CTP", "CTP printing plate", ItemType.GOOD, "ea") - item("COVER-MATT", "Matt lamination film", ItemType.GOOD, "m2") - // Finished goods - item("BIZ-CARD-250", "Business cards, 250gsm coated, 100/box", ItemType.GOOD, "pack") - item("BROCHURE-A5", "A5 tri-fold brochure, full color", ItemType.GOOD, "ea") - item("POSTER-A3", "A3 promotional poster, glossy", ItemType.GOOD, "ea") - } - - private fun item(code: String, name: String, type: ItemType, uom: String) { - itemService.create(CreateItemCommand(code = code, name = name, description = null, itemType = type, baseUomCode = uom)) - } - - // ─── Locations ─────────────────────────────────────────────────── - - private fun seedLocations() { - location("WH-RAW", "Raw materials warehouse") - location("WH-FG", "Finished goods warehouse") - } - - private fun location(code: String, name: String) { - locationService.create(CreateLocationCommand(code = code, name = name, type = LocationType.WAREHOUSE)) - } - - // ─── Partners ──────────────────────────────────────────────────── - - private fun seedPartners() { - partner("CUST-WCAD", "Wucai Advertising Co.", PartnerType.CUSTOMER, "info@wucai-ad.example") - partner("CUST-GLOBE", "Globe Marketing Ltd.", PartnerType.CUSTOMER, "ops@globe.example") - partner("SUPP-HZPAPER", "Huazhong Paper Co.", PartnerType.SUPPLIER, "sales@hzpaper.example") - partner("SUPP-INKPRO", "InkPro Industries", PartnerType.SUPPLIER, "orders@inkpro.example") - } - - private fun partner(code: String, name: String, type: PartnerType, email: String) { - partnerService.create(CreatePartnerCommand(code = code, name = name, type = type, email = email)) - } - - // ─── Opening stock ─────────────────────────────────────────────── - - private fun seedStock() { - val raw = locationService.findByCode("WH-RAW")!! - val fg = locationService.findByCode("WH-FG")!! - - stockBalanceService.adjust(SENTINEL, raw.id, BigDecimal("5000")) - stockBalanceService.adjust("INK-4C", raw.id, BigDecimal("80")) - stockBalanceService.adjust("PLATE-CTP", raw.id, BigDecimal("100")) - stockBalanceService.adjust("COVER-MATT", raw.id, BigDecimal("500")) - - stockBalanceService.adjust("BIZ-CARD-250", fg.id, BigDecimal("200")) - stockBalanceService.adjust("BROCHURE-A5", fg.id, BigDecimal("150")) - stockBalanceService.adjust("POSTER-A3", fg.id, BigDecimal("50")) - } - - // ─── Pre-seeded work order with BOM + routing ──────────────────── - // - // Matches the EBC-PP-001 flow: a production work order for - // business cards with 3 BOM inputs and a 3-step routing - // (CTP plate-making → offset printing → post-press finishing). - // Left in DRAFT so the demo operator can start it, walk the - // operations on the shop-floor dashboard, and complete it. - - private fun seedWorkOrder() { - workOrderService.create( - CreateWorkOrderCommand( - code = "WO-PRINT-0001", - outputItemCode = "BIZ-CARD-250", - outputQuantity = BigDecimal("50"), - dueDate = LocalDate.now().plusDays(3), - inputs = listOf( - WorkOrderInputCommand(lineNo = 1, itemCode = SENTINEL, quantityPerUnit = BigDecimal("10"), sourceLocationCode = "WH-RAW"), - WorkOrderInputCommand(lineNo = 2, itemCode = "INK-4C", quantityPerUnit = BigDecimal("0.2"), sourceLocationCode = "WH-RAW"), - WorkOrderInputCommand(lineNo = 3, itemCode = "PLATE-CTP", quantityPerUnit = BigDecimal("1"), sourceLocationCode = "WH-RAW"), - ), - operations = listOf( - WorkOrderOperationCommand(lineNo = 1, operationCode = "CTP", workCenter = "CTP-ROOM-01", standardMinutes = BigDecimal("30")), - WorkOrderOperationCommand(lineNo = 2, operationCode = "PRINT", workCenter = "PRESS-A", standardMinutes = BigDecimal("45")), - WorkOrderOperationCommand(lineNo = 3, operationCode = "FINISH", workCenter = "BIND-01", standardMinutes = BigDecimal("20")), - ), - ), - ) - } - - // ─── Sales orders (DRAFT — confirm to auto-spawn work orders) ─── - // - // Two orders for different customers. Confirming either one - // triggers SalesOrderConfirmedSubscriber in pbc-production, - // which auto-creates one DRAFT work order per SO line (the - // B-010 step from EBC-PP-001). The demo operator watches the - // work order count jump on the dashboard after clicking Confirm. - - private fun seedSalesOrders() { - salesOrderService.create( - CreateSalesOrderCommand( - code = "SO-2026-0001", - partnerCode = "CUST-WCAD", - orderDate = LocalDate.now(), - currencyCode = "USD", - lines = listOf( - SalesOrderLineCommand(lineNo = 1, itemCode = "BIZ-CARD-250", quantity = BigDecimal("100"), unitPrice = BigDecimal("8.50"), currencyCode = "USD"), - SalesOrderLineCommand(lineNo = 2, itemCode = "BROCHURE-A5", quantity = BigDecimal("500"), unitPrice = BigDecimal("2.20"), currencyCode = "USD"), - ), - ), - ) - salesOrderService.create( - CreateSalesOrderCommand( - code = "SO-2026-0002", - partnerCode = "CUST-GLOBE", - orderDate = LocalDate.now(), - currencyCode = "USD", - lines = listOf( - SalesOrderLineCommand(lineNo = 1, itemCode = "POSTER-A3", quantity = BigDecimal("200"), unitPrice = BigDecimal("3.80"), currencyCode = "USD"), - ), - ), - ) - } - - // ─── Purchase order (DRAFT — confirm + receive to restock) ─────── - - private fun seedPurchaseOrder() { - purchaseOrderService.create( - CreatePurchaseOrderCommand( - code = "PO-2026-0001", - partnerCode = "SUPP-HZPAPER", - orderDate = LocalDate.now(), - expectedDate = LocalDate.now().plusDays(5), - currencyCode = "USD", - lines = listOf( - PurchaseOrderLineCommand(lineNo = 1, itemCode = SENTINEL, quantity = BigDecimal("10000"), unitPrice = BigDecimal("0.03"), currencyCode = "USD"), - PurchaseOrderLineCommand(lineNo = 2, itemCode = "INK-4C", quantity = BigDecimal("50"), unitPrice = BigDecimal("45.00"), currencyCode = "USD"), - ), - ), - ) - } - - companion object { - const val SENTINEL: String = "PAPER-80G-A4" - } -} diff --git a/distribution/src/main/resources/application-dev.yaml b/distribution/src/main/resources/application-dev.yaml index da7677f..752cea8 100644 --- a/distribution/src/main/resources/application-dev.yaml +++ b/distribution/src/main/resources/application-dev.yaml @@ -21,14 +21,6 @@ vibeerp: directory: ./plugins-dev files: local-path: ./files-dev - demo: - # Opt in to the one-shot DemoSeedRunner (in distribution/.../demo/) - # which stages a tiny but representative dataset on first boot. - # Idempotent — the runner checks for its own sentinel item before - # creating anything, so restarting the dev server is a no-op once - # the seed has been applied. Production deployments leave this - # property unset and the @ConditionalOnProperty bean never loads. - seed: true logging: level: diff --git a/web/src/pages/DashboardPage.tsx b/web/src/pages/DashboardPage.tsx index fbaed75..cfd5915 100644 --- a/web/src/pages/DashboardPage.tsx +++ b/web/src/pages/DashboardPage.tsx @@ -100,48 +100,42 @@ export function DashboardPage() { )}
-

- Demo: EBC-PP-001 Work Order Management Flow -

+

Getting started

- Walk the printing company's production flow end-to-end — sales order to finished goods. + The framework's buy-make-sell loop, end to end.

  1. - Create or open a sales order —{' '} - - create a new order - {' '} - or open an existing one from the{' '} - list. + Set up master data — create{' '} + items,{' '} + partners, and{' '} + locations. + Then{' '} + adjust stock{' '} + to set opening balances.
  2. - Confirm the order — click{' '} - Confirm. The system - auto-generates one production work order per line (EBC-PP-001 step B-010). Check the{' '} - Work Orders{' '} - page to see them appear. Finance posts an AR entry automatically. + Create a sales order —{' '} + new order{' '} + with line items. Confirm it — the system auto-generates production work orders and + posts an AR journal entry with double-entry lines (DR Accounts Receivable, CR Revenue).
  3. - Walk the pre-seeded work order — open{' '} - WO-PRINT-0001. - It has a 3-step routing (CTP plate-making → offset printing → post-press finishing) and - a BOM with paper, ink, and CTP plates. Click Start{' '} - to put it in progress, then watch it on the{' '} - Shop Floor dashboard. + Walk the work order — start it, walk routing operations on the{' '} + Shop Floor, + then complete it. Materials are consumed, finished goods credited.
  4. - Complete the work order — materials are consumed from the warehouse, - finished goods are credited. Check{' '} - Stock Balances{' '} - and{' '} + Ship the sales order — stock leaves the warehouse, the AR journal + entry settles. View the ledger in{' '} Movements{' '} - to see the ledger entries. + and double-entry lines in{' '} + Journal Entries.
  5. - Ship the sales order — finished goods leave the warehouse, the AR - journal entry settles from POSTED to SETTLED in{' '} - Finance. + Restock via purchase — create a{' '} + purchase order, + confirm, and receive into a warehouse. AP journal entry posts and settles.
-- libgit2 0.22.2