From 2d062470b4bf488dbb4e4a35483b34c0b5866918 Mon Sep 17 00:00:00 2001 From: Fai Luk Date: Sat, 14 Mar 2026 18:13:48 +0800 Subject: [PATCH] adding PS settings --- .../modules/jobOrder/service/PSService.kt | 126 ++++++++++++++++++ .../modules/jobOrder/web/PSController.kt | 51 ++++++- .../service/ProductionScheduleService.kt | 9 +- .../01_create_item_daily_out.sql | 10 ++ 4 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/db/changelog/changes/20260314_01_item_daily_out/01_create_item_daily_out.sql diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt index b090ff6..3ab55a3 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt @@ -4,6 +4,7 @@ import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository import com.ffii.fpsms.modules.jobOrder.web.model.PrintRequest import com.ffii.fpsms.modules.jobOrder.web.model.LaserRequest import org.springframework.stereotype.Service +import java.time.LocalDate import com.ffii.core.support.JdbcDao @@ -12,6 +13,131 @@ open class PSService( private val jdbcDao: JdbcDao, ) { + /** Default: past 30 days including today. */ + fun getItemDailyOut(fromDate: LocalDate? = null, toDate: LocalDate? = null): List> { + val to = toDate ?: LocalDate.now() + val from = fromDate ?: to.minusDays(29) + val args = mapOf( + "fromDate" to from.toString(), + "toDate" to to.toString(), + ) + val sql = """ + SELECT + (SELECT dailyQty FROM item_daily_out WHERE itemCode = items.code) AS dailyQty, + (SELECT + IFNULL(ROUND(AVG(d.dailyQty)), 0) + FROM + (SELECT + SUM(dol.qty) AS dailyQty + FROM + delivery_order_line dol + LEFT JOIN delivery_order do ON dol.deliveryOrderId = do.id + WHERE + do.deleted = 0 + AND dol.itemId = items.id + AND do.estimatedArrivalDate >= :fromDate AND do.estimatedArrivalDate <= :toDate + GROUP BY do.estimatedArrivalDate) AS d) AS avgQtyLastMonth, + (SELECT SUM(reqQty) FROM job_order WHERE bomId = bom.id AND status != 'completed') AS pendingJobQty, + (SELECT COUNT(1) FROM coffee_or_tea WHERE systemType = 'coffee' AND itemCode = items.code AND deleted = 0) AS isCoffee, + (SELECT COUNT(1) FROM coffee_or_tea WHERE systemType = 'tea' AND itemCode = items.code AND deleted = 0) AS isTea, + (SELECT COUNT(1) FROM coffee_or_tea WHERE systemType = 'lemon' AND itemCode = items.code AND deleted = 0) AS isLemon, + CASE WHEN item_fake_onhand.onHandQty IS NOT NULL THEN item_fake_onhand.onHandQty + ELSE inventory.onHandQty - 500 END AS stockQty, + bom.baseScore, + bom.outputQty, + bom.outputQtyUom, + (SELECT udfudesc + FROM delivery_order_line + LEFT JOIN uom_conversion ON delivery_order_line.uomId = uom_conversion.id + WHERE delivery_order_line.itemId = bom.itemId + LIMIT 1) AS doUom, + items.code AS itemCode, + items.name AS itemName, + uc_stock.udfudesc AS unit, + bom.description, + inventory.onHandQty, + item_fake_onhand.onHandQty AS fakeOnHandQty, + bom.itemId, + bom.id AS bomId, + CASE WHEN bom.isDark = 5 THEN 11 WHEN bom.isDark = 3 THEN 6 WHEN bom.isDark = 1 THEN 2 ELSE 0 END AS markDark, + CASE WHEN bom.isFloat = 5 THEN 11 WHEN bom.isFloat = 3 THEN 6 WHEN bom.isFloat = 1 THEN 2 ELSE 0 END AS markFloat, + CASE WHEN bom.isDense = 5 THEN 11 WHEN bom.isDense = 3 THEN 6 WHEN bom.isDense = 1 THEN 2 ELSE 0 END AS markDense, + bom.timeSequence AS markTimeSequence, + bom.complexity AS markComplexity, + CASE WHEN bom.allergicSubstances = 5 THEN 11 ELSE 0 END AS markAS, + inventory.id AS inventoryId + FROM + bom + LEFT JOIN items ON bom.itemId = items.id + LEFT JOIN inventory ON items.id = inventory.itemId + LEFT JOIN item_fake_onhand ON items.code = item_fake_onhand.itemCode + LEFT JOIN item_uom iu ON iu.itemId = items.id AND iu.stockUnit = 1 + LEFT JOIN uom_conversion uc_stock ON uc_stock.id = iu.uomId + WHERE 1 + """.trimIndent() + return jdbcDao.queryForList(sql, args) + } + + /** Update or insert dailyQty for itemCode. */ + fun setDailyQtyOut(itemCode: String, dailyQty: Number) { + val args = mapOf("itemCode" to itemCode, "dailyQty" to dailyQty) + val updated = jdbcDao.executeUpdate( + "UPDATE item_daily_out SET dailyQty = :dailyQty WHERE itemCode = :itemCode", + args + ) + if (updated == 0) { + jdbcDao.executeUpdate( + "INSERT INTO item_daily_out (itemCode, dailyQty) VALUES (:itemCode, :dailyQty)", + args + ) + } + } + + /** Remove dailyQty override for itemCode (delete row from item_daily_out). */ + fun clearDailyQtyOut(itemCode: String) { + jdbcDao.executeUpdate( + "DELETE FROM item_daily_out WHERE itemCode = :itemCode", + mapOf("itemCode" to itemCode) + ) + } + + /** Set or clear item_fake_onhand for itemCode. If onHandQty is null, delete the row; else update or insert. */ + fun setFakeOnHand(itemCode: String, onHandQty: Number?) { + if (onHandQty == null) { + jdbcDao.executeUpdate( + "DELETE FROM item_fake_onhand WHERE itemCode = :itemCode", + mapOf("itemCode" to itemCode) + ) + return + } + val args = mapOf("itemCode" to itemCode, "onHandQty" to onHandQty) + val updated = jdbcDao.executeUpdate( + "UPDATE item_fake_onhand SET onHandQty = :onHandQty WHERE itemCode = :itemCode", + args + ) + if (updated == 0) { + jdbcDao.executeUpdate( + "INSERT INTO item_fake_onhand (itemCode, onHandQty) VALUES (:itemCode, :onHandQty)", + args + ) + } + } + + /** Set or clear coffee_or_tea for itemCode + systemType (coffee / tea / lemon). */ + fun setCoffeeOrTea(itemCode: String, systemType: String, enabled: Boolean) { + val args = mapOf("itemCode" to itemCode, "systemType" to systemType) + jdbcDao.executeUpdate( + "DELETE FROM coffee_or_tea WHERE itemCode = :itemCode AND systemType = :systemType", + args + ) + if (enabled) { + jdbcDao.executeUpdate( + "INSERT INTO coffee_or_tea (itemCode, systemType) VALUES (:itemCode, :systemType)", + args + ) + } + } + fun searchProductionSchedules(produceAt: String): List> { val args = mapOf( "produceAt" to produceAt, diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt index 870bf09..df74fb7 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PSController.kt @@ -28,6 +28,55 @@ class PSController( val results = psService.getProductionScheduleLines(psId) return ResponseEntity.ok(results) } - + /** 每日平均出貨量: itemCode, itemName, avgQtyLastMonth, dailyQty, isCoffee, isTea, isLemon. Default: past 30 days. */ + @GetMapping("/itemDailyOut.json") + fun itemDailyOut( + @RequestParam(required = false) fromDate: String?, + @RequestParam(required = false) toDate: String?, + ): ResponseEntity>> { + val to = toDate?.let { LocalDate.parse(it) } ?: LocalDate.now() + val from = fromDate?.let { LocalDate.parse(it) } ?: to.minusDays(29) + val results = psService.getItemDailyOut(from, to) + return ResponseEntity.ok(results) + } + + /** Set daily out qty for an item (update or insert). */ + @PostMapping("/setDailyQtyOut") + fun setDailyQtyOut(@RequestBody body: Map): ResponseEntity> { + val itemCode = body["itemCode"]?.toString() ?: return ResponseEntity.badRequest().body(mapOf("error" to "itemCode required")) + val dailyQty = (body["dailyQty"] as? Number) ?: (body["dailyQty"]?.toString()?.toDoubleOrNull() ?: 0) + psService.setDailyQtyOut(itemCode, dailyQty) + return ResponseEntity.ok(mapOf("ok" to true, "itemCode" to itemCode, "dailyQty" to dailyQty)) + } + + /** Clear daily out qty for an item (delete from item_daily_out). */ + @PostMapping("/clearDailyQtyOut") + fun clearDailyQtyOut(@RequestBody body: Map): ResponseEntity> { + val itemCode = body["itemCode"]?.toString() ?: return ResponseEntity.badRequest().body(mapOf("error" to "itemCode required")) + psService.clearDailyQtyOut(itemCode) + return ResponseEntity.ok(mapOf("ok" to true, "itemCode" to itemCode)) + } + + /** Set or clear fake onhand for an item. onHandQty: number to set, or omit/null to delete. */ + @PostMapping("/setFakeOnHand") + fun setFakeOnHand(@RequestBody body: Map): ResponseEntity> { + val itemCode = body["itemCode"]?.toString() ?: return ResponseEntity.badRequest().body(mapOf("error" to "itemCode required")) + val onHandQty = body["onHandQty"]?.let { (it as? Number) ?: (it.toString().toDoubleOrNull()) } + psService.setFakeOnHand(itemCode, onHandQty) + return ResponseEntity.ok(mapOf("ok" to true, "itemCode" to itemCode, "onHandQty" to (onHandQty ?: "deleted"))) + } + + /** Set or clear coffee/tea/lemon for an item. systemType: coffee | tea | lemon, enabled: boolean. */ + @PostMapping("/setCoffeeOrTea") + fun setCoffeeOrTea(@RequestBody body: Map): ResponseEntity> { + val itemCode = body["itemCode"]?.toString() ?: return ResponseEntity.badRequest().body(mapOf("error" to "itemCode required")) + val systemType = body["systemType"]?.toString() ?: return ResponseEntity.badRequest().body(mapOf("error" to "systemType required")) + if (systemType !in listOf("coffee", "tea", "lemon")) { + return ResponseEntity.badRequest().body(mapOf("error" to "systemType must be coffee, tea, or lemon")) + } + val enabled = body["enabled"] == true + psService.setCoffeeOrTea(itemCode, systemType, enabled) + return ResponseEntity.ok(mapOf("ok" to true, "itemCode" to itemCode, "systemType" to systemType, "enabled" to enabled)) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt index c23f8c4..0fd4a47 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt @@ -665,8 +665,10 @@ open class ProductionScheduleService( i.* FROM (SELECT - (SELECT - ROUND(AVG(d.dailyQty) * 1.5) + COALESCE( + (SELECT dailyQty FROM item_daily_out WHERE itemCode = items.code), + (SELECT + ROUND(AVG(d.dailyQty)) FROM (SELECT SUM(dol.qty) AS dailyQty @@ -677,7 +679,8 @@ open class ProductionScheduleService( do.deleted = 0 and dol.itemId = items.id AND do.estimatedArrivalDate >= :fromDate AND do.estimatedArrivalDate <= :toDate - GROUP BY do.estimatedArrivalDate) AS d) AS avgQtyLastMonth, + GROUP BY do.estimatedArrivalDate) AS d) + ) AS avgQtyLastMonth, (select sum(reqQty) from job_order where bomId = bom.id and status != 'completed') AS pendingJobQty, diff --git a/src/main/resources/db/changelog/changes/20260314_01_item_daily_out/01_create_item_daily_out.sql b/src/main/resources/db/changelog/changes/20260314_01_item_daily_out/01_create_item_daily_out.sql new file mode 100644 index 0000000..8abeaa0 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20260314_01_item_daily_out/01_create_item_daily_out.sql @@ -0,0 +1,10 @@ +--liquibase formatted sql + +--changeset ps:create item_daily_out table +--precondition onFail:MARK_RAN +--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'item_daily_out' +CREATE TABLE `item_daily_out` ( + `itemCode` VARCHAR(100) NOT NULL, + `dailyQty` DECIMAL(14,2) NULL DEFAULT 0, + PRIMARY KEY (`itemCode`) +);