From 79fa957896777c2e20cbb5a3ba6bdd6c4eaf232e Mon Sep 17 00:00:00 2001 From: Harry Groves Date: Wed, 30 Sep 2026 15:11:51 +0800 Subject: [PATCH] Remove hard-coded placeholders and data --- .../fpsms/modules/common/SettingNames.java | 2 +- .../alert/SchedulerSyncAlertProperties.kt | 2 +- .../common/alert/SchedulerSyncAlertService.kt | 4 +- .../modules/common/demo/DemoSandboxSeeder.kt | 21 ++++++ .../service/DoFloorSupplierSettingsService.kt | 6 +- .../service/PlasticBagPrinterService.kt | 6 +- .../web/model/LaserBag2SettingsResponse.kt | 2 +- .../StockItemConsumptionTrendReportService.kt | 75 ++++++++----------- .../service/StockLotOnhandReportService.kt | 21 ++++-- .../service/StockTakeVarianceReportService.kt | 50 ++++++------- .../com/ffii/fpsms/py/PyJobOrderListItem.kt | 2 +- .../com/ffii/fpsms/py/PyJobOrderListMapper.kt | 4 +- src/main/resources/application-prod.yml | 4 +- src/main/resources/application.yml | 2 +- .../jobOrder/service/LaserBag2PayloadTest.kt | 12 +-- .../report/service/StockLedgerPdfSpeedTest.kt | 2 +- .../ffii/fpsms/py/PyJobOrderListMapperTest.kt | 12 +-- 17 files changed, 119 insertions(+), 108 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java b/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java index f5e4bbae..2a485305 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java +++ b/src/main/java/com/ffii/fpsms/modules/common/SettingNames.java @@ -129,7 +129,7 @@ public abstract class SettingNames { /** Laser marking TCP port (default 45678) */ public static final String LASER_PRINT_PORT = "LASER_PRINT.port"; - /** Comma-separated BOM item codes shown on /laserPrint job list (e.g. PP1175); blank = no filter (all packaging JOs) */ + /** Comma-separated BOM item codes shown on /laserPrint job list; blank = no filter (all packaging JOs) */ public static final String LASER_PRINT_ITEM_CODES = "LASER_PRINT.itemCodes"; /** JSON: last laser TCP send where printer returned receive (job order no., lot, itemId/stockInLineId, etc.) */ diff --git a/src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertProperties.kt b/src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertProperties.kt index fbfe8b38..e0098c43 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertProperties.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertProperties.kt @@ -46,7 +46,7 @@ data class SmsProperties( data class EmailAlertProperties( /** Send sync alerts by email (uses DB MAIL.smtp.* — Office 365). */ val enabled: Boolean = false, - /** Comma-separated recipients, e.g. vluk@2fi-solutions.com.hk,ops@example.com */ + /** Comma-separated recipients. */ val toAddresses: String = "", /** Subject prefix; full subject is prefix + job + code in brackets. */ val subjectPrefix: String = "FPSMS M18 sync alert", diff --git a/src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertService.kt b/src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertService.kt index caae5b34..274d6968 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertService.kt +++ b/src/main/java/com/ffii/fpsms/modules/common/alert/SchedulerSyncAlertService.kt @@ -424,7 +424,7 @@ open class SchedulerSyncAlertService( .map { it.trim() } .filter { it.isNotBlank() } if (emailTo.isEmpty()) { - return "Set scheduler.sync-alert.email.to-addresses (e.g. vluk@2fi-solutions.com.hk)" + return "Set scheduler.sync-alert.email.to-addresses" } if (!syncAlertEmailSender.isSmtpConfigured()) { return syncAlertEmailSender.smtpConfigIssue() @@ -452,7 +452,7 @@ open class SchedulerSyncAlertService( e.message?.contains("not authenticated", ignoreCase = true) == true -> "\n\nOffice 365 fix checklist:\n" + "1. UPDATE settings SET value='true' WHERE name='MAIL.smtp.auth';\n" + - "2. MAIL.smtp.username must match the mailbox (vluk@2fi-solutions.com.hk)\n" + + "2. MAIL.smtp.username must match the sending mailbox\n" + "3. Use an App password if MFA is on (not your normal login password)\n" + "4. IT must enable 'Authenticated SMTP' for this mailbox in Exchange admin\n" + "5. Restart backend after changing settings (SMTP client is cached)" 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 8802ab80..e10525ff 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 @@ -135,6 +135,7 @@ class DemoSandboxSeeder( refuseIfLiveSchemaReadable() transactionTemplate.executeWithoutResult { + clearLivePrinterDefaults() ensureAuthorities() val existing = userRepository.findByUsernameAndDeletedFalse(DEMO_USERNAME) if (existing.isPresent) { @@ -154,6 +155,26 @@ class DemoSandboxSeeder( * The demo login must not be able to read the live company schema. * MySQL error 1142/1044 means access denied; 1049 means that schema is not visible. */ + /** Drop live printer host and item-code defaults copied in with the schema. */ + private fun clearLivePrinterDefaults() { + jdbcTemplate.update( + """ + UPDATE settings + SET value = '' + WHERE name = 'LASER_PRINT.host' + AND value IN ('192.168.18.77', '192.168.17.10') + """.trimIndent(), + ) + jdbcTemplate.update( + """ + UPDATE settings + SET value = '' + WHERE name = 'LASER_PRINT.itemCodes' + AND value IN ('PP1175', 'PP2211') + """.trimIndent(), + ) + } + private fun refuseIfLiveSchemaReadable() { try { jdbcTemplate.queryForObject("SELECT 1 FROM fpsmsdb.`user` LIMIT 1", Int::class.java) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoFloorSupplierSettingsService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoFloorSupplierSettingsService.kt index 76f0acfb..a7d1ea55 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoFloorSupplierSettingsService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoFloorSupplierSettingsService.kt @@ -13,8 +13,8 @@ open class DoFloorSupplierSettingsService( private const val SETTING_DO_FLOOR_SUPPLIERS_2F = "DO.floor.suppliers.2F" private const val SETTING_DO_FLOOR_SUPPLIERS_4F = "DO.floor.suppliers.4F" - private val DEFAULT_SUPPLIERS_2F = listOf("P07", "P06D", "P06Y") - private val DEFAULT_SUPPLIERS_4F = listOf("P06B") + private val DEFAULT_SUPPLIERS_2F = listOf("DEMO-SUP") + private val DEFAULT_SUPPLIERS_4F = listOf("DEMO-SUP") } open fun supplierCodesFromSetting(settingName: String, defaultList: List): List { @@ -33,7 +33,7 @@ open class DoFloorSupplierSettingsService( } open fun allowedSupplierCodesForFloor(floor: String?): List { - val f = floor?.trim()?.uppercase(Locale.ROOT).orEmpty() + val f = floor?.trim()?.replace("/", "")?.uppercase(Locale.ROOT).orEmpty() val (codes2F, codes4F) = loadDoFloorSupplierLists() return when { f.isEmpty() || f == "ALL" || f == "All" -> (codes2F + codes4F).distinct() diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt index 3a574677..1184efe3 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt @@ -106,9 +106,9 @@ class PlasticBagPrinterService( private val hongKongZoneId = ZoneId.of("Asia/Hong_Kong") companion object { - private const val DEFAULT_LASER_BAG2_HOST = "192.168.18.77" + private const val DEFAULT_LASER_BAG2_HOST = "" private const val DEFAULT_LASER_BAG2_PORT = 45678 - private const val DEFAULT_LASER_ITEM_CODES = "PP1175" + private const val DEFAULT_LASER_ITEM_CODES = "" private const val PACKAGING_PROCESS_NAME = "包裝" /** Designer product BMP canvas (LOGO), 1-bit. */ private const val ONPACK_PRODUCT_BMP_WIDTH = 593 @@ -1226,7 +1226,7 @@ class PlasticBagPrinterService( return resource.inputStream.use { it.readBytes() } } - /** Template files on classpath use uppercase code, e.g. `onpack2030_2/PP1175.image`. */ + /** Template files on classpath use uppercase code, e.g. `onpack2030_2/DEMO-FG-01.image`. */ private fun loadOnPack2030_2ImageTemplateOrNull(codeLower: String): ByteArray? { onPackTemplateFileService.loadImage(OnPackTemplateFileService.MACHINE_LEMON, codeLower)?.let { return it } val resourcePath = "onpack2030_2/${codeLower.uppercase()}.image" diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/LaserBag2SettingsResponse.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/LaserBag2SettingsResponse.kt index ec6e64b3..e6526fed 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/LaserBag2SettingsResponse.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/LaserBag2SettingsResponse.kt @@ -1,7 +1,7 @@ package com.ffii.fpsms.modules.jobOrder.web.model /** - * @param itemCodes Comma-separated item codes for the laser job list filter (e.g. `PP1175` or `PP1175,AB123`). Empty string means no filter (show all packaging job orders). + * @param itemCodes Comma-separated item codes for the laser job list filter. Empty string means no filter (show all packaging job orders). * @param lastReceiveSuccess Last job where the laser printer returned a receive ack (from settings JSON); null if never recorded or empty. */ data class LaserBag2SettingsResponse( diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/StockItemConsumptionTrendReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/StockItemConsumptionTrendReportService.kt index b244c0be..e4c02e85 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/StockItemConsumptionTrendReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/StockItemConsumptionTrendReportService.kt @@ -55,10 +55,36 @@ class StockItemConsumptionTrendReportService( return "AND (${conditions.joinToString(" OR ")})" } + private val itemTypes = listOf("mat", "sfg", "fg") + + /** Item types used by the demo catalogue. "All" includes every one of them. */ + private fun itemTypeFilterSql( + stockCategory: String?, + args: MutableMap, + paramPrefix: String, + ): String { + val selected = if ( + stockCategory.isNullOrBlank() || + stockCategory == "All" || + stockCategory.contains("All") + ) { + itemTypes + } else { + stockCategory.split(",").map { it.trim() }.filter { it in itemTypes } + } + val categories = selected.ifEmpty { itemTypes } + val conditions = categories.mapIndexed { index, cat -> + val paramName = "${paramPrefix}_$index" + args[paramName] = cat + "it.type = :$paramName" + } + return "AND (${conditions.joinToString(" OR ")})" + } + /** * Queries the database for Stock Item Consumption Trend Report data. * Flow: - * 1. Filter items by type (MAT, WIP) - this is the stockCategory filter + * 1. Filter items by type (mat, sfg, fg) - this is the stockCategory filter * 2. Use items.code to match stock_ledger.itemCode * 3. Aggregate stock_ledger.outQty by month based on stock_ledger.modified * Supports comma-separated values for stockCategory, stockSubCategory, and itemCode. @@ -72,30 +98,7 @@ class StockItemConsumptionTrendReportService( ): List> { val args = mutableMapOf() - // Filter by stockCategory from items.type (MAT/WIP/NM/FG/CMB) - // Supports multiple categories separated by comma (e.g., "MAT,WIP,NM") - // Valid categories: MAT, WIP, NM, FG, CMB - // If "All" is selected or empty, show all valid categories - // If specific categories are selected, show only those - val validCategories = setOf("MAT", "WIP", "NM", "FG", "CMB") - val stockCategorySql = if (!stockCategory.isNullOrBlank() && stockCategory != "All" && !stockCategory.contains("All")) { - // Handle multiple categories (comma-separated) - val categories = stockCategory.split(",").map { it.trim() }.filter { it.isNotBlank() && it != "All" && validCategories.contains(it) } - if (categories.isNotEmpty()) { - val conditions = categories.mapIndexed { index, cat -> - val paramName = "stockCategory_$index" - args[paramName] = cat - "it.type = :$paramName" - } - "AND (${conditions.joinToString(" OR ")})" - } else { - // If no valid categories, default to all valid categories - "AND (it.type = 'MAT' OR it.type = 'WIP' OR it.type = 'NM' OR it.type = 'FG' OR it.type = 'CMB')" - } - } else { - // If "All" or empty, filter to all valid categories - "AND (it.type = 'MAT' OR it.type = 'WIP' OR it.type = 'NM' OR it.type = 'FG' OR it.type = 'CMB')" - } + val stockCategorySql = itemTypeFilterSql(stockCategory, args, "stockCategory") // itemCode now contains prefixes (first 2 letters), so we use LIKE to match codes starting with those prefixes val itemCodeSql = if (!itemCode.isNullOrBlank()) { @@ -176,30 +179,12 @@ class StockItemConsumptionTrendReportService( /** * Gets list of item code prefixes (first 2 letters) based on stockCategory filter. - * Supports multiple categories separated by comma (e.g., "MAT,WIP,NM"). - * If stockCategory is "All" or null, returns prefixes for all valid categories (MAT, WIP, NM, FG, CMB). + * If stockCategory is "All" or null, returns prefixes for mat, sfg, and fg. * Returns a list of maps with "prefix" key. */ fun getStockItemCodePrefixes(stockCategory: String?): List> { val args = mutableMapOf() - val validCategories = setOf("MAT", "WIP", "NM", "FG", "CMB") - - val stockCategorySql = if (!stockCategory.isNullOrBlank() && stockCategory != "All" && !stockCategory.contains("All")) { - // Handle multiple categories (comma-separated) - val categories = stockCategory.split(",").map { it.trim() }.filter { it.isNotBlank() && it != "All" && validCategories.contains(it) } - if (categories.isNotEmpty()) { - val conditions = categories.mapIndexed { index, cat -> - val paramName = "stockCategory_$index" - args[paramName] = cat - "it.type = :$paramName" - } - "AND (${conditions.joinToString(" OR ")})" - } else { - "AND (it.type = 'MAT' OR it.type = 'WIP' OR it.type = 'NM' OR it.type = 'FG' OR it.type = 'CMB')" - } - } else { - "AND (it.type = 'MAT' OR it.type = 'WIP' OR it.type = 'NM' OR it.type = 'FG' OR it.type = 'CMB')" - } + val stockCategorySql = itemTypeFilterSql(stockCategory, args, "stockCategory") val sql = """ SELECT DISTINCT LEFT(it.code, 2) as prefix diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt index 15fffb4a..1b7a61d9 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt @@ -178,7 +178,11 @@ stock_one AS ( 4 ) END AS avgUnitPriceRaw, - UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) AS rootPoPrefix + CASE + WHEN root_sil.jobOrderId IS NOT NULL THEN 'production' + WHEN root_sil.purchaseOrderId IS NOT NULL THEN 'purchase' + ELSE 'other' + END AS lotOriginKey FROM inventory_lot_line ill INNER JOIN inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = 0 @@ -372,8 +376,8 @@ stock_one AS ( val avg = toDouble(r["avgUnitPriceRaw"]) val key = "${r["itemNo"]}|${r["unitOfMeasure"]}" val last = lastTrnByLot[lotId] - val origin = r["rootPoPrefix"]?.toString().orEmpty().uppercase() - val showPrice = origin == "PP" || origin == "PF" + val origin = r["lotOriginKey"]?.toString().orEmpty() + val showPrice = origin == "purchase" val row = HashMap(24) row["inventoryLotLineId"] = lotId.toString() row["remarks"] = r["remarks"] ?: "" @@ -419,14 +423,17 @@ stock_one AS ( return v.toString().toLongOrNull() ?: 0L } - /** PP/PF = root PO code prefix after TRF walk; other = not PP/PF (ADJ, TRF of other origin, JO, OPEN, …). */ + /** purchase = root stock-in from a PO; production = job order; other = neither. */ private fun buildLotOriginFilterSql(lotOrigin: String?): String { val v = lotOrigin?.trim().orEmpty() if (v.isBlank() || v.equals("All", ignoreCase = true)) return "" return when (v.lowercase()) { - "pp" -> "AND UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'PP'" - "pf" -> "AND UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'PF'" - "other" -> "AND UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) NOT IN ('PP', 'PF')" + "purchase" -> "AND root_sil.purchaseOrderId IS NOT NULL AND root_sil.jobOrderId IS NULL" + "production" -> "AND root_sil.jobOrderId IS NOT NULL" + "other" -> """ + AND (root_sil.purchaseOrderId IS NULL OR root_sil.purchaseOrderId = 0) + AND root_sil.jobOrderId IS NULL + """.trimIndent() else -> "" } } diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt index 9b190257..9c34042f 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt @@ -79,14 +79,11 @@ lot_root_origin AS ( private const val LOT_ROOT_CATEGORY_SQL = """ CASE - WHEN root_sil.jobOrderId IS NOT NULL THEN '工廠生產' + WHEN root_sil.jobOrderId IS NOT NULL THEN '生產' WHEN UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'OPEN' THEN '期初存貨' WHEN UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'ADJ' THEN '倉存調整' - WHEN UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'PP' THEN 'PP' - WHEN UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'PF' THEN 'PF' - WHEN UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'TOA' THEN 'TOA' - WHEN root_sil.purchaseOrderId IS NOT NULL THEN '採購入倉' - ELSE '其他入倉' + WHEN root_sil.purchaseOrderId IS NOT NULL THEN '採購' + ELSE '其他' END""" private const val LOT_TYPE_DISPLAY_SQL = """ @@ -1007,26 +1004,27 @@ ORDER BY * - 篩選用 root_sil / root_po(根來源類型) */ private fun buildLotTypeFilterClause(type: String?): String { - val normalized = type?.trim().orEmpty() - if (normalized.isBlank() || normalized.equals("all", ignoreCase = true)) return "" - return when (normalized) { - "PP" -> "AND UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'PP'" - "PF" -> "AND UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'PF'" - "TOA" -> "AND UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'TOA'" - "工廠生產" -> "AND root_sil.jobOrderId IS NOT NULL" - "倉存調整" -> "AND UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'ADJ'" - "期初存貨" -> "AND UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'OPEN'" - "採購入倉" -> """ - AND root_sil.purchaseOrderId IS NOT NULL - AND UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) NOT IN ('PP', 'PF', 'TOA') - """.trimIndent() - "其他入倉" -> """ - AND (root_sil.purchaseOrderId IS NULL OR root_sil.purchaseOrderId = 0) - AND root_sil.jobOrderId IS NULL - AND UPPER(TRIM(COALESCE(root_sil.type, ''))) NOT IN ('OPEN', 'ADJ') - """.trimIndent() - else -> "" - } + val parts = type?.split(",") + ?.map { it.trim() } + ?.filter { it.isNotBlank() && !it.equals("all", ignoreCase = true) } + .orEmpty() + if (parts.isEmpty()) return "" + val predicates = parts.mapNotNull { lotTypePredicate(it) } + if (predicates.isEmpty()) return "" + return "AND (" + predicates.joinToString(" OR ") { "($it)" } + ")" + } + + private fun lotTypePredicate(type: String): String? = when (type) { + "purchase" -> "root_sil.purchaseOrderId IS NOT NULL AND root_sil.jobOrderId IS NULL" + "production" -> "root_sil.jobOrderId IS NOT NULL" + "adjustment" -> "UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'ADJ'" + "opening" -> "UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'OPEN'" + "other" -> """ + (root_sil.purchaseOrderId IS NULL OR root_sil.purchaseOrderId = 0) + AND root_sil.jobOrderId IS NULL + AND UPPER(TRIM(COALESCE(root_sil.type, ''))) NOT IN ('OPEN', 'ADJ') + """.trimIndent() + else -> null } /** LIKE 多值工具方法 */ diff --git a/src/main/java/com/ffii/fpsms/py/PyJobOrderListItem.kt b/src/main/java/com/ffii/fpsms/py/PyJobOrderListItem.kt index 95b737c5..9e8b7314 100644 --- a/src/main/java/com/ffii/fpsms/py/PyJobOrderListItem.kt +++ b/src/main/java/com/ffii/fpsms/py/PyJobOrderListItem.kt @@ -10,7 +10,7 @@ import java.time.LocalDateTime * stockInLineId and itemId are for QR code: {"itemId": xxx, "stockInLineId": xxx} * lotNo replaces job order no. on the label display. * [itemName] is BOM/item display name plus stock UOM [udfudesc] in parentheses when appropriate; - * GET /py/laser-job-orders (/laserPrint) may omit that suffix for specific item codes (e.g. PP1175) due to laser name length. + * GET /py/laser-job-orders (/laserPrint) may omit that suffix for item codes listed for the laser name-length limit. */ data class PyJobOrderListItem( val id: Long, diff --git a/src/main/java/com/ffii/fpsms/py/PyJobOrderListMapper.kt b/src/main/java/com/ffii/fpsms/py/PyJobOrderListMapper.kt index 9ca929d2..d9d8fd24 100644 --- a/src/main/java/com/ffii/fpsms/py/PyJobOrderListMapper.kt +++ b/src/main/java/com/ffii/fpsms/py/PyJobOrderListMapper.kt @@ -57,9 +57,9 @@ object PyJobOrderListMapper { /** * Item codes where /laserPrint must not append stock UOM to [itemName] (laser field length limit). - * BOM base name only, e.g. PP1175 "鮮檸檬汁(P+4)" without "(1包X2磅)" suffix. + * Empty on the demo branch so no live item code is special-cased. */ - private val LASER_OMIT_APPENDED_STOCK_UOM_ITEM_CODES = setOf("PP1175") + private val LASER_OMIT_APPENDED_STOCK_UOM_ITEM_CODES = emptySet() /** * Display name for GET /py/laser-job-orders and /plastic/laser-job-orders (/laserPrint page). diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index dcd2d6e5..6e40e361 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -61,10 +61,10 @@ scheduler: to-numbers: "" email: enabled: true - to-addresses: "vluk@2fi-solutions.com.hk,kelvin.yau@2fi-solutions.com.hk" + to-addresses: "" po-line: enabled: true - # From = MAIL.smtp.username in DB (e.g. vinluk95@gmail.com + Gmail app password) + # From = MAIL.smtp.username in DB settings. # Laser Bag2 (/laserPrint) TCP auto-send; uses LASER_PRINT host/port/itemCodes from DB and sends first matching job only. laser: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 74ce2bc6..0b309e1f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -57,7 +57,7 @@ scheduler: content-sid: ${TWILIO_WHATSAPP_CONTENT_SID:} email: enabled: ${SYNC_ALERT_EMAIL_ENABLED:true} - to-addresses: "vluk@2fi-solutions.com.hk,kelvin.yau@2fi-solutions.com.hk" + to-addresses: "" do1: min-records-processed: 400 grace-minutes-after-schedule: 30 diff --git a/src/test/kotlin/com/ffii/fpsms/modules/jobOrder/service/LaserBag2PayloadTest.kt b/src/test/kotlin/com/ffii/fpsms/modules/jobOrder/service/LaserBag2PayloadTest.kt index fe9ce57d..c8bad85b 100644 --- a/src/test/kotlin/com/ffii/fpsms/modules/jobOrder/service/LaserBag2PayloadTest.kt +++ b/src/test/kotlin/com/ffii/fpsms/modules/jobOrder/service/LaserBag2PayloadTest.kt @@ -11,20 +11,20 @@ class LaserBag2PayloadTest { @Test fun payload_without_expiry_keeps_original_three_fields() { assertEquals( - "{\"itemId\":1,\"stockInLineId\":2};PP1175;鮮檸檬汁;;", - PlasticBagPrinterService.buildLaserBag2Payload(1, 2, "PP1175", "鮮檸檬汁", null), + "{\"itemId\":1,\"stockInLineId\":2};DEMO-FG-01;鄉村麵包;;", + PlasticBagPrinterService.buildLaserBag2Payload(1, 2, "DEMO-FG-01", "鄉村麵包", null), ) assertEquals( - "0;PP1175;鮮檸檬汁;;", - PlasticBagPrinterService.buildLaserBag2Payload(null, null, "PP1175", "鮮檸檬汁"), + "0;DEMO-FG-01;鄉村麵包;;", + PlasticBagPrinterService.buildLaserBag2Payload(null, null, "DEMO-FG-01", "鄉村麵包"), ) } @Test fun payload_with_iso_expiry_adds_fourth_print_label_field() { assertEquals( - "{\"itemId\":1,\"stockInLineId\":2};PP1175;鮮檸檬汁;Expiry Date 20260821;;", - PlasticBagPrinterService.buildLaserBag2Payload(1, 2, "PP1175", "鮮檸檬汁", "2026-08-21"), + "{\"itemId\":1,\"stockInLineId\":2};DEMO-FG-01;鄉村麵包;Expiry Date 20260821;;", + PlasticBagPrinterService.buildLaserBag2Payload(1, 2, "DEMO-FG-01", "鄉村麵包", "2026-08-21"), ) } diff --git a/src/test/kotlin/com/ffii/fpsms/modules/report/service/StockLedgerPdfSpeedTest.kt b/src/test/kotlin/com/ffii/fpsms/modules/report/service/StockLedgerPdfSpeedTest.kt index 03a714b2..38a59bd4 100644 --- a/src/test/kotlin/com/ffii/fpsms/modules/report/service/StockLedgerPdfSpeedTest.kt +++ b/src/test/kotlin/com/ffii/fpsms/modules/report/service/StockLedgerPdfSpeedTest.kt @@ -23,7 +23,7 @@ class StockLedgerPdfSpeedTest { "stockOut" to "0", "cumBalance" to "1,210", "orderRefNo" to "DO-20260901-${i % 1000}", - "storeLocation" to "W201", + "storeLocation" to "DEMO-A", "totalStockIn" to "400", "totalStockOut" to "0", "totalCumBalance" to "1,210", diff --git a/src/test/kotlin/com/ffii/fpsms/py/PyJobOrderListMapperTest.kt b/src/test/kotlin/com/ffii/fpsms/py/PyJobOrderListMapperTest.kt index 70b74853..5a286703 100644 --- a/src/test/kotlin/com/ffii/fpsms/py/PyJobOrderListMapperTest.kt +++ b/src/test/kotlin/com/ffii/fpsms/py/PyJobOrderListMapperTest.kt @@ -66,14 +66,14 @@ class PyJobOrderListMapperTest { } @Test - fun buildDisplayItemNameForLaser_omits_appended_uom_for_PP1175() { + fun buildDisplayItemNameForLaser_appends_uom_when_name_has_none() { assertEquals( - "鮮檸檬汁(P+4)", - PyJobOrderListMapper.buildDisplayItemNameForLaser("鮮檸檬汁(P+4)", stockUom, "PP1175"), + "鄉村麵包($stockUom)", + PyJobOrderListMapper.buildDisplayItemNameForLaser("鄉村麵包", stockUom, "DEMO-FG-01"), ) assertEquals( - "咖哩汁(1包X2磅)", - PyJobOrderListMapper.buildDisplayItemNameForLaser("咖哩汁(1包X2磅)", stockUom, "PP1080"), + "奶包(1包X2磅)", + PyJobOrderListMapper.buildDisplayItemNameForLaser("奶包", stockUom, "DEMO-FG-02"), ) } @@ -92,7 +92,7 @@ class PyJobOrderListMapperTest { @Test fun shelfLifeForItem_is_null_when_item_missing() { val (days, useMinus18, expiry) = PyJobOrderListMapper.shelfLifeForItem( - "PP1175", + "DEMO-FG-01", emptyMap(), LocalDate.of(2026, 8, 20), )