| @@ -17,6 +17,8 @@ open class DoFloorSupplierSettingsService( | |||
| const val SETTING_JO_PICK_EXCLUDE_WAREHOUSES = "JO.pick.excludeWarehouses" | |||
| const val SETTING_DO_PICK_EXCLUDE_PRINT_MODE = "DO.pick.excludePrintMode" | |||
| const val SETTING_JO_PICK_EXCLUDE_PRINT_MODE = "JO.pick.excludePrintMode" | |||
| const val SETTING_DO_PICK_EXCLUDE_HIDE_QR = "DO.pick.excludeHideQrWarehouses" | |||
| const val SETTING_JO_PICK_EXCLUDE_HIDE_QR = "JO.pick.excludeHideQrWarehouses" | |||
| const val PRINT_MODE_HIDE_LIST = "hideList" | |||
| const val PRINT_MODE_HIDE_QR = "hideQr" | |||
| @@ -36,7 +38,8 @@ open class DoFloorSupplierSettingsService( | |||
| ?.trim() | |||
| .orEmpty() | |||
| if (raw.isEmpty()) return defaultList | |||
| val parsed = raw.split(",").map { it.trim() }.filter { it.isNotEmpty() }.distinct() | |||
| if (raw == EMPTY_CODE_LIST_MARKER) return emptyList() | |||
| val parsed = raw.split(",").map { it.trim() }.filter { it.isNotEmpty() && it != EMPTY_CODE_LIST_MARKER }.distinct() | |||
| return parsed.ifEmpty { defaultList } | |||
| } | |||
| @@ -69,9 +72,47 @@ open class DoFloorSupplierSettingsService( | |||
| open fun loadJoPickExcludePrintMode(): String = printModeFromSetting(SETTING_JO_PICK_EXCLUDE_PRINT_MODE) | |||
| /** | |||
| * Warehouses omitted from the print list. Scanning their physical QR does not pass. | |||
| * Hide-QR warehouses stay on the list and can still be scanned. | |||
| * A missing hide-QR row keeps the old single print mode. | |||
| */ | |||
| open fun loadDoHideListWarehouseCodes(): Set<String> = | |||
| hideListCodes( | |||
| loadDoPickExcludeWarehouseCodes(), | |||
| SETTING_DO_PICK_EXCLUDE_HIDE_QR, | |||
| loadDoPickExcludePrintMode(), | |||
| ) | |||
| open fun loadJoHideListWarehouseCodes(): Set<String> = | |||
| hideListCodes( | |||
| loadJoPickExcludeWarehouseCodes(), | |||
| SETTING_JO_PICK_EXCLUDE_HIDE_QR, | |||
| loadJoPickExcludePrintMode(), | |||
| ) | |||
| open fun rejectsScanOfExcludedWarehouse(printMode: String): Boolean = | |||
| printMode != PRINT_MODE_HIDE_QR | |||
| private fun hideListCodes(all: Set<String>, hideQrSetting: String, legacyMode: String): Set<String> { | |||
| val row = settingsRepository.findByName(hideQrSetting) | |||
| val raw = if (row.isEmpty) "" else row.get().value?.trim().orEmpty() | |||
| if (raw.isEmpty() || raw == EMPTY_CODE_LIST_MARKER) { | |||
| return if (legacyMode == PRINT_MODE_HIDE_QR) emptySet() else all | |||
| } | |||
| val hideQr = parseWarehouseCodes(raw) | |||
| return all.filter { it !in hideQr }.toSet() | |||
| } | |||
| private fun parseWarehouseCodes(raw: String?): Set<String> { | |||
| val value = raw?.trim().orEmpty() | |||
| if (value.isEmpty() || value == EMPTY_CODE_LIST_MARKER) return emptySet() | |||
| return value.split(",") | |||
| .map { it.trim().uppercase(Locale.ROOT) } | |||
| .filter { it.isNotEmpty() && it != EMPTY_CODE_LIST_MARKER } | |||
| .toSet() | |||
| } | |||
| private fun printModeFromSetting(settingName: String): String { | |||
| val raw = settingsRepository.findByName(settingName).map { it.value }.orElse(null)?.trim().orEmpty() | |||
| return if (raw == PRINT_MODE_HIDE_QR) PRINT_MODE_HIDE_QR else PRINT_MODE_HIDE_LIST | |||
| @@ -280,24 +280,22 @@ open class DoWorkbenchMainService( | |||
| ): String? { | |||
| val code = warehouseCode?.trim()?.uppercase(Locale.ROOT).orEmpty() | |||
| if (code.isEmpty()) return null | |||
| val joCodes = doFloorSupplierSettingsService.loadJoPickExcludeWarehouseCodes() | |||
| val (codes, printMode) = when (pickOrderType) { | |||
| PickOrderType.JOB_ORDER -> joCodes to doFloorSupplierSettingsService.loadJoPickExcludePrintMode() | |||
| val hideList = when (pickOrderType) { | |||
| PickOrderType.JOB_ORDER -> doFloorSupplierSettingsService.loadJoHideListWarehouseCodes() | |||
| PickOrderType.Consumable -> { | |||
| val limited = if (userId == null) { | |||
| if (userId == null) { | |||
| emptySet() | |||
| } else { | |||
| ConsumableWorkbenchPickConstants.resolveExcludeWarehouseCodes(userId, joCodes) | |||
| val joCodes = doFloorSupplierSettingsService.loadJoPickExcludeWarehouseCodes() | |||
| val limited = ConsumableWorkbenchPickConstants.resolveExcludeWarehouseCodes(userId, joCodes) | |||
| .map { it.uppercase(Locale.ROOT) } | |||
| .toSet() | |||
| limited.intersect(doFloorSupplierSettingsService.loadJoHideListWarehouseCodes()) | |||
| } | |||
| limited to doFloorSupplierSettingsService.loadJoPickExcludePrintMode() | |||
| } | |||
| else -> doFloorSupplierSettingsService.loadDoPickExcludeWarehouseCodes() to | |||
| doFloorSupplierSettingsService.loadDoPickExcludePrintMode() | |||
| else -> doFloorSupplierSettingsService.loadDoHideListWarehouseCodes() | |||
| } | |||
| if (code !in codes) return null | |||
| if (!doFloorSupplierSettingsService.rejectsScanOfExcludedWarehouse(printMode)) return null | |||
| if (code !in hideList) return null | |||
| return "此儲位不在揀貨清單,掃到實物 QR 也不可以揀。" | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| --liquibase formatted sql | |||
| --changeset fpsms:20260923_pick_exclude_hide_qr | |||
| -- Warehouses that stay on the print list without a QR. | |||
| -- `-` means none. A missing row still follows the single DO/JO print mode. | |||
| INSERT INTO `settings` (`name`, `value`, `category`, `type`) | |||
| SELECT v.name, v.value, v.category, v.type | |||
| FROM ( | |||
| SELECT 'DO.pick.excludeHideQrWarehouses' AS name, | |||
| '-' AS value, | |||
| 'PICK_RULES' AS category, | |||
| 'string' AS type | |||
| ) v | |||
| WHERE NOT EXISTS ( | |||
| SELECT 1 FROM `settings` s WHERE s.name = 'DO.pick.excludeHideQrWarehouses' | |||
| ); | |||
| INSERT INTO `settings` (`name`, `value`, `category`, `type`) | |||
| SELECT v.name, v.value, v.category, v.type | |||
| FROM ( | |||
| SELECT 'JO.pick.excludeHideQrWarehouses' AS name, | |||
| '-' AS value, | |||
| 'PICK_RULES' AS category, | |||
| 'string' AS type | |||
| ) v | |||
| WHERE NOT EXISTS ( | |||
| SELECT 1 FROM `settings` s WHERE s.name = 'JO.pick.excludeHideQrWarehouses' | |||
| ); | |||
| @@ -0,0 +1,29 @@ | |||
| --liquibase formatted sql | |||
| --changeset fpsms:20260923_pick_exclude_rules | |||
| -- Placeholder so the settings row exists. `-` means derive rules from the warehouse-code list. | |||
| -- Saving a rule replaces this with JSON and rewrites the expanded warehouse codes. | |||
| INSERT INTO `settings` (`name`, `value`, `category`, `type`) | |||
| SELECT v.name, v.value, v.category, v.type | |||
| FROM ( | |||
| SELECT 'DO.pick.excludeRules' AS name, | |||
| '-' AS value, | |||
| 'PICK_RULES' AS category, | |||
| 'string' AS type | |||
| ) v | |||
| WHERE NOT EXISTS ( | |||
| SELECT 1 FROM `settings` s WHERE s.name = 'DO.pick.excludeRules' | |||
| ); | |||
| INSERT INTO `settings` (`name`, `value`, `category`, `type`) | |||
| SELECT v.name, v.value, v.category, v.type | |||
| FROM ( | |||
| SELECT 'JO.pick.excludeRules' AS name, | |||
| '-' AS value, | |||
| 'PICK_RULES' AS category, | |||
| 'string' AS type | |||
| ) v | |||
| WHERE NOT EXISTS ( | |||
| SELECT 1 FROM `settings` s WHERE s.name = 'JO.pick.excludeRules' | |||
| ); | |||