diff --git a/src/main/java/com/ffii/fpsms/modules/common/demo/DemoSandboxSeeder.kt b/src/main/java/com/ffii/fpsms/modules/common/demo/DemoSandboxSeeder.kt index 03554b19..8802ab80 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/demo/DemoSandboxSeeder.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/demo/DemoSandboxSeeder.kt @@ -139,10 +139,13 @@ class DemoSandboxSeeder( val existing = userRepository.findByUsernameAndDeletedFalse(DEMO_USERNAME) if (existing.isPresent) { assignDemoAuthorities(existing.get().id) + backfillDemoPurchaseLinePrices() + seedDashboardTrucks() log.info("Demo sandbox already present (user '{}').", DEMO_USERNAME) return@executeWithoutResult } seed() + seedDashboardTrucks() log.info("Demo sandbox seeded. Sign in as {} / {}", DEMO_USERNAME, DEMO_PASSWORD) } } @@ -171,6 +174,233 @@ class DemoSandboxSeeder( throw IllegalStateException("Demo database login can read the live schema. Refusing to start.") } + /** + * Truck board reads delivery_order_pick_order for today, tomorrow, and the day after. + * If those rows are still on an earlier day, shift them to the current date. + * Opening the dashboard does this, so a demo on a later day still has trucks to show. + */ + @Synchronized + fun alignDashboardTrucksToToday() { + val anchored = jdbcTemplate.query( + """ + SELECT requiredDeliveryDate + FROM delivery_order_pick_order + WHERE ticketNo = 'DEMO-DASH-0-2F-AM-A' AND deleted = 0 + LIMIT 1 + """.trimIndent(), + ) { rs, _ -> rs.getDate("requiredDeliveryDate")?.toLocalDate() }.firstOrNull() + if (anchored == LocalDate.now()) return + seedDashboardTrucks() + } + + private fun seedDashboardTrucks() { + val loafId = demoItemId("DEMO-FG-01") + val bunId = demoItemId("DEMO-FG-02") + val pcsId = jdbcTemplate.query( + "SELECT id FROM uom_conversion WHERE code = 'PCS' AND deleted = 0 ORDER BY id LIMIT 1", + ) { rs, _ -> rs.getLong("id") }.firstOrNull() + + for (truck in dashboardTrucks()) { + val inserted = jdbcTemplate.update( + """ + INSERT INTO delivery_order_pick_order ( + storeId, requiredDeliveryDate, truckDepartureTime, truckLanceCode, + shopCode, shopName, loadingSequence, ticketNo, ticketStatus, + handlerName, createdBy, modifiedBy, deleted + ) + SELECT ?, DATE_ADD(CURDATE(), INTERVAL ? DAY), ?, ?, ?, ?, ?, ?, ?, '示範倉務', 'demo', 'demo', 0 + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 FROM delivery_order_pick_order WHERE ticketNo = ? + ) + """.trimIndent(), + truck.storeId, + truck.dayOffset, + truck.departure, + truck.lane, + truck.shopCode, + truck.shopName, + truck.sequence, + truck.ticketNo, + truck.status, + truck.ticketNo, + ) + jdbcTemplate.update( + """ + UPDATE delivery_order_pick_order + SET storeId = ?, + requiredDeliveryDate = DATE_ADD(CURDATE(), INTERVAL ? DAY), + truckDepartureTime = ?, + truckLanceCode = ?, + shopCode = ?, + shopName = ?, + loadingSequence = ?, + ticketStatus = ?, + handlerName = '示範倉務', + ticketReleaseTime = CASE + WHEN ? = 'pending' THEN NULL + ELSE TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL ? DAY), ?) + END, + ticketCompleteDateTime = CASE + WHEN ? = 'completed' THEN TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL ? DAY), ?) + ELSE NULL + END, + deleted = 0 + WHERE ticketNo = ? + """.trimIndent(), + truck.storeId, + truck.dayOffset, + truck.departure, + truck.lane, + truck.shopCode, + truck.shopName, + truck.sequence, + truck.status, + truck.status, + truck.dayOffset, + truck.releaseTime, + truck.status, + truck.dayOffset, + truck.completeTime, + truck.ticketNo, + ) + jdbcTemplate.update( + """ + INSERT INTO truck ( + TruckLanceCode, DepartureTime, ShopCode, ShopName, LoadingSequence, + Store_id, districtReference, remark, deleted + ) + SELECT ?, ?, ?, ?, ?, ?, '示範區', '示範車線', 0 + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 FROM truck + WHERE deleted = 0 + AND TruckLanceCode = ? + AND Store_id = ? + AND DepartureTime = ? + ) + """.trimIndent(), + truck.lane, + truck.departure, + truck.shopCode, + truck.shopName, + truck.sequence, + truck.storeId, + truck.lane, + truck.storeId, + truck.departure, + ) + if (loafId != null && bunId != null && pcsId != null) { + seedDashboardPickLines(truck, loafId, bunId, pcsId) + } + if (inserted > 0) { + log.info("Added demo truck ticket {}.", truck.ticketNo) + } + } + } + + private fun seedDashboardPickLines(truck: DemoDashTruck, loafId: Long, bunId: Long, pcsId: Long) { + val pickCode = "DEMO-DASH-PK-${truck.ticketNo}" + jdbcTemplate.update( + """ + INSERT INTO pick_order (code, status, type, targetDate, deliveryOrderPickOrderId, deleted) + SELECT ?, 'released', 'do', TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL ? DAY), ?), d.id, 0 + FROM delivery_order_pick_order d + WHERE d.ticketNo = ? + AND NOT EXISTS (SELECT 1 FROM pick_order p WHERE p.code = ?) + """.trimIndent(), + pickCode, + truck.dayOffset, + truck.departure, + truck.ticketNo, + pickCode, + ) + jdbcTemplate.update( + """ + UPDATE pick_order p + JOIN delivery_order_pick_order d ON d.ticketNo = ? + SET p.deliveryOrderPickOrderId = d.id, + p.targetDate = TIMESTAMP(DATE_ADD(CURDATE(), INTERVAL ? DAY), ?), + p.deleted = 0 + WHERE p.code = ? + """.trimIndent(), + truck.ticketNo, + truck.dayOffset, + truck.departure, + pickCode, + ) + for ((itemId, qty) in listOf(loafId to "6", bunId to "4")) { + jdbcTemplate.update( + """ + INSERT INTO pick_order_line (poId, itemId, qty, uomId, status, deleted) + SELECT p.id, ?, ?, ?, 'pending', 0 + FROM pick_order p + WHERE p.code = ? + AND NOT EXISTS ( + SELECT 1 FROM pick_order_line l + WHERE l.poId = p.id AND l.itemId = ? AND l.deleted = 0 + ) + """.trimIndent(), + itemId, + qty, + pcsId, + pickCode, + itemId, + ) + } + } + + private fun demoItemId(code: String): Long? { + return jdbcTemplate.query( + "SELECT id FROM items WHERE code = ? AND deleted = 0 ORDER BY id LIMIT 1", + { rs, _ -> rs.getLong("id") }, + code, + ).firstOrNull() + } + + private fun dashboardTrucks(): List = listOf( + DemoDashTruck("DEMO-DASH-0-2F-AM-A", 0, "2/F", "示範-早班", "08:30:00", "DEMO-SHP", "海港咖啡店", 1, "completed", "07:10:00", "07:48:00"), + DemoDashTruck("DEMO-DASH-0-2F-AM-B", 0, "2/F", "示範-早班", "08:30:00", "DEMO-BAK", "山景餅店", 2, "completed", "07:20:00", "07:55:00"), + DemoDashTruck("DEMO-DASH-0-2F-PM-A", 0, "2/F", "示範-午班", "14:30:00", "DEMO-BAY", "灣畔餐廳", 1, "released", "12:05:00", null), + DemoDashTruck("DEMO-DASH-0-2F-PM-B", 0, "2/F", "示範-午班", "14:30:00", "DEMO-CAMP", "校園小食", 2, "pending", null, null), + DemoDashTruck("DEMO-DASH-0-4F-PM-A", 0, "4/F", "示範-晚班", "17:00:00", "DEMO-SHP", "海港咖啡店", 1, "pending", null, null), + DemoDashTruck("DEMO-DASH-1-2F-AM-A", 1, "2/F", "示範-早班", "09:00:00", "DEMO-SHP", "海港咖啡店", 1, "pending", null, null), + DemoDashTruck("DEMO-DASH-1-2F-AM-B", 1, "2/F", "示範-早班", "09:00:00", "DEMO-BAK", "山景餅店", 2, "pending", null, null), + DemoDashTruck("DEMO-DASH-1-4F-NOON-A", 1, "4/F", "示範-午班", "13:30:00", "DEMO-BAY", "灣畔餐廳", 1, "pending", null, null), + DemoDashTruck("DEMO-DASH-2-2F-AM-A", 2, "2/F", "示範-早班", "09:15:00", "DEMO-SHP", "海港咖啡店", 1, "pending", null, null), + DemoDashTruck("DEMO-DASH-2-4F-EVE-A", 2, "4/F", "示範-晚班", "16:00:00", "DEMO-CAMP", "校園小食", 1, "pending", null, null), + ) + + private data class DemoDashTruck( + val ticketNo: String, + val dayOffset: Int, + val storeId: String, + val lane: String, + val departure: String, + val shopCode: String, + val shopName: String, + val sequence: Int, + val status: String, + val releaseTime: String?, + val completeTime: String?, + ) + + /** Existing sandboxes were seeded without a unit price, which made PO detail fail. */ + private fun backfillDemoPurchaseLinePrices() { + val updated = jdbcTemplate.update( + """ + UPDATE purchase_order_line pol + INNER JOIN purchase_order po ON po.id = pol.purchaseOrderId + SET pol.price = 12.50, pol.up = 12.50 + WHERE po.code LIKE 'DEMO-PO-%' + AND (pol.price IS NULL OR pol.up IS NULL) + """.trimIndent(), + ) + if (updated > 0) { + log.info("Filled unit price on {} demo purchase order line(s).", updated) + } + } + private fun ensureAuthorities() { val roles = listOf( "ADMIN" to "行政", @@ -481,6 +711,8 @@ class DemoSandboxSeeder( uomM18 = uom this.qty = qty qtyM18 = qty + up = BigDecimal("12.50") + price = BigDecimal("12.50") this.status = status m18DataLog = dataLog }) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt index acf8cc3f..f4b74033 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt @@ -45,8 +45,10 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordRepository import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecord +import org.springframework.beans.factory.ObjectProvider import org.springframework.context.annotation.Lazy import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLineRepository +import com.ffii.fpsms.modules.common.demo.DemoSandboxSeeder import com.ffii.fpsms.modules.deliveryOrder.web.models.TicketReleaseTableResponse import com.ffii.fpsms.modules.deliveryOrder.web.models.TruckScheduleDashboardResponse import com.ffii.fpsms.modules.deliveryOrder.web.models.SearchDeliveryOrderInfoRequest @@ -64,7 +66,8 @@ open class DoPickOrderService( private val doPickOrderLineRepository: DoPickOrderLineRepository, @Lazy private val deliveryOrderRepository: DeliveryOrderRepository, private val doPickOrderLineRecordRepository: DoPickOrderLineRecordRepository, - private val pickOrderLineRepository: PickOrderLineRepository + private val pickOrderLineRepository: PickOrderLineRepository, + private val demoSandboxSeeder: ObjectProvider, ) { open fun findReleasedDoPickOrders(): List { return doPickOrderRepository.findByTicketStatusIn( @@ -847,6 +850,7 @@ open class DoPickOrderService( * Groups DoPickOrder and DoPickOrderRecord data to provide summary statistics. */ open fun getTruckScheduleDashboard(targetDate: LocalDate): List { + demoSandboxSeeder.getIfAvailable()?.alignDashboardTrucksToToday() // Source of truth: delivery_order_pick_order (+ linked pick_order / pick_order_line) // // NOTE: delivery_order_pick_order 沒有 truckId 欄位;dashboard 的 truckId 目前僅作為展示/鍵值用途, diff --git a/src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/projections/PurchaseOrderInfo.kt b/src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/projections/PurchaseOrderInfo.kt index 41f09a96..31bb2e62 100644 --- a/src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/projections/PurchaseOrderInfo.kt +++ b/src/main/java/com/ffii/fpsms/modules/purchaseOrder/entity/projections/PurchaseOrderInfo.kt @@ -8,9 +8,9 @@ import java.time.LocalDateTime interface PurchaseOrderInfo { val id: Long val code: String - val orderDate: LocalDateTime - val estimatedArrivalDate: LocalDateTime - val completeDate: LocalDateTime + val orderDate: LocalDateTime? + val estimatedArrivalDate: LocalDateTime? + val completeDate: LocalDateTime? val status: String @get:Value("#{target.supplier?.name}") val supplier: String? diff --git a/src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt b/src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt index 80a3e833..7f97bc00 100644 --- a/src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt @@ -268,7 +268,7 @@ open fun getPoSummariesByIds(ids: List): List { return purchaseOrderRepository.findAll() } - open fun getDetailedPo(id: Long): Map { + open fun getDetailedPo(id: Long): Map { // REMINDER: po code have duplication val po = purchaseOrderRepository.findPurchaseOrderInfoByIdAndDeletedIsFalse(id) val pol = polRepository.findAllByPurchaseOrderIdAndDeletedIsFalse(id) @@ -336,7 +336,7 @@ open fun getPoSummariesByIds(ids: List): List { .sumOf { line -> line.putAwayLines?.sumOf { it.qty?.takeIf { qty -> qty > BigDecimal.ZERO } ?: BigDecimal.ZERO } ?: BigDecimal.ZERO}, receivedQty = thisPol.stockInLines.sumOf { it.acceptedQty ?: BigDecimal.ZERO }, uom = thisPol.uomM18 ?: thisPol.uom!!, - price = thisPol.price!!, + price = thisPol.price ?: thisPol.up ?: BigDecimal.ZERO, status = thisPol.status!!.toString(), stockInLine = inLine, qcItems = qcItems, diff --git a/src/main/java/com/ffii/fpsms/modules/purchaseOrder/web/PurchaseOrderController.kt b/src/main/java/com/ffii/fpsms/modules/purchaseOrder/web/PurchaseOrderController.kt index d2ea361b..09532851 100644 --- a/src/main/java/com/ffii/fpsms/modules/purchaseOrder/web/PurchaseOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/purchaseOrder/web/PurchaseOrderController.kt @@ -98,7 +98,7 @@ class PurchaseOrderController( } @GetMapping("/detail/{id}") // purchaseOrderId - fun getDetailedPo(@PathVariable id: Long): Map { + fun getDetailedPo(@PathVariable id: Long): Map { /* // tested the sample printing function by a pdf file