diff --git a/src/main/java/com/ffii/core/utils/PdfUtils.java b/src/main/java/com/ffii/core/utils/PdfUtils.java index e4783c57..d295f0b9 100644 --- a/src/main/java/com/ffii/core/utils/PdfUtils.java +++ b/src/main/java/com/ffii/core/utils/PdfUtils.java @@ -10,10 +10,13 @@ import java.util.List; import net.sf.jasperreports.engine.JRDataSource; import net.sf.jasperreports.engine.JREmptyDataSource; +import net.sf.jasperreports.engine.JRParameter; import net.sf.jasperreports.engine.JasperFillManager; import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; import net.sf.jasperreports.engine.data.JRMapCollectionDataSource; +import java.util.HashMap; + public class PdfUtils { public static JasperReport loadJasperReport(String path) throws Exception { @@ -34,7 +37,12 @@ public class PdfUtils { } else { dataSources = new JRBeanCollectionDataSource(loopList); } - JasperPrint jasperPrint = JasperFillManager.fillReport(report, params, dataSources); + Map filled = new HashMap<>(); + if (params != null) { + filled.putAll(params); + } + filled.put(JRParameter.REPORT_RESOURCE_BUNDLE, ReportMessages.bundle()); + JasperPrint jasperPrint = JasperFillManager.fillReport(report, filled, dataSources); return jasperPrint; } diff --git a/src/main/java/com/ffii/core/utils/ReportMessages.kt b/src/main/java/com/ffii/core/utils/ReportMessages.kt new file mode 100644 index 00000000..71b7a002 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/ReportMessages.kt @@ -0,0 +1,13 @@ +package com.ffii.core.utils + +import java.util.Locale +import java.util.ResourceBundle + +/** Jasper label bundle for the current request locale. Root bundle is Chinese. */ +object ReportMessages { + @JvmStatic + fun bundle(): ResourceBundle { + val locale = if (UiText.isEnglish()) Locale.ENGLISH else Locale.CHINESE + return ResourceBundle.getBundle("i18n.reports", locale) + } +} diff --git a/src/main/java/com/ffii/core/utils/UiText.kt b/src/main/java/com/ffii/core/utils/UiText.kt new file mode 100644 index 00000000..57f23ee8 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/UiText.kt @@ -0,0 +1,17 @@ +package com.ffii.core.utils + +import org.springframework.context.i18n.LocaleContextHolder + +/** + * User-facing copy for the request locale. + * Chinese is the default. English is used only when the request language is English. + * Do not use this for values stored in the database or compared with stored data. + */ +object UiText { + @JvmStatic + fun isEnglish(): Boolean = + LocaleContextHolder.getLocale().language.equals("en", ignoreCase = true) + + @JvmStatic + fun t(zh: String, en: String): String = if (isEnglish()) en else zh +} diff --git a/src/main/java/com/ffii/fpsms/config/DefaultChineseLocale.java b/src/main/java/com/ffii/fpsms/config/DefaultChineseLocale.java new file mode 100644 index 00000000..5dc84c2b --- /dev/null +++ b/src/main/java/com/ffii/fpsms/config/DefaultChineseLocale.java @@ -0,0 +1,24 @@ +package com.ffii.fpsms.config; + +import java.util.Locale; + +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; + +/** + * The MVC config already creates {@code localeResolver}. A second bean with that + * name is rejected. This sets the existing resolver's default to Chinese when + * the request has no Accept-Language header. + */ +@Component +public class DefaultChineseLocale implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof AcceptHeaderLocaleResolver resolver && "localeResolver".equals(beanName)) { + resolver.setDefaultLocale(Locale.CHINESE); + } + return bean; + } +} diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/OnPackTemplateFileService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/OnPackTemplateFileService.kt index a6df5b15..546caf8f 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/OnPackTemplateFileService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/OnPackTemplateFileService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.jobOrder.service +import com.ffii.core.utils.UiText + import com.ffii.core.support.JdbcDao import com.ffii.fpsms.modules.jobOrder.entity.OnPackTemplateFile import com.ffii.fpsms.modules.jobOrder.entity.OnPackTemplateFileRepository @@ -67,15 +69,15 @@ open class OnPackTemplateFileService( open fun upload(machineRaw: String, itemCodeRaw: String, files: List): OnPackTemplateUploadResponse { val machine = normalizeMachine(machineRaw) val itemCode = normalizeItemCode(itemCodeRaw) - require(files.isNotEmpty()) { "請選擇要上傳的檔案(.image / .bmp / .job)" } + require(files.isNotEmpty()) { UiText.t("請選擇要上傳的檔案(.image / .bmp / .job)", "Choose a file to upload (.image / .bmp / .job)") } val saved = mutableListOf() files.forEach { part -> if (part.isEmpty) return@forEach val original = part.originalFilename ?: part.name val storedName = storedFileName(machine, itemCode, original) val bytes = part.bytes - require(bytes.isNotEmpty()) { "空檔案:$original" } - require(bytes.size <= MAX_BYTES) { "檔案過大(上限 ${MAX_BYTES / (1024 * 1024)}MB):$original" } + require(bytes.isNotEmpty()) { UiText.t("空檔案:", "Empty file: ") + original } + require(bytes.size <= MAX_BYTES) { UiText.t("檔案過大(上限 ", "File is too large (limit ") + (MAX_BYTES / (1024 * 1024)) + "MB):" + original } val row = try { repository.findByMachineAndItemCodeIgnoreCaseAndFileNameIgnoreCase( machine, @@ -88,7 +90,7 @@ open class OnPackTemplateFileService( } } catch (e: DataAccessException) { throw IllegalArgumentException( - "無法儲存 OnPack 模板。請重啟後端以執行 Liquibase(建立 onpack_template_file)。", + UiText.t("無法儲存 OnPack 模板。請重啟後端以執行 Liquibase(建立 onpack_template_file)。", "Could not save the OnPack template. Restart the backend so Liquibase can create onpack_template_file."), e, ) } @@ -102,13 +104,13 @@ open class OnPackTemplateFileService( repository.save(row) } catch (e: DataAccessException) { throw IllegalArgumentException( - "無法儲存 OnPack 模板。請重啟後端以執行 Liquibase(建立 onpack_template_file)。", + UiText.t("無法儲存 OnPack 模板。請重啟後端以執行 Liquibase(建立 onpack_template_file)。", "Could not save the OnPack template. Restart the backend so Liquibase can create onpack_template_file."), e, ) } saved.add(storedName) } - require(saved.isNotEmpty()) { "沒有可儲存的檔案" } + require(saved.isNotEmpty()) { UiText.t("沒有可儲存的檔案", "There is no file to save") } try { ensureOnPackQrRow(itemCode, machine, saved) } catch (e: DataAccessException) { @@ -120,10 +122,10 @@ open class OnPackTemplateFileService( @Transactional open fun softDelete(id: Long) { val row = try { - repository.findById(id).orElseThrow { IllegalArgumentException("找不到該模板檔") } + repository.findById(id).orElseThrow { IllegalArgumentException(UiText.t("找不到該模板檔", "Template file not found")) } } catch (e: DataAccessException) { throw IllegalArgumentException( - "無法刪除 OnPack 模板。請重啟後端以執行 Liquibase(建立 onpack_template_file)。", + UiText.t("無法刪除 OnPack 模板。請重啟後端以執行 Liquibase(建立 onpack_template_file)。", "Could not delete the OnPack template. Restart the backend so Liquibase can create onpack_template_file."), e, ) } @@ -241,7 +243,7 @@ open class OnPackTemplateFileService( mapOf("machine" to machine, "code" to itemCode), ) } catch (e: DataAccessException) { - throw IllegalStateException("無法儲存品號。請重啟後端以執行 Liquibase(建立 onpack_expiry_item_code)。", e) + throw IllegalStateException(UiText.t("無法儲存品號。請重啟後端以執行 Liquibase(建立 onpack_expiry_item_code)。", "Could not save the item code. Restart the backend so Liquibase can create onpack_expiry_item_code."), e) } if (existing.isPresent) { val row = existing.get() @@ -285,10 +287,10 @@ open class OnPackTemplateFileService( mapOf("machine" to machine, "code" to itemCode, "printName" to stored), ) if (n == 0) { - throw IllegalArgumentException("清單中沒有 $itemCode") + throw IllegalArgumentException(UiText.t("清單中沒有 ", "Item code is not in the list: ") + itemCode) } } catch (e: DataAccessException) { - throw IllegalStateException("無法儲存列印名稱。請重啟後端以執行 Liquibase(printName 欄位)。", e) + throw IllegalStateException(UiText.t("無法儲存列印名稱。請重啟後端以執行 Liquibase(printName 欄位)。", "Could not save the print name. Restart the backend so Liquibase can add the printName column."), e) } } if (body.useMinus18 != null) { @@ -311,7 +313,7 @@ open class OnPackTemplateFileService( mapOf("machine" to machine, "code" to itemCode), ) } catch (e: DataAccessException) { - throw IllegalStateException("無法刪除品號。請重啟後端以執行 Liquibase(建立 onpack_expiry_item_code)。", e) + throw IllegalStateException(UiText.t("無法刪除品號。請重啟後端以執行 Liquibase(建立 onpack_expiry_item_code)。", "Could not delete the item code. Restart the backend so Liquibase can create onpack_expiry_item_code."), e) } } @@ -499,13 +501,13 @@ open class OnPackTemplateFileService( return when (v) { MACHINE_JUICE, "bmp", "onpack2030", "汁水機" -> MACHINE_JUICE MACHINE_LEMON, "text", "onpack2030_2", "檸檬機" -> MACHINE_LEMON - else -> throw IllegalArgumentException("machine 必須是 juice(汁水機)或 lemon(檸檬機)") + else -> throw IllegalArgumentException(UiText.t("machine 必須是 juice(汁水機)或 lemon(檸檬機)", "machine must be juice or lemon")) } } fun normalizeItemCode(raw: String): String { val code = raw.trim().uppercase() - require(ITEM_CODE.matches(code)) { "品號格式不正確:$raw" } + require(ITEM_CODE.matches(code)) { UiText.t("品號格式不正確:", "Invalid item code: ") + raw } return code } @@ -520,7 +522,7 @@ open class OnPackTemplateFileService( fun storedFileName(machine: String, itemCode: String, originalName: String): String { val safe = sanitizeFileName(originalName) - ?: throw IllegalArgumentException("不支援的檔名(只接受 .image / .bmp / .job):$originalName") + ?: throw IllegalArgumentException(UiText.t("不支援的檔名(只接受 .image / .bmp / .job):", "Unsupported file name (only .image / .bmp / .job): ") + originalName) return if (safe.endsWith(".image", ignoreCase = true)) { if (machine == MACHINE_LEMON) "${itemCode.uppercase()}.image" else "${itemCode.lowercase()}.image" 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 1184efe3..7323bee1 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 @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.jobOrder.service +import com.ffii.core.utils.UiText + import com.ffii.core.support.JdbcDao import com.ffii.fpsms.modules.common.SettingNames import com.ffii.fpsms.modules.jobOrder.entity.JobOrder @@ -348,7 +350,7 @@ class PlasticBagPrinterService( logger.error("sendLaserBag2Job unexpected error", e) LaserBag2SendResponse( success = false, - message = "送出失敗:${e.message ?: e.javaClass.simpleName}", + message = UiText.t("送出失敗:", "Send failed: ") + (e.message ?: e.javaClass.simpleName), ) } } @@ -501,24 +503,24 @@ class PlasticBagPrinterService( if (laserAckLooksInvalid(ackRaw)) { return LaserBag2TcpResult( false, - "檸檬機回覆 invalid,未接受指令。", + UiText.t("檸檬機回覆 invalid,未接受指令。", "The lemon printer replied invalid and did not accept the command."), payload, ackRaw, false, ) } val msg = if (receiveAck) { - "已送出激光機:$payload(已確認)" + UiText.t("已送出激光機:", "Sent to the laser printer: ") + payload + UiText.t("(已確認)", " (confirmed)") } else { - "已送出激光機:$payload" + UiText.t("已送出激光機:", "Sent to the laser printer: ") + payload } return LaserBag2TcpResult(true, msg, payload, ackRaw, receiveAck) } catch (e: ConnectException) { - return LaserBag2TcpResult(false, "無法連線至 $ip:$port,請確認激光機已開機且 IP 正確。", payload, null, false) + return LaserBag2TcpResult(false, UiText.t("無法連線至 ", "Could not connect to ") + ip + ":" + port + UiText.t(",請確認激光機已開機且 IP 正確。", ". Check that the printer is on and the IP is correct."), payload, null, false) } catch (e: SocketTimeoutException) { - return LaserBag2TcpResult(false, "連線逾時 ($ip:$port),請檢查網路與連接埠。", payload, null, false) + return LaserBag2TcpResult(false, UiText.t("連線逾時 (", "Connection timed out (") + ip + ":" + port + UiText.t("),請檢查網路與連接埠。", "). Check the network and port."), payload, null, false) } catch (e: Exception) { - return LaserBag2TcpResult(false, "激光機送出失敗:${e.message}", payload, null, false) + return LaserBag2TcpResult(false, UiText.t("激光機送出失敗:", "Laser printer send failed: ") + (e.message), payload, null, false) } finally { try { Thread.sleep(100) @@ -590,9 +592,9 @@ class PlasticBagPrinterService( } return raw to null } catch (e: ConnectException) { - return null to "無法連線" + return null to UiText.t("無法連線", "Could not connect") } catch (e: SocketTimeoutException) { - return null to "讀取逾時" + return null to UiText.t("讀取逾時", "Read timed out") } catch (e: Exception) { return null to (e.message ?: e.javaClass.simpleName) } finally { @@ -611,9 +613,9 @@ class PlasticBagPrinterService( } private fun markStatusToLabel(v: Int?): String? = when (v) { - 0 -> "待機" - 1 -> "打標中" - 2 -> "其他狀態" + 0 -> UiText.t("待機", "Idle") + 1 -> UiText.t("打標中", "Marking") + 2 -> UiText.t("其他狀態", "Other status") else -> null } @@ -770,7 +772,7 @@ class PlasticBagPrinterService( .mapNotNull { (it["jobOrderId"] as? Number)?.toLong() } .toSet() val packagingJobOrders = normalizedJobOrders.filter { it.jobOrderId in allowedJobOrderIds } - require(packagingJobOrders.isNotEmpty()) { "No 包裝 process job orders found for export" } + require(packagingJobOrders.isNotEmpty()) { UiText.t("No 包裝 process job orders found for export", "No packing-process job orders found for export") } val expiryPrintNames = if (includeExpiry) { onPackTemplateFileService.expiryPrintNames(OnPackTemplateFileService.MACHINE_JUICE) @@ -820,7 +822,7 @@ class PlasticBagPrinterService( require(exportItemsListed.isNotEmpty()) { if (includeExpiry) { - "當日工單沒有在汁水機到期日 ZIP 品號清單中的項目(OnPack 模板可管理此清單)" + UiText.t("當日工單沒有在汁水機到期日 ZIP 品號清單中的項目(OnPack 模板可管理此清單)", "Today's job orders have no item codes on the juice expiry ZIP list. Manage that list in OnPack templates.") } else { "No OnPack QR (bmp) rows in onpack_qr for the selected job orders, or no matching templates" } @@ -847,9 +849,9 @@ class PlasticBagPrinterService( require(exportItems.isNotEmpty()) { if (includeExpiry && skippedWithoutExpiry.isNotEmpty()) { - "當日汁水機清單品號都沒有預設保質期,無法產生到期日 ZIP。請到設定 → 物品預設保質期新增:${skippedWithoutExpiry.joinToString("、")}" + UiText.t("當日汁水機清單品號都沒有預設保質期,無法產生到期日 ZIP。請到設定 → 物品預設保質期新增:", "None of today's juice-list item codes have a default shelf life, so the expiry ZIP cannot be created. Add them under Settings, item default shelf life: ") + (skippedWithoutExpiry.joinToString("、")) } else if (includeExpiry) { - "當日工單沒有在汁水機到期日 ZIP 品號清單中的項目(OnPack 模板可管理此清單)" + UiText.t("當日工單沒有在汁水機到期日 ZIP 品號清單中的項目(OnPack 模板可管理此清單)", "Today's job orders have no item codes on the juice expiry ZIP list. Manage that list in OnPack templates.") } else { "No OnPack QR (bmp) rows in onpack_qr for the selected job orders, or no matching templates" } @@ -858,7 +860,7 @@ class PlasticBagPrinterService( val expiryMasterImage = if (includeExpiry) loadPp1181ExpiryMasterImage() else null val expiryMasterJob = if (includeExpiry) loadPp1181MasterJob() else null if (includeExpiry) { - require(expiryMasterImage != null) { "找不到 PP1181 到期日主模板(onpack2030_exp/pp1181.image)" } + require(expiryMasterImage != null) { UiText.t("找不到 PP1181 到期日主模板(onpack2030_exp/pp1181.image)", "The PP1181 expiry master template was not found (onpack2030_exp/pp1181.image)") } } val baos = ByteArrayOutputStream() @@ -1013,7 +1015,7 @@ class PlasticBagPrinterService( .mapNotNull { (it["jobOrderId"] as? Number)?.toLong() } .toSet() val packagingJobOrders = normalizedJobOrders.filter { it.jobOrderId in allowedJobOrderIds } - require(packagingJobOrders.isNotEmpty()) { "No 包裝 process job orders found for export" } + require(packagingJobOrders.isNotEmpty()) { UiText.t("No 包裝 process job orders found for export", "No packing-process job orders found for export") } val exportItemsRaw = packagingJobOrders .groupBy { it.itemCode.trim().lowercase() } @@ -1053,7 +1055,7 @@ class PlasticBagPrinterService( require(exportItems.isNotEmpty()) { if (includeExpiry && skippedWithoutExpiry.isNotEmpty()) { - "當日檸檬機品號都沒有預設保質期,無法產生到期日 ZIP。請到設定 → 物品預設保質期新增:${skippedWithoutExpiry.joinToString("、")}" + UiText.t("當日檸檬機品號都沒有預設保質期,無法產生到期日 ZIP。請到設定 → 物品預設保質期新增:", "None of today's lemon item codes have a default shelf life, so the expiry ZIP cannot be created. Add them under Settings, item default shelf life: ") + (skippedWithoutExpiry.joinToString("、")) } else { "No OnPack QR (text) rows in onpack_qr for the selected job orders, or no matching templates" } @@ -1484,14 +1486,14 @@ class PlasticBagPrinterService( return try { Socket().use { socket -> socket.connect(InetSocketAddress(ip, port), 3000) - true to "$printerName 已連線($ip:$port)" + true to printerName + UiText.t(" 已連線(", " is connected (") + ip + ":" + port + ")" } } catch (e: SocketTimeoutException) { - false to "$printerName 連線逾時($ip:$port)" + false to printerName + UiText.t(" 連線逾時(", " timed out (") + ip + ":" + port + ")" } catch (e: ConnectException) { - false to "$printerName 無法連線($ip:$port)" + false to printerName + UiText.t(" 無法連線(", " could not connect (") + ip + ":" + port + ")" } catch (e: Exception) { - false to "$printerName 連線失敗($ip:$port):${e.message}" + false to printerName + UiText.t(" 連線失敗(", " connection failed (") + ip + ":" + port + "):" + (e.message) } } @@ -1719,7 +1721,7 @@ class PlasticBagPrinterService( var reader: BufferedReader? = null try { - println("Han's Laser TCP: 正在連接 $ip:$port ...") + println(UiText.t("Han's Laser TCP: 正在連接 ", "Han's Laser TCP: connecting to ") + ip + ":" + port + " ...") socket = Socket(ip, port).apply { soTimeout = 8000 // 8秒超時,避免卡死 } @@ -1734,7 +1736,7 @@ class PlasticBagPrinterService( Thread.sleep(300) // give device time to process val resp = reader.readLine()?.trim() ?: "" - println("命令: $cmd → 回應: $resp") + println(UiText.t("命令: ", "Command: ") + cmd + UiText.t(" → 回應: ", " → response: ") + resp) return resp.startsWith("OK") } diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PsScheduleRuleService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PsScheduleRuleService.kt index 7336b7fb..7f81c455 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PsScheduleRuleService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PsScheduleRuleService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.jobOrder.service +import com.ffii.core.utils.UiText + import com.ffii.core.support.JdbcDao import org.springframework.stereotype.Service import java.time.DayOfWeek @@ -132,13 +134,13 @@ open class PsScheduleRuleService( } fun savePlantRules(request: SavePsPlantRulesRequest) { - requireDays(request.redLineDays, request.safetyStockDays, "一般日") - requireDays(request.prepareMoreRedLineDays, request.prepareMoreSafetyStockDays, "多備日") - val coffee = requireProduceDays(request.coffeeDays, "類別 A") - val tea = requireProduceDays(request.teaDays, "類別 B") - val lemon = requireProduceDays(request.lemonDays, "類別 C") - val prepareMore = parseDaysStrict(request.prepareMoreDays, "多備星期") - val noProduction = parseDaysStrict(request.noProductionDays, "不生產星期") + requireDays(request.redLineDays, request.safetyStockDays, UiText.t("一般日", "Normal days")) + requireDays(request.prepareMoreRedLineDays, request.prepareMoreSafetyStockDays, UiText.t("多備日", "Extra-prep days")) + val coffee = requireProduceDays(request.coffeeDays, UiText.t("類別 A", "Category A")) + val tea = requireProduceDays(request.teaDays, UiText.t("類別 B", "Category B")) + val lemon = requireProduceDays(request.lemonDays, UiText.t("類別 C", "Category C")) + val prepareMore = parseDaysStrict(request.prepareMoreDays, UiText.t("多備星期", "Extra-prep weekdays")) + val noProduction = parseDaysStrict(request.noProductionDays, UiText.t("不生產星期", "Non-production weekdays")) upsert(RED_LINE, formatNumber(request.redLineDays), "float") upsert(SAFETY, formatNumber(request.safetyStockDays), "float") @@ -163,8 +165,8 @@ open class PsScheduleRuleService( if (red != null && safety != null) { requireDays(red, safety, itemCode) } else { - red?.let { requirePositive(it, "$itemCode 觸發日") } - safety?.let { requirePositive(it, "$itemCode 補貨日") } + red?.let { requirePositive(it, itemCode + UiText.t(" 觸發日", " trigger days")) } + safety?.let { requirePositive(it, itemCode + UiText.t(" 補貨日", " replenishment days")) } } } val produceDays = if (request.useCustomProduceDays) { @@ -257,29 +259,29 @@ open class PsScheduleRuleService( } private fun requireDays(redLine: Double, safety: Double, label: String) { - requirePositive(redLine, "$label 觸發日") - requirePositive(safety, "$label 補貨日") + requirePositive(redLine, label + UiText.t(" 觸發日", " trigger days")) + requirePositive(safety, label + UiText.t(" 補貨日", " replenishment days")) if (redLine > safety) { - throw IllegalArgumentException("$label:觸發日不可大於補貨日") + throw IllegalArgumentException(label + UiText.t(":觸發日不可大於補貨日", ": trigger days cannot be greater than replenishment days")) } } private fun requirePositive(value: Double, label: String) { if (!value.isFinite() || value <= 0.0) { - throw IllegalArgumentException("$label 必須大於 0") + throw IllegalArgumentException(label + UiText.t(" 必須大於 0", " must be greater than 0")) } } private fun requireProduceDays(tokens: List, label: String): Set { val days = parseDaysStrict(tokens, label) - if (days.isEmpty()) throw IllegalArgumentException("$label 至少要選一天可生產") + if (days.isEmpty()) throw IllegalArgumentException(label + UiText.t(" 至少要選一天可生產", " must include at least one production day")) return days } private fun parseDaysStrict(tokens: List, label: String): Set { val unknown = tokens.map { it.trim().uppercase() }.filter { it.isNotEmpty() && it !in DAY_TOKENS } if (unknown.isNotEmpty()) { - throw IllegalArgumentException("$label 有無法辨識的星期: ${unknown.joinToString(",")}") + throw IllegalArgumentException(label + UiText.t(" 有無法辨識的星期: ", " has an unknown weekday: ") + (unknown.joinToString(","))) } return parseDays(tokens.joinToString(",")) } diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt index e8c51e1b..2e7f744f 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.jobOrder.web +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.jobOrder.service.LaserBag2AutoSendService import com.ffii.fpsms.modules.jobOrder.service.OnPackZipResult import com.ffii.fpsms.modules.jobOrder.service.PlasticBagPrinterService @@ -72,7 +74,7 @@ class PlasticBagPrinterController( logger.error("print-laser-bag2 failed", e) LaserBag2SendResponse( success = false, - message = "送出失敗:${e.message ?: e.javaClass.simpleName}", + message = UiText.t("送出失敗:", "Send failed: ") + (e.message ?: e.javaClass.simpleName), ) } return if (resp.success) { @@ -368,18 +370,18 @@ class PlasticBagPrinterController( ip = request.printerIp, port = request.printerPort.toInt(), textChannel3 = request.textChannel3 - ?: throw IllegalArgumentException("缺少 textChannel3"), + ?: throw IllegalArgumentException(UiText.t("缺少 textChannel3", "textChannel3 is missing")), textChannel4 = request.textChannel4 - ?: throw IllegalArgumentException("缺少 textChannel4"), + ?: throw IllegalArgumentException(UiText.t("缺少 textChannel4", "textChannel4 is missing")), text3ObjectName = request.text3ObjectName ?: "Text3", text4ObjectName = request.text4ObjectName ?: "Text4" ) if (success) { - ResponseEntity.ok("Han's Laser TCP: 更新文字並觸發打標成功") + ResponseEntity.ok(UiText.t("Han's Laser TCP: 更新文字並觸發打標成功", "Han's Laser TCP: text updated and marking started")) } else { ResponseEntity.status(500) - .body("Han's Laser TCP: 部分或全部命令失敗,請檢查連接、物件名稱或 EZCAD TCP Server") + .body(UiText.t("Han's Laser TCP: 部分或全部命令失敗,請檢查連接、物件名稱或 EZCAD TCP Server", "Han's Laser TCP: one or more commands failed. Check the connection, object name, or EZCAD TCP server.")) } } catch (e: Exception) { ResponseEntity.status(500).body("Laser TCP Error: ${e.message}") diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ItemDefaultShelfLifeService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ItemDefaultShelfLifeService.kt index 59214232..3a9ed293 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ItemDefaultShelfLifeService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ItemDefaultShelfLifeService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.master.service +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.master.entity.ItemDefaultShelfLife import com.ffii.fpsms.modules.master.entity.ItemDefaultShelfLifeRepository import com.ffii.fpsms.modules.master.entity.ItemsRepository @@ -71,7 +73,7 @@ open class ItemDefaultShelfLifeService( val row = repository.findByDeletedFalseAndItemCodeIgnoreCase(code) ?: throw ResponseStatusException( HttpStatus.BAD_REQUEST, - "找不到 $code 的預設保質期。請先到設定 → 物品預設保質期新增。", + UiText.t("找不到 ", "No default shelf life for ") + code + UiText.t(" 的預設保質期。請先到設定 → 物品預設保質期新增。", ". Add it under Settings, item default shelf life."), ) row.useMinus18 = useMinus18 return toRow(repository.save(row)) 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 bbb9cc92..d0f1bb48 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 @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.master.service +import com.ffii.core.utils.UiText + import com.ffii.core.response.RecordsRes import com.ffii.core.support.AbstractBaseEntityService import com.ffii.core.support.JdbcDao @@ -483,9 +485,9 @@ open class ProductionScheduleService( return MessageResponse( id = request.id.toLong(), message = if (jobOrderCount == 0) - "沒有建立新工單(可能因為成品已有生產中的工單)." + UiText.t("沒有建立新工單(可能因為成品已有生產中的工單).", "No job order was created. The finished good may already have a job order in production.") else - "成功建立 $jobOrderCount 張工單!", + UiText.t("成功建立 ", "Created ") + jobOrderCount + UiText.t(" 張工單!", " job orders."), name = "", code = jobOrderCount.toString(), type = "psJobOrders", @@ -1494,8 +1496,8 @@ open class ProductionScheduleService( val sheet = workbook.createSheet(safeSheetName) val headers = listOf( - "物料編號", "物料名稱", "每日平均出貨量", "出貨前預計存貨量", "可用日", - "每批數量", "生產量(批)", "優先度" + UiText.t("物料編號", "Material code"), UiText.t("物料名稱", "Material name"), UiText.t("每日平均出貨量", "Avg daily shipments"), UiText.t("出貨前預計存貨量", "Expected stock before shipment"), UiText.t("可用日", "Days of cover"), + UiText.t("每批數量", "Qty per batch"), UiText.t("生產量(批)", "Production (batches)"), UiText.t("優先度", "Priority") ) // Header row @@ -1559,13 +1561,13 @@ open class ProductionScheduleService( } // ── MATERIAL SUMMARY SHEET ── - val matSheet = workbook.createSheet("物料用量總結") + val matSheet = workbook.createSheet(UiText.t("物料用量總結", "Material usage summary")) val matHeaders = listOf( - "物料編號", "物料名稱", "需求量", "總用量", - "單位", "數量(採購單位)", "採購單位", - "已採購數量", "庫存數", "庫存單位", - "相關成品編號", "相關成品名稱" + UiText.t("物料編號", "Material code"), UiText.t("物料名稱", "Material name"), UiText.t("需求量", "Required qty"), UiText.t("總用量", "Total usage"), + UiText.t("單位", "UOM"), UiText.t("數量(採購單位)", "Qty (purchase UOM)"), UiText.t("採購單位", "Purchase UOM"), + UiText.t("已採購數量", "Purchased qty"), UiText.t("庫存數", "On hand"), UiText.t("庫存單位", "Stock UOM"), + UiText.t("相關成品編號", "Related finished-good code"), UiText.t("相關成品名稱", "Related finished-good name") ) val matHeaderRow = matSheet.createRow(0) @@ -1651,7 +1653,7 @@ open class ProductionScheduleService( matSheet.setMargin(Sheet.BottomMargin, 0.7) // ── DAILY MATERIAL NEEDS SHEET ── - val dailySheet = workbook.createSheet("每天物料用量") + val dailySheet = workbook.createSheet(UiText.t("每天物料用量", "Daily material usage")) val dateFormatterDayMonth = DateTimeFormatter.ofPattern("d MMM", Locale.ENGLISH) val dateFormatterDayName = DateTimeFormatter.ofPattern("EEEE", Locale.ENGLISH) @@ -1666,7 +1668,7 @@ open class ProductionScheduleService( val headerRow1 = dailySheet.createRow(0) val headerRow2 = dailySheet.createRow(1) - val baseHeaders = listOf("物料編號", "物料名稱", "採購單位") + val baseHeaders = listOf(UiText.t("物料編號", "Material code"), UiText.t("物料名稱", "Material name"), UiText.t("採購單位", "Purchase UOM")) baseHeaders.forEachIndexed { i, title -> headerRow1.createCell(i).apply { setCellValue(title); cellStyle = headerStyle } diff --git a/src/main/java/com/ffii/fpsms/modules/monitoring/service/LabelPrinterMonitorService.kt b/src/main/java/com/ffii/fpsms/modules/monitoring/service/LabelPrinterMonitorService.kt index 1d0d98e6..71ab1ab4 100644 --- a/src/main/java/com/ffii/fpsms/modules/monitoring/service/LabelPrinterMonitorService.kt +++ b/src/main/java/com/ffii/fpsms/modules/monitoring/service/LabelPrinterMonitorService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.monitoring.service +import com.ffii.core.utils.UiText + import com.ffii.core.support.JdbcDao import com.ffii.fpsms.modules.master.entity.Printer import com.ffii.fpsms.modules.master.entity.PrinterRepository @@ -395,7 +397,7 @@ open class LabelPrinterMonitorService( private fun probeTcp(ip: String, port: Int): ProbeResult { if (ip.isBlank()) { - return ProbeResult(reachable = false, latencyMs = null, errorMessage = "未設定 IP") + return ProbeResult(reachable = false, latencyMs = null, errorMessage = UiText.t("未設定 IP", "IP address is not set")) } val start = System.currentTimeMillis() return try { diff --git a/src/main/java/com/ffii/fpsms/modules/monitoring/service/PrinterMonitorService.kt b/src/main/java/com/ffii/fpsms/modules/monitoring/service/PrinterMonitorService.kt index cbccd7f2..9b41ad0c 100644 --- a/src/main/java/com/ffii/fpsms/modules/monitoring/service/PrinterMonitorService.kt +++ b/src/main/java/com/ffii/fpsms/modules/monitoring/service/PrinterMonitorService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.monitoring.service +import com.ffii.core.utils.UiText + import com.ffii.core.support.JdbcDao import com.ffii.fpsms.modules.master.entity.Printer import com.ffii.fpsms.modules.master.entity.PrinterRepository @@ -208,7 +210,7 @@ open class PrinterMonitorService( status = "unconfigured", reachable = null, latencyMs = null, - errorMessage = "未設定 IP", + errorMessage = UiText.t("未設定 IP", "IP address is not set"), ) } val port = printer.port ?: defaultPort diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleApplier.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleApplier.kt index 26e67ee6..5b811abf 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleApplier.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleApplier.kt @@ -1,5 +1,6 @@ package com.ffii.fpsms.modules.pickOrder.service +import com.ffii.core.utils.UiText import com.ffii.fpsms.modules.pickOrder.entity.* import com.ffii.fpsms.modules.pickOrder.support.isOptimisticLockFailure import com.ffii.fpsms.modules.pickOrder.web.models.CreateTruckLaneSnapshotRequest @@ -75,7 +76,7 @@ open class TruckLaneScheduleApplier( truckLaneVersionService.createSnapshot( CreateTruckLaneSnapshotRequest( truckLanceCode = null, - note = "排程 #${schedule.id} 套用前快照 ${schedule.executeAt}", + note = UiText.t("排程 #", "Schedule #") + (schedule.id) + UiText.t(" 套用前快照 ", " pre-apply snapshot ") + (schedule.executeAt), createdBy = schedule.createdBy, ), ) @@ -99,11 +100,11 @@ open class TruckLaneScheduleApplier( truckLaneVersionService.createSnapshot( CreateTruckLaneSnapshotRequest( truckLanceCode = null, - note = "排程 #${schedule.id} 套用 (${ + note = UiText.t("排程 #", "Schedule #") + (schedule.id) + UiText.t(" 套用 (", " applied (") + ( lines.count { it.lineStatus == TruckLaneScheduleLineStatus.APPLIED } - } 列) ${schedule.executeAt}", + ) + UiText.t(" 列) ", " rows) ") + (schedule.executeAt), createdBy = schedule.createdBy, ), ) @@ -262,7 +263,7 @@ open class TruckLaneScheduleApplier( val restoreSuffix = if (preApplySnapshotId != null) { - ";已還原 snapshot #$preApplySnapshotId${restoreNote?.let { " ($it)" } ?: ""}" + UiText.t(";已還原 snapshot #", "; restored snapshot #") + preApplySnapshotId + (restoreNote?.let { " ($it)" } ?: "") } else { "" } @@ -310,7 +311,7 @@ open class TruckLaneScheduleApplier( private fun applyEnsureLane(line: TruckLaneScheduleLine): String? { val toKey = truckService.toBucketKey(line) if (truckService.trucksInLaneBucket(toKey).isNotEmpty()) { - return "車線已存在,跳過建立" + return UiText.t("車線已存在,跳過建立", "Truck lane already exists. Creation was skipped.") } ensureTargetLaneIfEmpty(line, fallbackDeparture = null) return null @@ -330,7 +331,7 @@ open class TruckLaneScheduleApplier( line.departureTime ?: fallbackDeparture ?: throw IllegalArgumentException( - "目標車線 ${toKey.truckLanceCode} 不存在或尚無任何列,且排程未提供 departureTime", + UiText.t("目標車線 ", "Target truck lane ") + (toKey.truckLanceCode) + UiText.t(" 不存在或尚無任何列,且排程未提供 departureTime", " does not exist or has no rows, and the schedule has no departure time"), ) truckService.createTruckWithoutShop( CreateTruckWithoutShopRequest( @@ -343,15 +344,15 @@ open class TruckLaneScheduleApplier( logisticId = line.logisticId, ), ) - return "已自動建立空白車線 ${toKey.truckLanceCode}" + return UiText.t("已自動建立空白車線 ", "Created a blank truck lane ") + (toKey.truckLanceCode) } private fun applyCreate( line: TruckLaneScheduleLine, createdTruckRowIds: MutableList, ): String? { - val shopId = line.shopId ?: throw IllegalArgumentException("缺少 shopId") - val departure = line.departureTime ?: throw IllegalArgumentException("缺少 departureTime") + val shopId = line.shopId ?: throw IllegalArgumentException(UiText.t("缺少 shopId", "shopId is required")) + val departure = line.departureTime ?: throw IllegalArgumentException(UiText.t("缺少 departureTime", "A departure time is required")) val toKey = truckService.toBucketKey(line) val seq = line.toLoadingSequence ?: 0 val saved = @@ -370,22 +371,22 @@ open class TruckLaneScheduleApplier( logisticId = line.logisticId, ), ) - val rowId = saved.id ?: throw IllegalStateException("CREATE 未取得 truck id") + val rowId = saved.id ?: throw IllegalStateException(UiText.t("CREATE 未取得 truck id", "CREATE did not return a truck id")) line.truckRowId = rowId createdTruckRowIds.add(rowId) return null } private fun applyMove(line: TruckLaneScheduleLine): String? { - val truckRowId = line.truckRowId ?: throw IllegalArgumentException("缺少 truckRowId") + val truckRowId = line.truckRowId ?: throw IllegalArgumentException(UiText.t("缺少 truckRowId", "truckRowId is required")) var truck = truckService.findTruckRowByIdIncludingDeleted(truckRowId) - ?: throw IllegalArgumentException("找不到 truck #$truckRowId") + ?: throw IllegalArgumentException(UiText.t("找不到 truck #", "Truck not found: #") + truckRowId) val notes = mutableListOf() if (truck.deleted == true) { truck = truckService.restoreTruckRowIfDeleted(truck) - notes.add("店鋪列已從刪除狀態還原") + notes.add(UiText.t("店鋪列已從刪除狀態還原", "Restored the shop row")) } val current = truckService.truckLaneBucketKeyOf(truck) @@ -396,7 +397,7 @@ open class TruckLaneScheduleApplier( current.storeId != fromStore || (current.remark ?: "") != (fromRemark ?: "") ) { - notes.add("店鋪列已不在排程建立時的來源車線,仍依 truck.id 移至目標") + notes.add(UiText.t("店鋪列已不在排程建立時的來源車線,仍依 truck.id 移至目標", "The shop row was no longer on the source lane. It was still moved by truck id.")) logger.info( "Schedule line {} truck {} moved from {} to target (was scheduled from {} {})", line.id, @@ -408,7 +409,7 @@ open class TruckLaneScheduleApplier( } val toKey = truckService.toBucketKey(line) - val seq = line.toLoadingSequence ?: throw IllegalArgumentException("缺少 toLoadingSequence") + val seq = line.toLoadingSequence ?: throw IllegalArgumentException(UiText.t("缺少 toLoadingSequence", "A loading sequence is required")) ensureTargetLaneIfEmpty(line, fallbackDeparture = truck.departureTime)?.let { notes.add(it) } truckService.applyScheduledTruckMove( truckRowId, @@ -421,7 +422,7 @@ open class TruckLaneScheduleApplier( } private fun applyDelete(line: TruckLaneScheduleLine): String? { - val truckRowId = line.truckRowId ?: throw IllegalArgumentException("缺少 truckRowId") + val truckRowId = line.truckRowId ?: throw IllegalArgumentException(UiText.t("缺少 truckRowId", "truckRowId is required")) truckService.deleteById(truckRowId) return null } diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleExcelSupport.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleExcelSupport.kt index eda5243b..c96a1db6 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleExcelSupport.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleExcelSupport.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.pickOrder.service +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.common.demo.DemoFloors import com.ffii.fpsms.modules.pickOrder.entity.Truck import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository @@ -37,16 +39,16 @@ class TruckLaneScheduleExcelSupport( ): Pair, List> { if (workbook == null || workbook.numberOfSheets == 0) { return emptyList() to - listOf(TruckLaneScheduleExcelRowError(0, "Excel 無工作表")) + listOf(TruckLaneScheduleExcelRowError(0, UiText.t("Excel 無工作表", "The workbook has no sheets"))) } val sheet = workbook.getSheetAt(0) if (sheet.physicalNumberOfRows < 2) { return emptyList() to - listOf(TruckLaneScheduleExcelRowError(0, "至少需要標題列與一筆資料")) + listOf(TruckLaneScheduleExcelRowError(0, UiText.t("至少需要標題列與一筆資料", "A header row and one data row are required"))) } val headerRow = sheet.getRow(sheet.firstRowNum) ?: return emptyList() to - listOf(TruckLaneScheduleExcelRowError(0, "缺少標題列")) + listOf(TruckLaneScheduleExcelRowError(0, UiText.t("缺少標題列", "The header row is missing"))) val colIndex = mutableMapOf() for (c in 0 until headerRow.lastCellNum) { @@ -64,7 +66,7 @@ class TruckLaneScheduleExcelSupport( return emptyList() to listOf( TruckLaneScheduleExcelRowError( 0, - "缺少必要欄位:ShopID/ShopCode 與 TargetLane", + UiText.t("缺少必要欄位:ShopID/ShopCode 與 TargetLane", "Required columns are missing: ShopID/ShopCode and TargetLane"), ), ) } @@ -93,17 +95,17 @@ class TruckLaneScheduleExcelSupport( ) if (shopRaw.isBlank()) { - errors.add(TruckLaneScheduleExcelRowError(rowIndex, "ShopID/ShopCode 不可為空")) + errors.add(TruckLaneScheduleExcelRowError(rowIndex, UiText.t("ShopID/ShopCode 不可為空", "ShopID/ShopCode cannot be empty"))) continue } if (laneRaw.isBlank()) { - errors.add(TruckLaneScheduleExcelRowError(rowIndex, "TargetLane 不可為空")) + errors.add(TruckLaneScheduleExcelRowError(rowIndex, UiText.t("TargetLane 不可為空", "TargetLane cannot be empty"))) continue } val truck = resolveTruckByShopCode(shopRaw.trim()) if (truck == null) { - errors.add(TruckLaneScheduleExcelRowError(rowIndex, "找不到店鋪:$shopRaw")) + errors.add(TruckLaneScheduleExcelRowError(rowIndex, UiText.t("找不到店鋪:", "Shop not found: ") + shopRaw)) continue } diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleService.kt index cdf885f7..e3fdddee 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckLaneScheduleService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.pickOrder.service +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.pickOrder.entity.* import com.ffii.fpsms.modules.pickOrder.web.models.* import org.springframework.transaction.annotation.Transactional @@ -25,7 +27,7 @@ open class TruckLaneScheduleService( validateExecuteAt(request.executeAt) val requestLines = resolveRequestLines(request) if (requestLines.isEmpty()) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "lines 不可為空") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("lines 不可為空", "Lines cannot be empty")) } val blocking = schedulePlanValidator.validatePlanLines(requestLines) if (blocking.isNotEmpty()) { @@ -72,7 +74,7 @@ open class TruckLaneScheduleService( note: String?, ): List { if (rows.isEmpty()) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無有效資料列") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("無有效資料列", "No valid data rows")) } val grouped = rows.groupBy { it.executeAt ?: defaultExecuteAt } return grouped.map { (executeAt, group) -> @@ -135,7 +137,7 @@ open class TruckLaneScheduleService( open fun getById(id: Long): TruckLaneScheduleResponse { val s = scheduleRepository.findByIdAndDeletedFalse(id) - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在") + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, UiText.t("排程不存在", "Schedule not found")) return toResponse(s, includeLines = true) } @@ -158,9 +160,9 @@ open class TruckLaneScheduleService( @Transactional open fun cancel(id: Long): TruckLaneScheduleResponse { val s = scheduleRepository.findByIdAndDeletedFalse(id) - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在") + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, UiText.t("排程不存在", "Schedule not found")) if (s.status != TruckLaneScheduleStatus.PENDING) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "僅 PENDING 可取消") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("僅 PENDING 可取消", "Only a pending schedule can be cancelled")) } s.status = TruckLaneScheduleStatus.CANCELLED val lineList = scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(s.id ?: 0L) @@ -172,16 +174,16 @@ open class TruckLaneScheduleService( open fun applyNow(id: Long): TruckLaneScheduleResponse { return when (scheduleApplier.applyOne(id)) { ScheduleApplyOutcome.NOT_FOUND -> - throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在") + throw ResponseStatusException(HttpStatus.NOT_FOUND, UiText.t("排程不存在", "Schedule not found")) ScheduleApplyOutcome.SKIPPED -> { val current = scheduleRepository.findByIdAndDeletedFalse(id) val msg = when (current?.status) { TruckLaneScheduleStatus.APPLYING -> - "排程正在執行中,請稍後再試" + UiText.t("排程正在執行中,請稍後再試", "The schedule is running. Try again shortly.") TruckLaneScheduleStatus.PENDING -> - "無法認領排程(可能已被其他程序處理)" - else -> "僅 PENDING 可立即執行" + UiText.t("無法認領排程(可能已被其他程序處理)", "Could not claim the schedule. Another process may have taken it.") + else -> UiText.t("僅 PENDING 可立即執行", "Only a pending schedule can be run now") } throw ResponseStatusException(HttpStatus.CONFLICT, msg) } @@ -197,15 +199,15 @@ open class TruckLaneScheduleService( executeAt: LocalDateTime?, ): TruckLaneScheduleResponse { val s = scheduleRepository.findByIdAndDeletedFalse(id) - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在") + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, UiText.t("排程不存在", "Schedule not found")) if (s.status == TruckLaneScheduleStatus.PARTIAL) { throw ResponseStatusException( HttpStatus.BAD_REQUEST, - "PARTIAL 排程不可重試,請先還原看板後重新建立排程", + UiText.t("PARTIAL 排程不可重試,請先還原看板後重新建立排程", "A partial schedule cannot be retried. Restore the board, then create a new schedule."), ) } if (s.status != TruckLaneScheduleStatus.FAILED) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "僅 FAILED 可重試失敗列") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("僅 FAILED 可重試失敗列", "Only a failed schedule can retry failed rows")) } val lines = scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(s.id ?: 0L) @@ -215,14 +217,14 @@ open class TruckLaneScheduleService( it.lineStatus == TruckLaneScheduleLineStatus.PENDING } if (retryLines.isEmpty()) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無可重試的失敗列") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("無可重試的失敗列", "There are no failed rows to retry")) } val whenAt = executeAt ?: LocalDateTime.now().plusMinutes(1) validateExecuteAt(whenAt) val requestLines = retryLines.mapNotNull { line -> lineToRequest(line) } if (requestLines.isEmpty()) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無可重試的失敗列") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("無可重試的失敗列", "There are no failed rows to retry")) } val created = createManual( CreateTruckLaneScheduleRequest( @@ -234,7 +236,7 @@ open class TruckLaneScheduleService( // 重試排程已接手追蹤,原 FAILED 單標為 IGNORED 以免失敗徽章長亮。 s.status = TruckLaneScheduleStatus.IGNORED s.errorMessage = - ((s.errorMessage?.let { "$it;" } ?: "") + "已由重試排程 #${created.id} 取代").take(2000) + ((s.errorMessage?.let { "$it;" } ?: "") + UiText.t("已由重試排程 #", "Replaced by retry schedule #") + (created.id) + UiText.t(" 取代", "")).take(2000) scheduleRepository.save(s) return created } @@ -245,22 +247,22 @@ open class TruckLaneScheduleService( executeAt: LocalDateTime?, ): TruckLaneScheduleResponse { val s = scheduleRepository.findByIdAndDeletedFalse(id) - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在") + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, UiText.t("排程不存在", "Schedule not found")) if (s.status != TruckLaneScheduleStatus.CANCELLED) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "僅 CANCELLED 可重新排程") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("僅 CANCELLED 可重新排程", "Only a cancelled schedule can be rescheduled")) } val lines = scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(s.id ?: 0L) val reactivatable = lines.filter { it.lineStatus == TruckLaneScheduleLineStatus.SKIPPED } if (reactivatable.isEmpty()) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無可重新排程的明細") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("無可重新排程的明細", "There are no rows to reschedule")) } val whenAt = executeAt ?: LocalDateTime.now().plusMinutes(1) validateExecuteAt(whenAt) val requestLines = reactivatable.mapNotNull { line -> lineToRequest(line) } if (requestLines.isEmpty()) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無可重新排程的明細") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("無可重新排程的明細", "There are no rows to reschedule")) } val blocking = schedulePlanValidator.validatePlanLines(requestLines) if (blocking.isNotEmpty()) { @@ -285,12 +287,12 @@ open class TruckLaneScheduleService( @Transactional open fun ignore(id: Long): TruckLaneScheduleResponse { val s = scheduleRepository.findByIdAndDeletedFalse(id) - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "排程不存在") + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, UiText.t("排程不存在", "Schedule not found")) if (s.status == TruckLaneScheduleStatus.IGNORED) { return toResponse(s, includeLines = true) } if (s.status == TruckLaneScheduleStatus.CANCELLED) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "已取消的排程無法忽略") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("已取消的排程無法忽略", "A cancelled schedule cannot be ignored")) } val lines = scheduleLineRepository.findAllBySchedule_IdAndDeletedFalseOrderByIdAsc(s.id ?: 0L) @@ -301,7 +303,7 @@ open class TruckLaneScheduleService( s.status == TruckLaneScheduleStatus.PARTIAL || failedLineCount > 0 if (!canIgnore) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "僅含失敗明細的排程可忽略") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("僅含失敗明細的排程可忽略", "Only a schedule with failed rows can be ignored")) } s.status = TruckLaneScheduleStatus.IGNORED return toResponse(scheduleRepository.save(s), includeLines = true) @@ -334,7 +336,7 @@ open class TruckLaneScheduleService( for (schedule in stale) { schedule.status = TruckLaneScheduleStatus.FAILED schedule.errorMessage = - "排程執行逾時(>${staleMinutes} 分鐘仍為 APPLYING),請依 runbook 手動還原".take(2000) + UiText.t("排程執行逾時(>", "Schedule timed out (still applying after ") + (staleMinutes) + UiText.t(" 分鐘仍為 APPLYING),請依 runbook 手動還原", " minutes). Restore it manually using the runbook.").take(2000) scheduleRepository.save(schedule) } } @@ -381,11 +383,11 @@ open class TruckLaneScheduleService( if (bucket.isNotEmpty()) { throw ResponseStatusException( HttpStatus.BAD_REQUEST, - "車線 ${toKey.truckLanceCode} 已存在,無需 ENSURE_LANE", + UiText.t("車線 ", "Truck lane ") + (toKey.truckLanceCode) + UiText.t(" 已存在,無需 ENSURE_LANE", " already exists. ENSURE_LANE is not needed."), ) } val departure = req.departureTime - ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "ENSURE_LANE 缺少 departureTime") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("ENSURE_LANE 缺少 departureTime", "ENSURE_LANE is missing a departure time")) TruckLaneScheduleLine().apply { lineAction = TruckLaneScheduleLineAction.ENSURE_LANE toTruckLanceCode = toKey.truckLanceCode @@ -400,14 +402,14 @@ open class TruckLaneScheduleService( TruckLaneScheduleLineAction.CREATE -> { val shopId = req.shopId - ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "CREATE 缺少 shopId") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("CREATE 缺少 shopId", "CREATE is missing shopId")) val departure = req.departureTime - ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "CREATE 缺少 departureTime") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("CREATE 缺少 departureTime", "CREATE is missing a departure time")) val bucket = truckService.trucksInLaneBucket(toKey) if (bucket.isEmpty() && !allowEmptyTarget) { throw ResponseStatusException( HttpStatus.BAD_REQUEST, - "目標車線 ${toKey.truckLanceCode} 不存在,需先 ENSURE_LANE", + UiText.t("目標車線 ", "Target truck lane ") + (toKey.truckLanceCode) + UiText.t(" 不存在,需先 ENSURE_LANE", " does not exist. Run ENSURE_LANE first."), ) } val seq = req.toLoadingSequence ?: 0 @@ -429,12 +431,12 @@ open class TruckLaneScheduleService( TruckLaneScheduleLineAction.DELETE -> { val truckRowId = req.truckRowId - ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "DELETE 缺少 truckRowId") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("DELETE 缺少 truckRowId", "DELETE is missing truckRowId")) assertNoOpenPending(truckRowId) val truck = truckService.findTruckById(truckRowId) - ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "找不到 truck #$truckRowId") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("找不到 truck #", "Truck not found: #") + truckRowId) if (truckService.isPlaceholderTruckRow(truck)) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "無法刪除空白占位列") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("無法刪除空白占位列", "A blank placeholder row cannot be deleted")) } val fromKey = truckService.truckLaneBucketKeyOf(truck) TruckLaneScheduleLine().apply { @@ -454,17 +456,17 @@ open class TruckLaneScheduleService( TruckLaneScheduleLineAction.MOVE -> { val truckRowId = req.truckRowId - ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "MOVE 缺少 truckRowId") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("MOVE 缺少 truckRowId", "MOVE is missing truckRowId")) assertNoOpenPending(truckRowId) val truck = truckService.findTruckById(truckRowId) - ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "找不到 truck #$truckRowId") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("找不到 truck #", "Truck not found: #") + truckRowId) val fromKey = truckService.truckLaneBucketKeyOf(truck) val err = truckService.validateMoveToLane(truckRowId, toKey, allowEmptyTarget) if (err != null) { throw ResponseStatusException(HttpStatus.BAD_REQUEST, "truck #$truckRowId: $err") } val seq = req.toLoadingSequence - ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "MOVE 缺少 toLoadingSequence") + ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("MOVE 缺少 toLoadingSequence", "MOVE is missing a loading sequence")) TruckLaneScheduleLine().apply { lineAction = TruckLaneScheduleLineAction.MOVE this.truckRowId = truckRowId @@ -498,7 +500,7 @@ open class TruckLaneScheduleService( ) { throw ResponseStatusException( HttpStatus.BAD_REQUEST, - "truck #$truckRowId 已有待執行排程", + "truck #" + truckRowId + UiText.t(" 已有待執行排程", " already has a pending schedule"), ) } } @@ -536,7 +538,7 @@ open class TruckLaneScheduleService( private fun validateExecuteAt(executeAt: LocalDateTime) { val min = LocalDateTime.now().minusMinutes(2) if (executeAt.isBefore(min)) { - throw ResponseStatusException(HttpStatus.BAD_REQUEST, "執行時間不可早於目前時間") + throw ResponseStatusException(HttpStatus.BAD_REQUEST, UiText.t("執行時間不可早於目前時間", "The run time cannot be earlier than now")) } } diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt index 6d208178..e3bbe423 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.pickOrder.service +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.common.demo.DemoFloors import com.ffii.core.support.AbstractBaseEntityService import com.ffii.core.support.JdbcDao @@ -546,12 +548,12 @@ open class TruckService( allowEmptyTarget: Boolean, ): String? { val truck = truckRepository.findById(truckRowId).orElse(null) - ?: return "找不到 truck 列 id=$truckRowId" - if (truck.deleted == true) return "truck 列已刪除" - if (isLanePlaceholderTruck(truck)) return "無法排程移動空白占位列" + ?: return UiText.t("找不到 truck 列 id=", "Truck row not found, id=") + truckRowId + if (truck.deleted == true) return UiText.t("truck 列已刪除", "The truck row is deleted") + if (isLanePlaceholderTruck(truck)) return UiText.t("無法排程移動空白占位列", "A blank placeholder row cannot be scheduled for a move") val toCode = to.truckLanceCode.trim() - if (toCode.isEmpty()) return "目標車線編號不可為空" + if (toCode.isEmpty()) return UiText.t("目標車線編號不可為空", "The target truck lane code cannot be empty") val fromKey = truckLaneBucketKeyOf(truck) val sameLane = @@ -563,11 +565,11 @@ open class TruckService( val targetBucket = trucksInLaneBucket(to) if (targetBucket.isEmpty()) { if (allowEmptyTarget) return null - return "目標車線不存在或尚無任何列:$toCode" + return UiText.t("目標車線不存在或尚無任何列:", "The target truck lane does not exist or has no rows: ") + toCode } if (laneHasShopConflict(truck, targetBucket, excludeTruckRowId = truckRowId)) { - return "目標車線已有相同店鋪" + return UiText.t("目標車線已有相同店鋪", "The target truck lane already has this shop") } return null } @@ -658,7 +660,7 @@ open class TruckService( ): List { assertNotScheduleLocked(truckRowId) var truck = findTruckRowByIdIncludingDeleted(truckRowId) - ?: throw IllegalArgumentException("找不到 truck #$truckRowId") + ?: throw IllegalArgumentException(UiText.t("找不到 truck #", "Truck not found: #") + truckRowId) truck = restoreTruckRowIfDeleted(truck) val err = validateMoveToLane(truckRowId, to) @@ -994,12 +996,12 @@ open class TruckService( rr.createCell(RouteLaneExcelSupport.COL_META_C).setCellValue(remark ?: "") rr = sheet.createRow(RouteLaneExcelSupport.ROW_STORE) - rr.createCell(RouteLaneExcelSupport.COL_META_A).setCellValue("樓層") + rr.createCell(RouteLaneExcelSupport.COL_META_A).setCellValue(UiText.t("樓層", "Floor")) rr.createCell(RouteLaneExcelSupport.COL_META_B) .setCellValue(trucks.first().storeId ?: "") rr = sheet.createRow(RouteLaneExcelSupport.ROW_DEPARTURE_DEFAULT) - rr.createCell(RouteLaneExcelSupport.COL_META_A).setCellValue("出車時間") + rr.createCell(RouteLaneExcelSupport.COL_META_A).setCellValue(UiText.t("出車時間", "Departure time")) rr.createCell(RouteLaneExcelSupport.COL_META_B).setCellValue( formatDepartureForExcel(trucks.first().departureTime), ) @@ -1046,7 +1048,7 @@ open class TruckService( fun buildRouteReportFilename(): String { val d = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) - return "車線Report_${d}.xlsx" + return UiText.t("車線Report_", "TruckLaneReport_") + (d) + ".xlsx" } private fun normalizeRemarkForLaneGroup(raw: String?): String { @@ -1103,7 +1105,7 @@ open class TruckService( val byCompanyId = trucks.groupBy { t -> val id = t.logistic?.id val nameRaw = t.logistic?.logisticName?.trim().orEmpty() - val name = if (nameRaw.isNotEmpty()) nameRaw else "未命名物流" + val name = if (nameRaw.isNotEmpty()) nameRaw else UiText.t("未命名物流", "Unnamed logistics") CompanyKey(id, name) } @@ -1124,8 +1126,8 @@ open class TruckService( RouteReportExcelSupport.writeTitle( sheet, st, - "新車綫($today)更改車線", - "製表: $preparedBy", + UiText.t("新車綫(", "New truck lanes (") + today + UiText.t(")更改車線", ") lane changes"), + UiText.t("製表: ", "Prepared by: ") + preparedBy, companies.size, ) @@ -1300,13 +1302,13 @@ open class TruckService( val summaryRows = buildSummaryRows(fromLines, toLines, diffLines, logisticNameById) val createdDate = (toV.created?.toString() ?: "").take(10) - val title = "車線報告(${createdDate.ifBlank { "—" }})更改車線" + val title = UiText.t("車線報告(", "Truck lane report (") + (createdDate.ifBlank { "—" }) + UiText.t(")更改車線", ") lane changes") val editor = (toV.modifiedBy ?: input.preparedBy).trim().ifBlank { input.preparedBy } val created = toV.created?.toString() ?: "—" val stats = summarizeSummaryRows(summaryRows) val statsText = - "新增 ${stats.added} · 移動 ${stats.moved} · 刪除 ${stats.deleted} · 欄位變更 ${stats.fieldChanged}" + UiText.t("新增 ", "Added ") + (stats.added) + UiText.t(" · 移動 ", " · Moved ") + (stats.moved) + UiText.t(" · 刪除 ", " · Deleted ") + (stats.deleted) + UiText.t(" · 欄位變更 ", " · Field changes ") + (stats.fieldChanged) TruckLaneVersionReportExcelSupport.writeSummarySheet( wb, @@ -1420,15 +1422,15 @@ open class TruckService( } private val FIELD_LABEL = mapOf( - "departureTime" to "發車時段", - "loadingSequence" to "裝載順序", - "branchName" to "分店名稱", - "districtReference" to "區域", - "shopCode" to "店鋪代碼", - "storeId" to "樓層/店別", - "remark" to "備註", - "truckLanceCode" to "車線代碼", - "logisticId" to "物流公司", + "departureTime" to UiText.t("發車時段", "Departure window"), + "loadingSequence" to UiText.t("裝載順序", "Loading sequence"), + "branchName" to UiText.t("分店名稱", "Shop name"), + "districtReference" to UiText.t("區域", "Area"), + "shopCode" to UiText.t("店鋪代碼", "Shop code"), + "storeId" to UiText.t("樓層/店別", "Floor / shop type"), + "remark" to UiText.t("備註", "Notes"), + "truckLanceCode" to UiText.t("車線代碼", "Truck lane code"), + "logisticId" to UiText.t("物流公司", "Logistics company"), ) private fun buildSummaryRows( @@ -1465,11 +1467,11 @@ open class TruckService( .toSet() val textBits = ArrayList() if (type == TruckLaneVersionReportExcelSupport.RowType.ADDED) { - textBits.add("新增到 $toLane") + textBits.add(UiText.t("新增到 ", "Added to ") + toLane) } else if (type == TruckLaneVersionReportExcelSupport.RowType.DELETED) { - textBits.add("自 $fromLane 移除") + textBits.add(UiText.t("自 ", "Removed from ") + fromLane + UiText.t(" 移除", "")) } else if (type == TruckLaneVersionReportExcelSupport.RowType.MOVED) { - textBits.add("由 $fromLane → $toLane") + textBits.add(UiText.t("由 ", "Moved ") + fromLane + " → " + toLane) } for (c in d.changes) { if (c.field == "truckLanceCode" || c.field == "remark") continue @@ -1500,7 +1502,7 @@ open class TruckService( shopCode = (b?.shopCode ?: a?.shopCode ?: "").trim(), fromLane = fromLane, toLane = toLane, - changeText = textBits.joinToString(";").ifEmpty { "欄位變更" }, + changeText = textBits.joinToString(";").ifEmpty { UiText.t("欄位變更", "Field changes") }, changedFields = changedFields, ), ) @@ -1557,11 +1559,11 @@ open class TruckService( rr.createCell(RouteLaneExcelSupport.COL_META_C).setCellValue(k.remark) rr = sheet.createRow(RouteLaneExcelSupport.ROW_STORE) - rr.createCell(RouteLaneExcelSupport.COL_META_A).setCellValue("樓層") + rr.createCell(RouteLaneExcelSupport.COL_META_A).setCellValue(UiText.t("樓層", "Floor")) rr.createCell(RouteLaneExcelSupport.COL_META_B).setCellValue(lines.first().storeId ?: "") rr = sheet.createRow(RouteLaneExcelSupport.ROW_DEPARTURE_DEFAULT) - rr.createCell(RouteLaneExcelSupport.COL_META_A).setCellValue("出車時間") + rr.createCell(RouteLaneExcelSupport.COL_META_A).setCellValue(UiText.t("出車時間", "Departure time")) val deptDefault = parseLocalTimeOrNull(lines.firstNotNullOfOrNull { it.departureTime }) rr.createCell(RouteLaneExcelSupport.COL_META_B).setCellValue(formatDepartureForExcel(deptDefault)) @@ -1672,7 +1674,7 @@ open class TruckService( val byCompany = linesWithLane.groupBy { l -> val id = l.logisticId val logi = if (id != null) logisticsById[id] else null - val name = logi?.logisticName?.trim().orEmpty().ifEmpty { "未命名物流" } + val name = logi?.logisticName?.trim().orEmpty().ifEmpty { UiText.t("未命名物流", "Unnamed logistics") } CompanyKey(id, name) }.entries.sortedBy { it.key.name } @@ -1681,8 +1683,8 @@ open class TruckService( TruckLaneVersionRouteReportExcelSupport.writeTitle( sheet, st, - "車線報告($createdDate)更改車線", - "製表: $editor", + UiText.t("車線報告(", "Truck lane report (") + createdDate + UiText.t(")更改車線", ") lane changes"), + UiText.t("製表: ", "Prepared by: ") + editor, byCompany.size.coerceAtLeast(1), ) @@ -1728,7 +1730,7 @@ open class TruckService( val shown = fields .filterNot { it == "truckLanceCode" || it == "remark" || it == "logisticId" } .map { FIELD_LABEL[it] ?: it } - if (shown.isNotEmpty()) "變更: ${shown.joinToString(",")}" else null + if (shown.isNotEmpty()) UiText.t("變更: ", "Changes: ") + (shown.joinToString(",")) else null } val logisticChange = logisticChangeByRowId[id]?.let { ch -> val fromName = ch.from?.trim()?.toLongOrNull() @@ -1743,7 +1745,7 @@ open class TruckService( .orEmpty() .ifEmpty { ch.to?.trim().orEmpty() } .ifEmpty { "—" } - if (fromName != toName) "物流公司:$fromName → $toName" else null + if (fromName != toName) UiText.t("物流公司:", "Logistics company: ") + fromName + " → " + toName else null } val linesText = ArrayList(4) linesText.add(name) diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/TruckLaneScheduleController.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/TruckLaneScheduleController.kt index 23f9bc7a..9cd93629 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/TruckLaneScheduleController.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/TruckLaneScheduleController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.pickOrder.web +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.pickOrder.entity.TruckLaneScheduleStatus import com.ffii.fpsms.modules.pickOrder.service.TruckLaneScheduleService @@ -149,7 +151,7 @@ open class TruckLaneScheduleController @Autowired constructor( name = null, code = null, type = "truckLaneSchedule", - message = "無有效列可匯入", + message = UiText.t("無有效列可匯入", "No valid rows to import"), errorPosition = null, entity = parsed, ), diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/LotExpiryAlertExcelBuilder.kt b/src/main/java/com/ffii/fpsms/modules/report/service/LotExpiryAlertExcelBuilder.kt index 71594460..4b0a0378 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/LotExpiryAlertExcelBuilder.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/LotExpiryAlertExcelBuilder.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.service +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.report.service.LotExpiryAlertReportService.SearchFilters import org.apache.poi.ss.usermodel.BorderStyle import org.apache.poi.ss.usermodel.CellStyle @@ -35,20 +37,20 @@ internal object LotExpiryAlertExcelBuilder { workbook.setCompressTempFiles(true) try { val styles = createStyles(workbook) - val reportTitle = "到期警示報告" + val reportTitle = UiText.t("到期警示報告", "Expiry alert report") val sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(reportTitle)) val headers = listOf( - "到期警示", - "樓層", - "到期日", - "貨品編號", - "貨品名稱", - "批號", - "倉庫", - "區域", - "儲位", - "剩餘數量", - "單位", + UiText.t("到期警示", "Expiry alert"), + UiText.t("樓層", "Floor"), + UiText.t("到期日", "Expiry date"), + UiText.t("貨品編號", "Item code"), + UiText.t("貨品名稱", "Item name"), + UiText.t("批號", "Lot no."), + UiText.t("倉庫", "Warehouse"), + UiText.t("區域", "Area"), + UiText.t("儲位", "Slot"), + UiText.t("剩餘數量", "Remaining qty"), + UiText.t("單位", "UOM"), ) val totalColumns = headers.size var rowIndex = 0 @@ -65,7 +67,7 @@ internal object LotExpiryAlertExcelBuilder { val dateRow = sheet.createRow(rowIndex++) dateRow.heightInPoints = 32f dateRow.createCell(0).apply { - setCellValue("下載日期:$downloadedAt") + setCellValue(UiText.t("下載日期:", "Downloaded: ") + downloadedAt) cellStyle = styles.subtitle } val dateEndCol = 3.coerceAtMost(totalColumns - 2) @@ -74,14 +76,14 @@ internal object LotExpiryAlertExcelBuilder { } val filterStartCol = dateEndCol + 1 val filterText = listOf( - "貨品編號=${displayFilter(filters.itemCode)}", - "貨品=${displayFilter(filters.itemName)}", - "批號=${displayFilter(filters.lotNo)}", - "未來天數=${filters.resolvedDaysAhead()}", - "到期分類=${displayBucket(filters.bucket, filters.resolvedDaysAhead())}", + UiText.t("貨品編號=", "Item code=") + (displayFilter(filters.itemCode)), + UiText.t("貨品=", "Item=") + (displayFilter(filters.itemName)), + UiText.t("批號=", "Lot no.=") + (displayFilter(filters.lotNo)), + UiText.t("未來天數=", "Days ahead=") + (filters.resolvedDaysAhead()), + UiText.t("到期分類=", "Expiry group=") + (displayBucket(filters.bucket, filters.resolvedDaysAhead())), ).joinToString(" ") dateRow.createCell(filterStartCol).apply { - setCellValue("搜尋條件:$filterText") + setCellValue(UiText.t("搜尋條件:", "Filters: ") + filterText) cellStyle = styles.subtitle } if (filterStartCol < totalColumns - 1) { @@ -103,7 +105,7 @@ internal object LotExpiryAlertExcelBuilder { val r = sheet.createRow(rowIndex++) r.heightInPoints = 22f r.createCell(0).apply { - setCellValue("查無資料") + setCellValue(UiText.t("查無資料", "No data")) cellStyle = styles.center } for (c in 1 until totalColumns) { @@ -210,15 +212,15 @@ internal object LotExpiryAlertExcelBuilder { private fun displayFilter(raw: String?): String { val v = raw?.trim().orEmpty() - return if (v.isEmpty()) "全部" else v + return if (v.isEmpty()) UiText.t("全部", "All") else v } private fun displayBucket(raw: String?, daysAhead: Int): String { return when (raw?.trim()?.lowercase()) { - "expired" -> "過期尚未處理" - "today" -> "今日到期" - "upcoming" -> "未來 ${daysAhead} 日到期" - else -> "全部" + "expired" -> UiText.t("過期尚未處理", "Expired, not yet handled") + "today" -> UiText.t("今日到期", "Expires today") + "upcoming" -> UiText.t("未來 ", "Within ") + (daysAhead) + UiText.t(" 日到期", " days") + else -> UiText.t("全部", "All") } } diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/LotExpiryAlertReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/LotExpiryAlertReportService.kt index b388c42d..9eef3fc6 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/LotExpiryAlertReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/LotExpiryAlertReportService.kt @@ -1,6 +1,7 @@ package com.ffii.fpsms.modules.report.service import com.ffii.core.support.JdbcDao +import com.ffii.core.utils.UiText import org.springframework.stereotype.Service @Service @@ -71,6 +72,9 @@ open class LotExpiryAlertReportService( ) } } + val expired = UiText.t("已過期", "Expired").replace("'", "''") + val today = UiText.t("今日到期", "Expires today").replace("'", "''") + val upcoming = UiText.t("${daysAhead}日內到期", "Expires within $daysAhead days").replace("'", "''") return jdbcDao.queryForList( """ SELECT @@ -86,10 +90,10 @@ open class LotExpiryAlertReportService( COALESCE(uc.udfudesc, uc.code, '') AS unitOfMeasure, CASE WHEN il.expiryDate IS NULL THEN '' - WHEN il.expiryDate < CURRENT_DATE THEN '已過期' - WHEN il.expiryDate = CURRENT_DATE THEN '今日到期' + WHEN il.expiryDate < CURRENT_DATE THEN '$expired' + WHEN il.expiryDate = CURRENT_DATE THEN '$today' WHEN il.expiryDate <= DATE_ADD(CURRENT_DATE, INTERVAL :daysAhead DAY) - THEN '${daysAhead}日內到期' + THEN '$upcoming' ELSE '' END AS expiryAlert FROM inventory_lot_line ill diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt index 0f489afc..219ef457 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt @@ -1,5 +1,6 @@ package com.ffii.fpsms.modules.report.service +import com.ffii.core.utils.ReportMessages import com.ffii.fpsms.modules.common.demo.DemoFloors import org.springframework.stereotype.Service import net.sf.jasperreports.engine.* @@ -1734,7 +1735,7 @@ return result fun createExcelResponse(templatePath: String, params: Map, dataList: List>): ByteArray { val jasperReport = compiledReport(templatePath) val dataSource = JRMapCollectionDataSource(dataList) - val jasperPrint = JasperFillManager.fillReport(jasperReport, params, dataSource) + val jasperPrint = JasperFillManager.fillReport(jasperReport, withReportBundle(params), dataSource) val out = ByteArrayOutputStream() val exporter = JRXlsxExporter() exporter.setExporterInput(SimpleExporterInput(jasperPrint)) @@ -1749,10 +1750,16 @@ return result fun createPdfResponse(templatePath: String, params: Map, dataList: List>): ByteArray { val jasperReport = compiledReport(templatePath) val dataSource = JRMapCollectionDataSource(dataList) - val jasperPrint = JasperFillManager.fillReport(jasperReport, params, dataSource) + val jasperPrint = JasperFillManager.fillReport(jasperReport, withReportBundle(params), dataSource) return JasperExportManager.exportReportToPdf(jasperPrint) } + private fun withReportBundle(params: Map): Map { + val copy = HashMap(params) + copy[JRParameter.REPORT_RESOURCE_BUNDLE] = ReportMessages.bundle() + return copy + } + fun mergePdfBytes(parts: List): ByteArray { if (parts.isEmpty()) return ByteArray(0) if (parts.size == 1) return parts[0] diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/StockLedgerPdf.kt b/src/main/java/com/ffii/fpsms/modules/report/service/StockLedgerPdf.kt index 381e5711..38ff7743 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/StockLedgerPdf.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/StockLedgerPdf.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.service +import com.ffii.core.utils.UiText + import org.apache.pdfbox.pdmodel.PDDocument import org.apache.pdfbox.pdmodel.PDPage import org.apache.pdfbox.pdmodel.PDPageContentStream @@ -30,16 +32,16 @@ object StockLedgerPdf { private data class Col(val x: Float, val w: Float, val title: String, val alignRight: Boolean = false) private val columns = listOf( - Col(11f, 78f, "出入賬日期"), - Col(91f, 58f, "類型"), - Col(151f, 120f, "批號"), - Col(273f, 72f, "到期日"), - Col(347f, 58f, "纍計期初", alignRight = true), - Col(407f, 48f, "入庫", alignRight = true), - Col(457f, 48f, "出庫", alignRight = true), - Col(507f, 62f, "纍計存量", alignRight = true), - Col(571f, 140f, "參考編號"), - Col(713f, 112f, "存貨位置"), + Col(11f, 78f, UiText.t("出入賬日期", "Ledger date")), + Col(91f, 58f, UiText.t("類型", "Type")), + Col(151f, 120f, UiText.t("批號", "Lot no.")), + Col(273f, 72f, UiText.t("到期日", "Expiry date")), + Col(347f, 58f, UiText.t("纍計期初", "Opening"), alignRight = true), + Col(407f, 48f, UiText.t("入庫", "Stock in"), alignRight = true), + Col(457f, 48f, UiText.t("出庫", "Stock out"), alignRight = true), + Col(507f, 62f, UiText.t("纍計存量", "Cumulative balance"), alignRight = true), + Col(571f, 140f, UiText.t("參考編號", "Reference no.")), + Col(713f, 112f, UiText.t("存貨位置", "Stock location")), ) fun write( @@ -64,7 +66,7 @@ object StockLedgerPdf { fun closeGroup() { if (count > 1) { canvas.ensureRow() - canvas.text(250f, canvas.y, "貨品總量:", BODY_SIZE, 95f, alignRight = true) + canvas.text(250f, canvas.y, UiText.t("貨品總量:", "Total qty: "), BODY_SIZE, 95f, alignRight = true) canvas.text(columns[5].x, canvas.y, totalIn, BODY_SIZE, columns[5].w, alignRight = true) canvas.text(columns[6].x, canvas.y, totalOut, BODY_SIZE, columns[6].w, alignRight = true) canvas.text(columns[7].x, canvas.y, totalBal, BODY_SIZE, columns[7].w, alignRight = true) @@ -168,7 +170,7 @@ object StockLedgerPdf { val total = pages.size pages.forEachIndexed { index, page -> PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true).use { cs -> - val label = "頁數 ${index + 1} / $total" + val label = UiText.t("頁數 ", "Page ") + (index + 1) + " / " + total cs.beginText() cs.setFont(font, 9f) val labelX = PAGE_W - 18f - widthOf(label, 9f) @@ -187,11 +189,11 @@ object StockLedgerPdf { stream = PDPageContentStream(doc, page) y = PAGE_H - 36f openText() - text(300f, y, "庫存明細報告", 14f) + text(300f, y, UiText.t("庫存明細報告", "Stock ledger report"), 14f) y -= 18f - text(18f, y, "報告日期:$reportDate 報告時間:$reportTime", 9f) + text(18f, y, UiText.t("報告日期:", "Report date: ") + reportDate + UiText.t(" 報告時間:", " Report time: ") + reportTime, 9f) val period = if (periodStart.isNotBlank() || periodEnd.isNotBlank()) { - "報告期間:${periodStart.ifBlank { "—" }} 至 ${periodEnd.ifBlank { "—" }}" + UiText.t("報告期間:", "Report period: ") + (periodStart.ifBlank { "—" }) + UiText.t(" 至 ", " to ") + (periodEnd.ifBlank { "—" }) } else { "" } diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/StockLedgerReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/StockLedgerReportService.kt index a33c7bde..3b3432c2 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/StockLedgerReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/StockLedgerReportService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.service +import com.ffii.core.utils.UiText + import com.ffii.core.support.JdbcDao import org.springframework.stereotype.Service import java.math.BigDecimal @@ -409,13 +411,13 @@ ORDER BY ledger.dt DESC, ledger.slId DESC date > otherDate || (date == otherDate && id > otherId) private fun ledgerTypeLabel(raw: String): String = when (raw) { - "OPEN" -> "開倉" - "NOR" -> "出入倉" - "TRF" -> "轉倉" - "ADJ" -> "調整" - "MISS" -> "遺失" - "BAD" -> "不良品" - "TKE" -> "盤點" + "OPEN" -> UiText.t("開倉", "Opening") + "NOR" -> UiText.t("出入倉", "In/out") + "TRF" -> UiText.t("轉倉", "Transfer") + "ADJ" -> UiText.t("調整", "Adjustment") + "MISS" -> UiText.t("遺失", "Shortage") + "BAD" -> UiText.t("不良品", "Damaged") + "TKE" -> UiText.t("盤點", "Stock take") else -> raw } 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 1b7a61d9..b5d8b23c 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 @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.service +import com.ffii.core.utils.UiText + import com.ffii.core.support.JdbcDao import org.springframework.stereotype.Service import java.time.LocalDate @@ -288,7 +290,7 @@ stock_one AS ( mapOf("lotIds" to chunk), ) n += rows.size - applySilSolHits(rows, hits, "入庫") + applySilSolHits(rows, hits, UiText.t("入庫", "Stock in")) } return n } @@ -310,7 +312,7 @@ stock_one AS ( mapOf("lotIds" to chunk), ) n += rows.size - applySilSolHits(rows, hits, "入庫") + applySilSolHits(rows, hits, UiText.t("入庫", "Stock in")) } return n } @@ -332,7 +334,7 @@ stock_one AS ( mapOf("lotIds" to chunk), ) n += rows.size - applySilSolHits(rows, hits, "出庫") + applySilSolHits(rows, hits, UiText.t("出庫", "Stock out")) } return n } @@ -346,16 +348,16 @@ stock_one AS ( val prev = hits[lotId] val newer = prev == null || ts > prev.ts || - (ts == prev.ts && kind == "出庫" && prev.kind != "出庫") + (ts == prev.ts && kind == UiText.t("出庫", "Stock out") && prev.kind != UiText.t("出庫", "Stock out")) if (newer) hits[lotId] = SilSolHit(ts, date, kind) } } private fun lastTrnType(inQty: Double, outQty: Double): String = when { - outQty > 0 && inQty <= 0 -> "出庫" - inQty > 0 && outQty <= 0 -> "入庫" - inQty > 0 && outQty > 0 -> if (outQty >= inQty) "出庫" else "入庫" + outQty > 0 && inQty <= 0 -> UiText.t("出庫", "Stock out") + inQty > 0 && outQty <= 0 -> UiText.t("入庫", "Stock in") + inQty > 0 && outQty > 0 -> if (outQty >= inQty) UiText.t("出庫", "Stock out") else UiText.t("入庫", "Stock in") 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 b19de85d..80d15fd3 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 @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.service +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.common.demo.DemoFloors import com.ffii.core.support.JdbcDao import org.springframework.stereotype.Service @@ -78,21 +80,36 @@ lot_root_origin AS ( ON root_po.id = root_sil.purchaseOrderId AND root_po.deleted = 0 """ - private const val LOT_ROOT_CATEGORY_SQL = """ + } + + private fun sqlQuote(label: String) = "'" + label.replace("'", "''") + "'" + + private fun lotRootCategorySql(): String { + val production = sqlQuote(UiText.t("生產", "Production")) + val opening = sqlQuote(UiText.t("期初存貨", "Opening stock")) + val adjustment = sqlQuote(UiText.t("倉存調整", "Stock adjustment")) + val purchase = sqlQuote(UiText.t("採購", "Purchase")) + val other = sqlQuote(UiText.t("其他", "Other")) + return """ CASE - 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 root_sil.purchaseOrderId IS NOT NULL THEN '採購' - ELSE '其他' + WHEN root_sil.jobOrderId IS NOT NULL THEN $production + WHEN UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'OPEN' THEN $opening + WHEN UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'ADJ' THEN $adjustment + WHEN root_sil.purchaseOrderId IS NOT NULL THEN $purchase + ELSE $other END""" + } - private const val LOT_TYPE_DISPLAY_SQL = """ + private fun lotTypeDisplaySql(): String { + val transfer = sqlQuote(UiText.t("轉倉(原:", "Transfer (from: ")) + val close = sqlQuote(UiText.t(")", ")")) + val root = lotRootCategorySql() + return """ CASE WHEN UPPER(TRIM(COALESCE(origin_sil.type, ''))) = 'TRF' OR origin_sil.stockTransferId IS NOT NULL - THEN CONCAT('轉倉(原:', $LOT_ROOT_CATEGORY_SQL, ')') - ELSE $LOT_ROOT_CATEGORY_SQL + THEN CONCAT($transfer, $root, $close) + ELSE $root END""" } @@ -299,7 +316,7 @@ data AS ( ls.approverStockTakeQty AS stkApproverQty, ls.varianceQty AS stkVarianceQty, ls.date AS stockTakeDateRaw, - $LOT_TYPE_DISPLAY_SQL AS lotTypeRaw + ${lotTypeDisplaySql()} AS lotTypeRaw FROM inventory_lot_line ill INNER JOIN inventory_lot il ON ill.inventoryLotId = il.id @@ -577,7 +594,7 @@ data AS ( ls.strDate AS stockTakeDateRaw, ls.approverTime AS approvalDateTimeRaw, ls.stockTakeRecordStatus AS stockTakeRecordStatus, - $LOT_TYPE_DISPLAY_SQL AS lotTypeRaw + ${lotTypeDisplaySql()} AS lotTypeRaw FROM latest_str ls INNER JOIN inventory_lot il ON ls.lotId = il.id @@ -876,7 +893,7 @@ data AS ( lb.strDate AS stockTakeDateRaw, lb.approverTime AS approvalDateTimeRaw, lb.stockTakeRecordStatus AS stockTakeRecordStatus, - $LOT_TYPE_DISPLAY_SQL AS lotTypeRaw + ${lotTypeDisplaySql()} AS lotTypeRaw FROM line_bounds lb INNER JOIN inventory_lot_line ill ON ill.id = lb.inventoryLotLineId INNER JOIN inventory_lot il ON ill.inventoryLotId = il.id AND il.deleted = 0 @@ -962,23 +979,24 @@ ORDER BY /** Overall 模式報表副標題(全系統輪次,舊 API 相容) */ fun getStockTakeVarianceOverallCaption(): String = - "全輪次(各批號/倉別最新已完成盤點)" + UiText.t("全輪次(各批號/倉別最新已完成盤點)", "All rounds (latest completed count per lot and location)") /** 多選輪次 Overall 模式報表副標題 */ fun getStockTakeVarianceMultiRoundCaption(stockTakeRoundIds: List): String { if (stockTakeRoundIds.isEmpty()) return getStockTakeVarianceOverallCaption() val parts = stockTakeRoundIds.map { getStockTakeRoundCaption(it) } - return "已選輪次(各批號/倉別最新已完成盤點):${parts.joinToString(";")}" + return UiText.t("已選輪次(各批號/倉別最新已完成盤點):", "Selected rounds (latest completed count per lot and location): ") + parts.joinToString(UiText.t(";", "; ")) } /** 報表表頭:盤點輪次說明(與 /report/stock-take-rounds 選項格式一致) */ fun getStockTakeRoundCaption(stockTakeRoundId: Long): String { + val roundLabel = UiText.t("盤點輪次", "Round ").replace("'", "''") val sql = """ SELECT CONCAT( CASE WHEN NULLIF(TRIM(MAX(st.stockTakeRoundName)), '') IS NULL - THEN CONCAT('盤點輪次', CAST(st.stockTakeRoundId AS CHAR)) + THEN CONCAT('$roundLabel', CAST(st.stockTakeRoundId AS CHAR)) ELSE TRIM(MAX(st.stockTakeRoundName)) END, ' — ', @@ -994,7 +1012,7 @@ ORDER BY mapOf("stockTakeRoundId" to stockTakeRoundId) ).firstOrNull() val cap = row?.get("cap") as? String - return if (!cap.isNullOrBlank()) cap else "盤點輪次$stockTakeRoundId" + return if (!cap.isNullOrBlank()) cap else UiText.t("盤點輪次", "Round ") + stockTakeRoundId } /** diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/DoInventoryUomMismatchReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/DoInventoryUomMismatchReportController.kt index a646fae2..15529446 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/DoInventoryUomMismatchReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/DoInventoryUomMismatchReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.report.service.DoInventoryUomMismatchReportService import org.apache.poi.ss.usermodel.BorderStyle import org.apache.poi.ss.usermodel.FillPatternType @@ -69,17 +71,17 @@ class DoInventoryUomMismatchReportController( } val columns = listOf( - "deliveryDate" to "預計送貨日期", - "doCode" to "送貨單號", - "supplierCode" to "供應商編號", - "supplierName" to "供應商名稱", - "itemCode" to "貨品編號", - "itemName" to "貨品名稱", - "doQty" to "送貨單數量", - "doUom" to "送貨單單位", - "mismatchLotNo" to "不符批號", - "mismatchLotQty" to "不符批數量", - "mismatchLotUom" to "不符批單位", + "deliveryDate" to UiText.t("預計送貨日期", "Planned delivery date"), + "doCode" to UiText.t("送貨單號", "Delivery note no."), + "supplierCode" to UiText.t("供應商編號", "Supplier code"), + "supplierName" to UiText.t("供應商名稱", "Supplier name"), + "itemCode" to UiText.t("貨品編號", "Item code"), + "itemName" to UiText.t("貨品名稱", "Item name"), + "doQty" to UiText.t("送貨單數量", "DN qty"), + "doUom" to UiText.t("送貨單單位", "DN UOM"), + "mismatchLotNo" to UiText.t("不符批號", "Mismatched lot"), + "mismatchLotQty" to UiText.t("不符批數量", "Mismatched qty"), + "mismatchLotUom" to UiText.t("不符批單位", "Mismatched UOM"), ) var r = 0 diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/DoUserPickAuditReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/DoUserPickAuditReportController.kt index 01ca7f04..169de7cd 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/DoUserPickAuditReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/DoUserPickAuditReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.report.service.DoUserPickAuditReportService import org.apache.poi.ss.usermodel.BorderStyle import org.apache.poi.ss.usermodel.FillPatternType @@ -52,15 +54,15 @@ class DoUserPickAuditReportController( contentType = MediaType.parseMediaType( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ) - setContentDispositionFormData("attachment", "成品出倉揀貨合規報告.xlsx") - set("filename", "成品出倉揀貨合規報告.xlsx") + setContentDispositionFormData("attachment", UiText.t("成品出倉揀貨合規報告.xlsx", "FG outbound pick compliance.xlsx")) + set("filename", UiText.t("成品出倉揀貨合規報告.xlsx", "FG outbound pick compliance.xlsx")) } return ResponseEntity(bytes, headers, HttpStatus.OK) } private fun buildExcel(rows: List>): ByteArray { val workbook = XSSFWorkbook() - val sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName("DO揀貨人員稽核")) + val sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(UiText.t("DO揀貨人員稽核", "Delivery pick audit"))) val headerStyle = workbook.createCellStyle().apply { alignment = HorizontalAlignment.CENTER verticalAlignment = VerticalAlignment.CENTER @@ -82,20 +84,20 @@ class DoUserPickAuditReportController( // key = SQL alias; label = Chinese Excel header val columns = listOf( - "datetime" to "完成時間", - "ticketNo" to "提票號碼", - "itemCode" to "貨品編號", - "itemName" to "貨品名稱", - "isEgg" to "是否雞蛋類", - "suggestLot" to "建議批號", - "actualLot" to "實際批號", - "qty" to "所需數量", - "actualQty" to "實際提料數量", - "lotBeforeQty" to "提料前庫存", - "lotAfterQty" to "提料後庫存", - "issueJustComplete" to "跳過提料(使用「已完成」)", - "user" to "提料人", - "issueTypes" to "偏離類型", + "datetime" to UiText.t("完成時間", "Completed at"), + "ticketNo" to UiText.t("提票號碼", "Ticket no."), + "itemCode" to UiText.t("貨品編號", "Item code"), + "itemName" to UiText.t("貨品名稱", "Item name"), + "isEgg" to UiText.t("是否雞蛋類", "Egg item"), + "suggestLot" to UiText.t("建議批號", "Suggested lot"), + "actualLot" to UiText.t("實際批號", "Actual lot"), + "qty" to UiText.t("所需數量", "Required qty"), + "actualQty" to UiText.t("實際提料數量", "Actual pick qty"), + "lotBeforeQty" to UiText.t("提料前庫存", "Stock before pick"), + "lotAfterQty" to UiText.t("提料後庫存", "Stock after pick"), + "issueJustComplete" to UiText.t("跳過提料(使用「已完成」)", "Pick skipped (marked completed)"), + "user" to UiText.t("提料人", "Picker"), + "issueTypes" to UiText.t("偏離類型", "Deviation type"), ) var r = 0 @@ -117,12 +119,12 @@ class DoUserPickAuditReportController( null -> cell.setCellValue("") is Number -> { if (key == "issueJustComplete") { - cell.setCellValue(if (v.toDouble() != 0.0) "是" else "否") + cell.setCellValue(if (v.toDouble() != 0.0) UiText.t("是", "Yes") else UiText.t("否", "No")) } else { cell.setCellValue(v.toDouble()) } } - is Boolean -> cell.setCellValue(if (v) "是" else "否") + is Boolean -> cell.setCellValue(if (v) UiText.t("是", "Yes") else UiText.t("否", "No")) else -> cell.setCellValue(v.toString()) } } diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/FGStockOutTraceabilityReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/FGStockOutTraceabilityReportController.kt index 5c2d666b..d7b726c6 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/FGStockOutTraceabilityReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/FGStockOutTraceabilityReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.report.service.FGStockOutTraceabilityReportService import com.ffii.fpsms.modules.report.service.ReportService import org.springframework.http.HttpHeaders @@ -122,7 +124,7 @@ class FGStockOutTraceabilityReportController( private fun createFGStockOutTraceabilityExcel(dbData: List>): ByteArray { val workbook = XSSFWorkbook() - val reportTitle = "成品出倉追蹤報告" + val reportTitle = UiText.t("成品出倉追蹤報告", "FG outbound traceability report") val safeSheetName = WorkbookUtil.createSafeSheetName(reportTitle) val sheet = workbook.createSheet(safeSheetName) @@ -172,17 +174,17 @@ class FGStockOutTraceabilityReportController( } val headers = listOf( - "貨品編號", - "貨品名稱", - "單位", - "出貨單號", - "提料單號", - "批號", - "到期日", - "出貨數量", - "提料人", - "存貨位置", - "提料備註", + UiText.t("貨品編號", "Item code"), + UiText.t("貨品名稱", "Item name"), + UiText.t("單位", "UOM"), + UiText.t("出貨單號", "Shipment no."), + UiText.t("提料單號", "Pick order no."), + UiText.t("批號", "Lot no."), + UiText.t("到期日", "Expiry date"), + UiText.t("出貨數量", "Shipped qty"), + UiText.t("提料人", "Picker"), + UiText.t("存貨位置", "Stock location"), + UiText.t("提料備註", "Pick notes"), ) val titleStyle = workbook.createCellStyle().apply { @@ -276,7 +278,7 @@ class FGStockOutTraceabilityReportController( } } summaryRow.createCell(6).apply { - setCellValue("總出倉量 :") + setCellValue(UiText.t("總出倉量 :", "Total outbound qty: ")) cellStyle = summaryLabelInExpiryColStyle } summaryRow.createCell(7).apply { diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt index 73902a82..76c48cb2 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/ItemQcFailReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import org.springframework.http.* import org.springframework.web.bind.annotation.* import com.ffii.fpsms.modules.report.service.ItemQcFailReportService @@ -49,15 +51,15 @@ class ItemQcFailReportController( val pdfParts = mutableListOf() if (passedRows.isNotEmpty()) { - pdfParts.add(createItemQcPdf(stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, passedRows, "合格")) + pdfParts.add(createItemQcPdf(stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, passedRows, UiText.t("合格", "Pass"))) } if (failedRows.isNotEmpty()) { - pdfParts.add(createItemQcPdf(stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, failedRows, "不合格")) + pdfParts.add(createItemQcPdf(stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, failedRows, UiText.t("不合格", "Fail"))) } val pdfBytes = when { pdfParts.isEmpty() -> createItemQcPdf( - stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, emptyList(), "合格" + stockCategory, itemCode, lastInDateStart, lastInDateEnd, qcType, emptyList(), UiText.t("合格", "Pass") ) pdfParts.size == 1 -> pdfParts[0] else -> reportService.mergePdfBytes(pdfParts) @@ -154,7 +156,7 @@ class ItemQcFailReportController( return when (qcType?.trim()?.uppercase()) { "IQC" -> "IQC" "EPQC" -> "EPQC" - else -> "全部" + else -> UiText.t("全部", "All") } } @@ -174,8 +176,8 @@ class ItemQcFailReportController( qcType: String?, ): ByteArray { val workbook: Workbook = XSSFWorkbook() - writeQcSheet(workbook, "合格", passedRows, lastInDateStart, lastInDateEnd, qcType) - writeQcSheet(workbook, "不合格", failedRows, lastInDateStart, lastInDateEnd, qcType) + writeQcSheet(workbook, UiText.t("合格", "Pass"), passedRows, lastInDateStart, lastInDateEnd, qcType) + writeQcSheet(workbook, UiText.t("不合格", "Fail"), failedRows, lastInDateStart, lastInDateEnd, qcType) return workbookToByteArray(workbook) } @@ -187,7 +189,7 @@ class ItemQcFailReportController( lastInDateEnd: String?, qcType: String?, ) { - val reportTitle = "庫存品質檢測報告" + val reportTitle = UiText.t("庫存品質檢測報告", "Stock QC report") val safeSheetName = WorkbookUtil.createSafeSheetName(sheetLabel) val sheet = workbook.createSheet(safeSheetName) @@ -227,7 +229,7 @@ class ItemQcFailReportController( val startTxt = lastInDateStart ?: "" val endTxt = lastInDateEnd ?: "" val qcTypeTxt = resolveQcTypeLabel(qcType) - cell.setCellValue("QC 檢測日期: ${startTxt} - ${endTxt} QC 類型: $qcTypeTxt") + cell.setCellValue(UiText.t("QC 檢測日期: ", "QC date: ") + (startTxt) + " - " + (endTxt) + UiText.t(" QC 類型: ", " QC type: ") + qcTypeTxt) } val headerStyle = workbook.createCellStyle().apply { @@ -286,20 +288,20 @@ class ItemQcFailReportController( } val headers = listOf( - "庫存類別", - "物料編號", - "物料名稱", - "單位", - "批號", - "到期日", - "QC 類型", - "QC 範本", - "檢測標準", - "批量", - "不合格數量", - "實測值", - "備註", - "訂單/工單", + UiText.t("庫存類別", "Stock category"), + UiText.t("物料編號", "Material code"), + UiText.t("物料名稱", "Material name"), + UiText.t("單位", "UOM"), + UiText.t("批號", "Lot no."), + UiText.t("到期日", "Expiry date"), + UiText.t("QC 類型", "QC type"), + UiText.t("QC 範本", "QC template"), + UiText.t("檢測標準", "QC standard"), + UiText.t("批量", "Lot qty"), + UiText.t("不合格數量", "Fail qty"), + UiText.t("實測值", "Measured value"), + UiText.t("備註", "Notes"), + UiText.t("訂單/工單", "Order / job order"), ) run { diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/MaterialStockOutTraceabilityReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/MaterialStockOutTraceabilityReportController.kt index d43dde9e..4ad02638 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/MaterialStockOutTraceabilityReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/MaterialStockOutTraceabilityReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.report.service.MaterialStockOutTraceabilityReportService import com.ffii.fpsms.modules.report.service.ReportService import org.springframework.http.HttpHeaders @@ -207,7 +209,7 @@ class MaterialStockOutTraceabilityReportController( lastOutDateEnd: String?, ): ByteArray { val workbook = XSSFWorkbook() - val reportTitle = "物料出倉追蹤報告" + val reportTitle = UiText.t("物料出倉追蹤報告", "Material outbound traceability report") val safeSheetName = WorkbookUtil.createSafeSheetName(reportTitle) val sheet = workbook.createSheet(safeSheetName) @@ -257,17 +259,17 @@ class MaterialStockOutTraceabilityReportController( } val headers = listOf( - "貨品編號", - "貨品名稱", - "單位", - "工單編號", - "提料單號", - "批號", - "到期日", - "出貨數量", - "提料人", - "存貨位置", - "提料備註", + UiText.t("貨品編號", "Item code"), + UiText.t("貨品名稱", "Item name"), + UiText.t("單位", "UOM"), + UiText.t("工單編號", "Job order no."), + UiText.t("提料單號", "Pick order no."), + UiText.t("批號", "Lot no."), + UiText.t("到期日", "Expiry date"), + UiText.t("出貨數量", "Shipped qty"), + UiText.t("提料人", "Picker"), + UiText.t("存貨位置", "Stock location"), + UiText.t("提料備註", "Pick notes"), ) // Header row @@ -328,7 +330,7 @@ class MaterialStockOutTraceabilityReportController( } } summaryRow.createCell(6).apply { - setCellValue("總出倉量 :") + setCellValue(UiText.t("總出倉量 :", "Total outbound qty: ")) cellStyle = summaryLabelInExpiryColStyle } summaryRow.createCell(7).apply { diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt index 5a1ab1b5..6ad3c3ca 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import net.sf.jasperreports.engine.* import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource import org.apache.poi.ss.usermodel.BorderStyle @@ -788,15 +790,15 @@ class ReportController( ): ByteArray { val workbook = XSSFWorkbook() val styles = createCommonWorkbookStyles(workbook) - val reportTitle = "入倉追蹤報告" + val reportTitle = UiText.t("入倉追蹤報告", "Stock-in traceability report") val sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(reportTitle)) val headers = listOf( - "貨品編號", "貨品名稱", "單位", - "送貨單編號", "批號", "到期日", - "上架數量", "入庫檢查樣品數量", "入庫檢查缺陷數量", - "入庫檢查缺陷百分比", "入庫檢查結果", "入庫檢查備註", - "存貨位置", "供應商名稱", + UiText.t("貨品編號", "Item code"), UiText.t("貨品名稱", "Item name"), UiText.t("單位", "UOM"), + UiText.t("送貨單編號", "Delivery note no."), UiText.t("批號", "Lot no."), UiText.t("到期日", "Expiry date"), + UiText.t("上架數量", "Put-away qty"), UiText.t("入庫檢查樣品數量", "Stock-in check sample qty"), UiText.t("入庫檢查缺陷數量", "Stock-in check defect qty"), + UiText.t("入庫檢查缺陷百分比", "Stock-in check defect %"), UiText.t("入庫檢查結果", "Stock-in check result"), UiText.t("入庫檢查備註", "Stock-in check notes"), + UiText.t("存貨位置", "Stock location"), UiText.t("供應商名稱", "Supplier name"), ) val totalColumns = headers.size var rowIndex = 0 @@ -815,13 +817,13 @@ class ReportController( ")" val subtitleRow = sheet.createRow(rowIndex++) subtitleRow.createCell(0).apply { - setCellValue("報告日期:$reportDateTime") + setCellValue(UiText.t("報告日期:", "Report date: ") + reportDateTime) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 0, 5)) subtitleRow.createCell(6).apply { - val cap = (lastInDateStart.trim().ifBlank { "" }) + " 至 " + (lastInDateEnd.trim().ifBlank { "" }) - setCellValue("最後入倉日期:${cap.trim()}") + val cap = (lastInDateStart.trim().ifBlank { "" }) + UiText.t(" 至 ", " to ") + (lastInDateEnd.trim().ifBlank { "" }) + setCellValue(UiText.t("最後入倉日期:", "Last stock-in date: ") + (cap.trim())) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 6, totalColumns - 1)) @@ -855,7 +857,7 @@ class ReportController( // - 上架數量 (col 6) // - 入庫檢查樣品數量 (col 7) // - 入庫檢查缺陷數量 (col 8) - r.getCell(5).apply { setCellValue("合計:"); cellStyle = styles.sumLabel } + r.getCell(5).apply { setCellValue(UiText.t("合計:", "Total: ")); cellStyle = styles.sumLabel } setNumberCellFromFormatted(r, 6, totalStockIn, styles.sumQty, styles.dash, preferInt = false) setNumberCellFromFormatted(r, 7, _totalSample, styles.sumQty, styles.dash, preferInt = false) setNumberCellFromFormatted(r, 8, _totalDefect, styles.sumQty, styles.dash, preferInt = false) @@ -944,12 +946,12 @@ class ReportController( ): ByteArray { val workbook = XSSFWorkbook() val styles = createCommonWorkbookStyles(workbook) - val reportTitle = "成品出倉報告" + val reportTitle = UiText.t("成品出倉報告", "Finished goods outbound report") val sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(reportTitle)) val headers = listOf( - "貨品編號", "貨品名稱", "單位", - "送貨日期", "DN編號", "送貨訂單編號", "門店名稱", "數量", "貨車", + UiText.t("貨品編號", "Item code"), UiText.t("貨品名稱", "Item name"), UiText.t("單位", "UOM"), + UiText.t("送貨日期", "Delivery date"), UiText.t("DN編號", "DN no."), UiText.t("送貨訂單編號", "Delivery order no."), UiText.t("門店名稱", "Shop name"), UiText.t("數量", "Qty"), UiText.t("貨車", "Truck"), ) val totalColumns = headers.size var rowIndex = 0 @@ -968,17 +970,17 @@ class ReportController( ")" val subtitleRow = sheet.createRow(rowIndex++) subtitleRow.createCell(0).apply { - setCellValue("報告日期:$reportDateTime") + setCellValue(UiText.t("報告日期:", "Report date: ") + reportDateTime) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 0, 2)) subtitleRow.createCell(3).apply { - setCellValue("成品出倉日期:${lastOutDateStart.trim()} 至 ${lastOutDateEnd.trim()}") + setCellValue(UiText.t("成品出倉日期:", "FG outbound date: ") + (lastOutDateStart.trim()) + UiText.t(" 至 ", " to ") + (lastOutDateEnd.trim())) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 3, 6)) subtitleRow.createCell(7).apply { - setCellValue("年份:$year") + setCellValue(UiText.t("年份:", "Year: ") + year) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 7, totalColumns - 1)) @@ -1008,7 +1010,7 @@ class ReportController( } } r.getCell(6).apply { - setCellValue("每單交貨總數:") + setCellValue(UiText.t("每單交貨總數:", "Delivery total: ")) cellStyle = styles.sumLabel } r.getCell(7).apply { @@ -1078,14 +1080,14 @@ class ReportController( ): ByteArray { val workbook = XSSFWorkbook() val styles = createCommonWorkbookStyles(workbook) - val reportTitle = "庫存結餘報告" + val reportTitle = UiText.t("庫存結餘報告", "Stock balance report") val sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(reportTitle)) val headers = listOf( - "貨品編號", "貨品名稱", "單位", - "期初存量", "纍計存入量", "纍計存出量", - "錯誤輸入或遺失", "盤盈虧", "不良品棄置", - "現存存貨", "單位均價", "庫存總價值", + UiText.t("貨品編號", "Item code"), UiText.t("貨品名稱", "Item name"), UiText.t("單位", "UOM"), + UiText.t("期初存量", "Opening qty"), UiText.t("纍計存入量", "Cumulative stock in"), UiText.t("纍計存出量", "Cumulative stock out"), + UiText.t("錯誤輸入或遺失", "Mis-entry or loss"), UiText.t("盤盈虧", "Variance"), UiText.t("不良品棄置", "Damaged disposal"), + UiText.t("現存存貨", "On hand"), UiText.t("單位均價", "Avg unit price"), UiText.t("庫存總價值", "Total stock value"), ) val totalColumns = headers.size var rowIndex = 0 @@ -1104,7 +1106,7 @@ class ReportController( ")" val subtitleRow = sheet.createRow(rowIndex++) subtitleRow.createCell(0).apply { - setCellValue("報告日期:$reportDateTime") + setCellValue(UiText.t("報告日期:", "Report date: ") + reportDateTime) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 0, totalColumns - 1)) diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/SemiFGProductionAnalysisReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/SemiFGProductionAnalysisReportController.kt index 4182292e..16fca08f 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/SemiFGProductionAnalysisReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/SemiFGProductionAnalysisReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import net.sf.jasperreports.engine.* import org.springframework.http.* import org.springframework.web.bind.annotation.* @@ -160,7 +162,7 @@ class SemiFGProductionAnalysisReportController( val excelBytes = createSemiFGProductionAnalysisExcel( dbData = dbData, - reportTitle = "成品/半成品生產分析報告", + reportTitle = UiText.t("成品/半成品生產分析報告", "Finished / semi-finished production report"), year = year ) @@ -249,13 +251,13 @@ class SemiFGProductionAnalysisReportController( val infoRow = sheet.createRow(rowIndex++) val reportDateCell = infoRow.createCell(0) - reportDateCell.setCellValue("報告日期:${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))}") + reportDateCell.setCellValue(UiText.t("報告日期:", "Report date: ") + (LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))) reportDateCell.cellStyle = infoStyle val reportTimeCell = infoRow.createCell(6) - reportTimeCell.setCellValue("報告時間:${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))}") + reportTimeCell.setCellValue(UiText.t("報告時間:", "Report time: ") + (LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")))) reportTimeCell.cellStyle = infoStyle val reportYearCell = infoRow.createCell(11) - reportYearCell.setCellValue("年份:${year?.takeIf { it.isNotBlank() && it != "All" } ?: "All"}") + reportYearCell.setCellValue(UiText.t("年份:", "Year: ") + (year?.takeIf { it.isNotBlank() && it != "All" } ?: "All")) reportYearCell.cellStyle = infoStyle sheet.addMergedRegion(CellRangeAddress(1, 1, 0, 5)) sheet.addMergedRegion(CellRangeAddress(1, 1, 6, 10)) @@ -264,22 +266,22 @@ class SemiFGProductionAnalysisReportController( rowIndex++ // spacer row val headers = listOf( - "貨品編號", - "貨品名稱", - "單位", - "一月", - "二月", - "三月", - "四月", - "五月", - "六月", - "七月", - "八月", - "九月", - "十月", - "十一月", - "十二月", - "上架總計" + UiText.t("貨品編號", "Item code"), + UiText.t("貨品名稱", "Item name"), + UiText.t("單位", "UOM"), + UiText.t("一月", "Jan"), + UiText.t("二月", "Feb"), + UiText.t("三月", "Mar"), + UiText.t("四月", "Apr"), + UiText.t("五月", "May"), + UiText.t("六月", "Jun"), + UiText.t("七月", "Jul"), + UiText.t("八月", "Aug"), + UiText.t("九月", "Sep"), + UiText.t("十月", "Oct"), + UiText.t("十一月", "Nov"), + UiText.t("十二月", "Dec"), + UiText.t("上架總計", "Put-away total") ) val headerRow = sheet.createRow(rowIndex++) @@ -341,15 +343,15 @@ class SemiFGProductionAnalysisReportController( "qtyJul", "qtyAug", "qtySep", "qtyOct", "qtyNov", "qtyDec", ) val monthLabels = listOf( - "一月", "二月", "三月", "四月", "五月", "六月", - "七月", "八月", "九月", "十月", "十一月", "十二月", + UiText.t("一月", "Jan"), UiText.t("二月", "Feb"), UiText.t("三月", "Mar"), UiText.t("四月", "Apr"), UiText.t("五月", "May"), UiText.t("六月", "Jun"), + UiText.t("七月", "Jul"), UiText.t("八月", "Aug"), UiText.t("九月", "Sep"), UiText.t("十月", "Oct"), UiText.t("十一月", "Nov"), UiText.t("十二月", "Dec"), ) - val dataSheetName = WorkbookUtil.createSafeSheetName("圖表數據") + val dataSheetName = WorkbookUtil.createSafeSheetName(UiText.t("圖表數據", "Chart data")) val dataSheet = workbook.createSheet(dataSheetName) val headerRow = dataSheet.createRow(0) - headerRow.createCell(0).setCellValue("貨品編號") + headerRow.createCell(0).setCellValue(UiText.t("貨品編號", "Item code")) monthLabels.forEachIndexed { i, label -> headerRow.createCell(1 + i).setCellValue(label) } @@ -364,7 +366,7 @@ class SemiFGProductionAnalysisReportController( val zColIndex = CellReference.convertColStringToIndex("Z") val zHeader = dataSheet.getRow(0) ?: dataSheet.createRow(0) - zHeader.createCell(zColIndex).setCellValue("全部") + zHeader.createCell(zColIndex).setCellValue(UiText.t("全部", "All")) dbData.forEachIndexed { idx, row -> val r = dataSheet.getRow(1 + idx) ?: dataSheet.createRow(1 + idx) r.createCell(zColIndex).setCellValue(row["itemNo"]?.toString() ?: "") @@ -373,14 +375,14 @@ class SemiFGProductionAnalysisReportController( val lastDataRow1Based = 1 + dbData.size val zListEnd1Based = 1 + dbData.size - val chartSheetName = WorkbookUtil.createSafeSheetName("生產圖表") + val chartSheetName = WorkbookUtil.createSafeSheetName(UiText.t("生產圖表", "Production chart")) val chartSheet = workbook.createSheet(chartSheetName) val labelR = chartSheet.createRow(0) - labelR.createCell(0).setCellValue("選擇貨品編號") + labelR.createCell(0).setCellValue(UiText.t("選擇貨品編號", "Select item code")) val filterCell = labelR.createCell(1) val defaultItemNo = dbData.firstOrNull()?.get("itemNo")?.toString()?.trim() - filterCell.setCellValue(defaultItemNo?.takeIf { it.isNotBlank() } ?: "全部") + filterCell.setCellValue(defaultItemNo?.takeIf { it.isNotBlank() } ?: UiText.t("全部", "All")) val dvHelper: DataValidationHelper = chartSheet.dataValidationHelper val listFormula = "'$dataSheetName'!\$Z\$1:\$Z\$$zListEnd1Based" @@ -388,9 +390,9 @@ class SemiFGProductionAnalysisReportController( val regions = CellRangeAddressList(0, 0, 1, 1) val validation = dvHelper.createValidation(constraint, regions) validation.showErrorBox = true - validation.createErrorBox("無效選擇", "請從清單中選擇貨品編號或「全部」。") + validation.createErrorBox(UiText.t("無效選擇", "Invalid choice"), UiText.t("請從清單中選擇貨品編號或「全部」。", "Choose an item code from the list, or All.")) validation.showPromptBox = true - validation.createPromptBox("篩選", "選擇貨品編號檢視該品項各月數量;選擇「全部」顯示所有品項加總。") + validation.createPromptBox(UiText.t("篩選", "Filter"), UiText.t("選擇貨品編號檢視該品項各月數量;選擇「全部」顯示所有品項加總。", "Select an item code to see monthly quantities, or All for the total of every item.")) chartSheet.addValidationData(validation) val catRowIndex = 3 @@ -406,26 +408,26 @@ class SemiFGProductionAnalysisReportController( val formula = if (lastDataRow1Based < 2) { "0" } else { - "IF(TRIM(\$B\$1)=\"全部\",SUM('$dataSheetName'!$colLetter\$2:$colLetter\$$lastDataRow1Based),IFERROR(INDEX('$dataSheetName'!$colLetter\$2:$colLetter\$$lastDataRow1Based,MATCH(TRIM(\$B\$1),'$dataSheetName'!\$A\$2:\$A\$$lastDataRow1Based,0)),0))" + "IF(TRIM(\$B\$1)=\"${UiText.t("全部", "All")}\",SUM('$dataSheetName'!$colLetter\$2:$colLetter\$$lastDataRow1Based),IFERROR(INDEX('$dataSheetName'!$colLetter\$2:$colLetter\$$lastDataRow1Based,MATCH(TRIM(\$B\$1),'$dataSheetName'!\$A\$2:\$A\$$lastDataRow1Based,0)),0))" } valRow.createCell(firstMonthCol + i).setCellFormula(formula) } - catRow.createCell(lastMonthCol + 1).setCellValue("總和") + catRow.createCell(lastMonthCol + 1).setCellValue(UiText.t("總和", "Total")) valRow.createCell(lastMonthCol + 1).setCellFormula("SUM(B5:M5)") val drawing = chartSheet.createDrawingPatriarch() val anchor = XSSFClientAnchor(0, 0, 0, 0, 1, 6, 14, 28) val chart = drawing.createChart(anchor) - chart.setTitleText("各月生產數量(依選擇之貨品編號)") + chart.setTitleText(UiText.t("各月生產數量(依選擇之貨品編號)", "Monthly production quantity (selected item)")) chart.setTitleOverlay(false) val legend = chart.getOrAddLegend() legend.position = LegendPosition.BOTTOM val bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM) - bottomAxis.setTitle("月份") + bottomAxis.setTitle(UiText.t("月份", "Month")) val leftAxis = chart.createValueAxis(AxisPosition.LEFT) - leftAxis.setTitle("生產數量") + leftAxis.setTitle(UiText.t("生產數量", "Production qty")) val catRangeAddr = CellRangeAddress(catRowIndex, catRowIndex, firstMonthCol, lastMonthCol) val valRangeAddr = CellRangeAddress(valRowIndex, valRowIndex, firstMonthCol, lastMonthCol) @@ -435,7 +437,7 @@ class SemiFGProductionAnalysisReportController( val barData = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis) as XDDFBarChartData barData.barDirection = BarDirection.COL val series = barData.addSeries(categories, values) - series.setTitle("生產數量", null) + series.setTitle(UiText.t("生產數量", "Production qty"), null) chart.plot(barData) chartSheet.setColumnWidth(0, 18 * 256) diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/StockItemConsumptionTrendReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/StockItemConsumptionTrendReportController.kt index 7572bd5d..3c9a34fd 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/StockItemConsumptionTrendReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/StockItemConsumptionTrendReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import net.sf.jasperreports.engine.* import org.springframework.http.* import org.springframework.web.bind.annotation.* @@ -155,7 +157,7 @@ class StockItemConsumptionTrendReportController( val excelBytes = createStockItemConsumptionTrendExcel( dbData = dbData, - reportTitle = "庫存材料消耗趨勢報告", + reportTitle = UiText.t("庫存材料消耗趨勢報告", "Material consumption trend report"), year = year, lastOutDateStart = lastOutDateStart, lastOutDateEnd = lastOutDateEnd @@ -261,17 +263,17 @@ class StockItemConsumptionTrendReportController( val infoRow = sheet.createRow(rowIndex++) val reportDateCell = infoRow.createCell(0) reportDateCell.setCellValue( - "報告日期:${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))}" + UiText.t("報告日期:", "Report date: ") + (LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) ) reportDateCell.cellStyle = infoStyle val reportTimeCell = infoRow.createCell(6) - reportTimeCell.setCellValue("報告時間:${LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))}") + reportTimeCell.setCellValue(UiText.t("報告時間:", "Report time: ") + (LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")))) reportTimeCell.cellStyle = infoStyle val reportYearCell = infoRow.createCell(11) reportYearCell.setCellValue( - "年份:${year?.takeIf { it.isNotBlank() && it != "All" } ?: "All"}" + UiText.t("年份:", "Year: ") + (year?.takeIf { it.isNotBlank() && it != "All" } ?: "All") ) reportYearCell.cellStyle = infoStyle @@ -285,9 +287,9 @@ class StockItemConsumptionTrendReportController( rangeRow.heightInPoints = 20f val rangeCell = rangeRow.createCell(0) val rangeText = buildString { - append("材料消耗日期:") + append(UiText.t("材料消耗日期:", "Consumption date: ")) append(lastOutDateStart ?: "") - append(" 至 ") + append(UiText.t(" 至 ", " to ")) append(lastOutDateEnd ?: "") } rangeCell.setCellValue(rangeText) @@ -296,22 +298,22 @@ class StockItemConsumptionTrendReportController( // Column headers val headers = listOf( - "貨品編號", - "貨品名稱", - "單位", - "一月", - "二月", - "三月", - "四月", - "五月", - "六月", - "七月", - "八月", - "九月", - "十月", - "十一月", - "十二月", - "總和" + UiText.t("貨品編號", "Item code"), + UiText.t("貨品名稱", "Item name"), + UiText.t("單位", "UOM"), + UiText.t("一月", "Jan"), + UiText.t("二月", "Feb"), + UiText.t("三月", "Mar"), + UiText.t("四月", "Apr"), + UiText.t("五月", "May"), + UiText.t("六月", "Jun"), + UiText.t("七月", "Jul"), + UiText.t("八月", "Aug"), + UiText.t("九月", "Sep"), + UiText.t("十月", "Oct"), + UiText.t("十一月", "Nov"), + UiText.t("十二月", "Dec"), + UiText.t("總和", "Total") ) rowIndex++ // spacer row @@ -371,15 +373,15 @@ class StockItemConsumptionTrendReportController( "qtyJul", "qtyAug", "qtySep", "qtyOct", "qtyNov", "qtyDec", ) val monthLabels = listOf( - "一月", "二月", "三月", "四月", "五月", "六月", - "七月", "八月", "九月", "十月", "十一月", "十二月", + UiText.t("一月", "Jan"), UiText.t("二月", "Feb"), UiText.t("三月", "Mar"), UiText.t("四月", "Apr"), UiText.t("五月", "May"), UiText.t("六月", "Jun"), + UiText.t("七月", "Jul"), UiText.t("八月", "Aug"), UiText.t("九月", "Sep"), UiText.t("十月", "Oct"), UiText.t("十一月", "Nov"), UiText.t("十二月", "Dec"), ) - val dataSheetName = WorkbookUtil.createSafeSheetName("圖表數據") + val dataSheetName = WorkbookUtil.createSafeSheetName(UiText.t("圖表數據", "Chart data")) val dataSheet = workbook.createSheet(dataSheetName) val headerRow = dataSheet.createRow(0) - headerRow.createCell(0).setCellValue("貨品編號") + headerRow.createCell(0).setCellValue(UiText.t("貨品編號", "Item code")) monthLabels.forEachIndexed { i, label -> headerRow.createCell(1 + i).setCellValue(label) } @@ -394,7 +396,7 @@ class StockItemConsumptionTrendReportController( val zColIndex = CellReference.convertColStringToIndex("Z") val zHeader = dataSheet.getRow(0) ?: dataSheet.createRow(0) - zHeader.createCell(zColIndex).setCellValue("全部") + zHeader.createCell(zColIndex).setCellValue(UiText.t("全部", "All")) dbData.forEachIndexed { idx, row -> val r = dataSheet.getRow(1 + idx) ?: dataSheet.createRow(1 + idx) r.createCell(zColIndex).setCellValue(row["itemNo"]?.toString() ?: "") @@ -403,14 +405,14 @@ class StockItemConsumptionTrendReportController( val lastDataRow1Based = 1 + dbData.size val zListEnd1Based = 1 + dbData.size - val chartSheetName = WorkbookUtil.createSafeSheetName("消耗圖表") + val chartSheetName = WorkbookUtil.createSafeSheetName(UiText.t("消耗圖表", "Consumption chart")) val chartSheet = workbook.createSheet(chartSheetName) val labelR = chartSheet.createRow(0) - labelR.createCell(0).setCellValue("選擇貨品編號") + labelR.createCell(0).setCellValue(UiText.t("選擇貨品編號", "Select item code")) val filterCell = labelR.createCell(1) val defaultItemNo = dbData.firstOrNull()?.get("itemNo")?.toString()?.trim() - filterCell.setCellValue(defaultItemNo?.takeIf { it.isNotBlank() } ?: "全部") + filterCell.setCellValue(defaultItemNo?.takeIf { it.isNotBlank() } ?: UiText.t("全部", "All")) val dvHelper: DataValidationHelper = chartSheet.dataValidationHelper val listFormula = "'$dataSheetName'!\$Z\$1:\$Z\$$zListEnd1Based" @@ -418,9 +420,9 @@ class StockItemConsumptionTrendReportController( val regions = CellRangeAddressList(0, 0, 1, 1) val validation = dvHelper.createValidation(constraint, regions) validation.showErrorBox = true - validation.createErrorBox("無效選擇", "請從清單中選擇貨品編號或「全部」。") + validation.createErrorBox(UiText.t("無效選擇", "Invalid choice"), UiText.t("請從清單中選擇貨品編號或「全部」。", "Choose an item code from the list, or All.")) validation.showPromptBox = true - validation.createPromptBox("篩選", "選擇貨品編號檢視該品項各月消耗數量;選擇「全部」顯示所有品項加總。") + validation.createPromptBox(UiText.t("篩選", "Filter"), UiText.t("選擇貨品編號檢視該品項各月消耗數量;選擇「全部」顯示所有品項加總。", "Select an item code to see monthly consumption, or All for the total of every item.")) chartSheet.addValidationData(validation) val catRowIndex = 3 @@ -437,27 +439,27 @@ class StockItemConsumptionTrendReportController( val formula = if (lastDataRow1Based < 2) { "0" } else { - "IF(TRIM(\$B\$1)=\"全部\",SUM('$dataSheetName'!$colLetter\$2:$colLetter\$$lastDataRow1Based),IFERROR(INDEX('$dataSheetName'!$colLetter\$2:$colLetter\$$lastDataRow1Based,MATCH(TRIM(\$B\$1),'$dataSheetName'!\$A\$2:\$A\$$lastDataRow1Based,0)),0))" + "IF(TRIM(\$B\$1)=\"${UiText.t("全部", "All")}\",SUM('$dataSheetName'!$colLetter\$2:$colLetter\$$lastDataRow1Based),IFERROR(INDEX('$dataSheetName'!$colLetter\$2:$colLetter\$$lastDataRow1Based,MATCH(TRIM(\$B\$1),'$dataSheetName'!\$A\$2:\$A\$$lastDataRow1Based,0)),0))" } valRow.createCell(firstMonthCol + i).setCellFormula(formula) } - catRow.createCell(lastMonthCol + 1).setCellValue("總和") + catRow.createCell(lastMonthCol + 1).setCellValue(UiText.t("總和", "Total")) valRow.createCell(lastMonthCol + 1).setCellFormula("SUM(B5:M5)") val drawing = chartSheet.createDrawingPatriarch() val anchor = XSSFClientAnchor(0, 0, 0, 0, 1, 6, 14, 28) val chart = drawing.createChart(anchor) - chart.setTitleText("各月消耗數量(依選擇之貨品編號)") + chart.setTitleText(UiText.t("各月消耗數量(依選擇之貨品編號)", "Monthly consumption quantity (selected item)")) chart.setTitleOverlay(false) val legend = chart.getOrAddLegend() legend.position = LegendPosition.BOTTOM val bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM) - bottomAxis.setTitle("月份") + bottomAxis.setTitle(UiText.t("月份", "Month")) val leftAxis = chart.createValueAxis(AxisPosition.LEFT) - leftAxis.setTitle("消耗數量") + leftAxis.setTitle(UiText.t("消耗數量", "Consumption qty")) val catRangeAddr = CellRangeAddress(catRowIndex, catRowIndex, firstMonthCol, lastMonthCol) val valRangeAddr = CellRangeAddress(valRowIndex, valRowIndex, firstMonthCol, lastMonthCol) @@ -467,7 +469,7 @@ class StockItemConsumptionTrendReportController( val barData = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis) as XDDFBarChartData barData.barDirection = BarDirection.COL val series = barData.addSeries(categories, values) - series.setTitle("消耗數量", null) + series.setTitle(UiText.t("消耗數量", "Consumption qty"), null) chart.plot(barData) chartSheet.setColumnWidth(0, 18 * 256) diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/StockLedgerReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/StockLedgerReportController.kt index 48a88789..e0efaa1c 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/StockLedgerReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/StockLedgerReportController.kt @@ -1,4 +1,6 @@ package com.ffii.fpsms.modules.report.web + +import com.ffii.core.utils.UiText import org.springframework.web.bind.annotation.* import org.springframework.http.* import java.time.LocalDate @@ -324,14 +326,14 @@ class StockLedgerReportController( workbook.setCompressTempFiles(true) try { val styles = createStyles(workbook) - val reportTitle = "庫存明細報告" + val reportTitle = UiText.t("庫存明細報告", "Stock ledger report") val sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(reportTitle)) val headers = listOf( - "貨品編號", "貨品名稱", "單位", - "出入賬日期", "類型", "批號", "到期日", - "纍計期初存量", "入庫", "出庫", "纍計存量", - "參考編號", "存貨位置", + UiText.t("貨品編號", "Item code"), UiText.t("貨品名稱", "Item name"), UiText.t("單位", "UOM"), + UiText.t("出入賬日期", "Ledger date"), UiText.t("類型", "Type"), UiText.t("批號", "Lot no."), UiText.t("到期日", "Expiry date"), + UiText.t("纍計期初存量", "Opening balance"), UiText.t("入庫", "Stock in"), UiText.t("出庫", "Stock out"), UiText.t("纍計存量", "Cumulative balance"), + UiText.t("參考編號", "Reference no."), UiText.t("存貨位置", "Stock location"), ) val totalColumns = headers.size var rowIndex = 0 @@ -350,12 +352,12 @@ class StockLedgerReportController( ")" val subtitleRow = sheet.createRow(rowIndex++) subtitleRow.createCell(0).apply { - setCellValue("報告日期:$reportDateTime") + setCellValue(UiText.t("報告日期:", "Report date: ") + reportDateTime) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 0, 4)) subtitleRow.createCell(5).apply { - setCellValue("報告期間:${reportPeriodStart.trim()} 至 ${reportPeriodEnd.trim()}") + setCellValue(UiText.t("報告期間:", "Report period: ") + (reportPeriodStart.trim()) + UiText.t(" 至 ", " to ") + (reportPeriodEnd.trim())) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 5, totalColumns - 1)) @@ -385,7 +387,7 @@ class StockLedgerReportController( for (c in 3 until totalColumns) { r.createCell(c).apply { setCellValue(""); cellStyle = styles.sumEmpty } } - r.getCell(7).apply { setCellValue("貨品總量:"); cellStyle = styles.sumLabel } + r.getCell(7).apply { setCellValue(UiText.t("貨品總量:", "Total qty: ")); cellStyle = styles.sumLabel } setIntCellFromFormatted(r, 8, totalIn, styles.sumQty, styles.dash) setIntCellFromFormatted(r, 9, totalOut, styles.sumQty, styles.dash) setIntCellFromFormatted(r, 10, totalBal, styles.sumQty, styles.dash) @@ -425,7 +427,7 @@ class StockLedgerReportController( setTextCell(r, 2, uom, styles.center) setTextCell(r, 3, m["trnDate"], styles.center) val typeText = m["trnRefNo"]?.toString()?.trim().orEmpty().let { t -> - if (t.equals("Expiry", ignoreCase = true)) "過期" else t + if (t.equals("Expiry", ignoreCase = true)) UiText.t("過期", "Expired") else t } setTextCell(r, 4, typeText, styles.center) setTextCell(r, 5, m["lotNo"], styles.text) diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt index 6718b7fb..ad03ef77 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.report.service.StockLotOnhandReportService import org.apache.poi.ss.usermodel.BorderStyle import org.apache.poi.ss.usermodel.CellStyle @@ -186,8 +188,8 @@ class StockLotOnhandReportController( private fun displayFilter(raw: String?): String { val v = raw?.trim().orEmpty() - if (v.isEmpty() || v.equals("All", ignoreCase = true)) return "全部" - if (v.equals("other", ignoreCase = true)) return "其他" + if (v.isEmpty() || v.equals("All", ignoreCase = true)) return UiText.t("全部", "All") + if (v.equals("other", ignoreCase = true)) return UiText.t("其他", "Other") return v } @@ -208,14 +210,14 @@ class StockLotOnhandReportController( workbook.setCompressTempFiles(true) try { val styles = createStyles(workbook) - val reportTitle = "庫存批次現況報告" + val reportTitle = UiText.t("庫存批次現況報告", "Lot on-hand report") val sheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(reportTitle)) val headers = listOf( - "貨品編號", "貨品名稱", "貨品單位結餘", "單位", - "批號", "到期日", - "樓層", "倉庫", "區域", "儲位", - "最後異動日", "最後異動(入庫/出庫)", - "批號結餘", "單位均價", "庫存總價值", + UiText.t("貨品編號", "Item code"), UiText.t("貨品名稱", "Item name"), UiText.t("貨品單位結餘", "Item balance"), UiText.t("單位", "UOM"), + UiText.t("批號", "Lot no."), UiText.t("到期日", "Expiry date"), + UiText.t("樓層", "Floor"), UiText.t("倉庫", "Warehouse"), UiText.t("區域", "Area"), UiText.t("儲位", "Slot"), + UiText.t("最後異動日", "Last movement date"), UiText.t("最後異動(入庫/出庫)", "Last movement (in/out)"), + UiText.t("批號結餘", "Lot balance"), UiText.t("單位均價", "Avg unit price"), UiText.t("庫存總價值", "Total stock value"), ) val totalColumns = headers.size var rowIndex = 0 @@ -236,25 +238,25 @@ class StockLotOnhandReportController( val dateRow = sheet.createRow(rowIndex++) dateRow.heightInPoints = 18f dateRow.createCell(0).apply { - setCellValue("報告日期(現況):$reportDateTime") + setCellValue(UiText.t("報告日期(現況):", "Report date (as of): ") + reportDateTime) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(1, 1, 0, totalColumns - 1)) val criteriaText = listOf( - "貨品編號=${displayFilter(itemCode)}", - "樓層=${displayFilter(storeId)}", - "倉庫=${displayFilter(warehouse)}", - "區域=${displayFilter(area)}", - "儲位=${displayFilter(slot)}", - "盤點區域說明=${displayFilter(stockTakeSectionDescription)}", - "批號=${displayFilter(lotNo)}", - "來源=${displayFilter(lotOrigin)}", + UiText.t("貨品編號=", "Item code=") + (displayFilter(itemCode)), + UiText.t("樓層=", "Floor=") + (displayFilter(storeId)), + UiText.t("倉庫=", "Warehouse=") + (displayFilter(warehouse)), + UiText.t("區域=", "Area=") + (displayFilter(area)), + UiText.t("儲位=", "Slot=") + (displayFilter(slot)), + UiText.t("盤點區域說明=", "Count section=") + (displayFilter(stockTakeSectionDescription)), + UiText.t("批號=", "Lot no.=") + (displayFilter(lotNo)), + UiText.t("來源=", "Origin=") + (displayFilter(lotOrigin)), ).joinToString(" ") val criteriaRow = sheet.createRow(rowIndex++) criteriaRow.heightInPoints = 32f criteriaRow.createCell(0).apply { - setCellValue("搜尋條件:$criteriaText") + setCellValue(UiText.t("搜尋條件:", "Filters: ") + criteriaText) cellStyle = styles.subtitle } sheet.addMergedRegion(CellRangeAddress(2, 2, 0, totalColumns - 1)) @@ -274,7 +276,7 @@ class StockLotOnhandReportController( val r = sheet.createRow(rowIndex++) r.heightInPoints = 22f r.createCell(0).apply { - setCellValue("查無資料") + setCellValue(UiText.t("查無資料", "No data")) cellStyle = styles.center } for (c in 1 until totalColumns) { diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt index bfaca59f..60e27034 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.report.web +import com.ffii.core.utils.UiText + import com.ffii.fpsms.modules.report.service.ReportService import com.ffii.fpsms.modules.report.service.StockTakeVarianceReportService import org.apache.poi.ss.usermodel.BorderStyle @@ -240,7 +242,7 @@ class StockTakeVarianceReportController( val s = stockTakeDateStart?.trim().orEmpty() val e = stockTakeDateEnd?.trim().orEmpty() - return if (s.isEmpty() && e.isEmpty()) "" else "$s 至 $e" + return if (s.isEmpty() && e.isEmpty()) "" else s + UiText.t(" 至 ", " to ") + e } private fun createStockTakeVarianceExcel( @@ -248,23 +250,23 @@ class StockTakeVarianceReportController( stockTakeCaption: String, ): ByteArray { val workbook = XSSFWorkbook() - val reportTitle = "庫存盤點報告" + val reportTitle = UiText.t("庫存盤點報告", "Stock take report") val safeSheetName = WorkbookUtil.createSafeSheetName(reportTitle) val sheet = workbook.createSheet(safeSheetName) val headers = listOf( - "貨品編號", - "貨品名稱", - "單位", - "批號", - "到期日", - "存貨位置", - "盤點前存量", - "盤點數", - "盤盈虧", - "盤盈虧百分比", - "類型", - "審核時間", + UiText.t("貨品編號", "Item code"), + UiText.t("貨品名稱", "Item name"), + UiText.t("單位", "UOM"), + UiText.t("批號", "Lot no."), + UiText.t("到期日", "Expiry date"), + UiText.t("存貨位置", "Stock location"), + UiText.t("盤點前存量", "Qty before count"), + UiText.t("盤點數", "Counted qty"), + UiText.t("盤盈虧", "Variance"), + UiText.t("盤盈虧百分比", "Variance %"), + UiText.t("類型", "Type"), + UiText.t("審核時間", "Approved at"), ) val totalColumns = headers.size var rowIndex = 0 @@ -400,12 +402,12 @@ class StockTakeVarianceReportController( ")" val subtitleRow = sheet.createRow(rowIndex++) subtitleRow.createCell(0).apply { - setCellValue("報告日期:$reportDateTime") + setCellValue(UiText.t("報告日期:", "Report date: ") + reportDateTime) cellStyle = subtitleStyle } sheet.addMergedRegion(CellRangeAddress(1, 1, 0, 2)) subtitleRow.createCell(3).apply { - setCellValue(if (stockTakeCaption.isBlank()) "" else "盤點日期:$stockTakeCaption") + setCellValue(if (stockTakeCaption.isBlank()) "" else UiText.t("盤點日期:", "Count date: ") + stockTakeCaption) cellStyle = subtitleStyle } sheet.addMergedRegion(CellRangeAddress(1, 1, 3, totalColumns - 1)) @@ -450,7 +452,7 @@ class StockTakeVarianceReportController( val labelCol = 5 val valueCol = 6 r.getCell(labelCol).apply { - setCellValue("貨品總量:") + setCellValue(UiText.t("貨品總量:", "Total qty: ")) cellStyle = summaryLabelThickBottomStyle } r.getCell(valueCol).apply { @@ -616,7 +618,7 @@ class StockTakeVarianceReportController( if (useLegacyOverall) { return Rep012ReportData( caption = stockTakeVarianceReportService.getStockTakeVarianceOverallCaption(), - conditionLabel = "盤點條件:", + conditionLabel = UiText.t("盤點條件:", "Count filter: "), dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2Overall( itemCode = itemCode, storeId = storeId, @@ -629,7 +631,7 @@ class StockTakeVarianceReportController( val id = roundIds.first() return Rep012ReportData( caption = stockTakeVarianceReportService.getStockTakeRoundCaption(id), - conditionLabel = "盤點輪次:", + conditionLabel = UiText.t("盤點輪次:", "Round: "), dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2( stockTakeRoundId = id, itemCode = itemCode, @@ -642,7 +644,7 @@ class StockTakeVarianceReportController( return Rep012ReportData( caption = stockTakeVarianceReportService.getStockTakeVarianceMultiRoundCaption(roundIds), - conditionLabel = "盤點條件:", + conditionLabel = UiText.t("盤點條件:", "Count filter: "), dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2Overall( itemCode = itemCode, storeId = storeId, diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index a68a082b..354062dc 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -1,5 +1,7 @@ package com.ffii.fpsms.modules.stock.service +import com.ffii.core.utils.UiText + import com.ffii.core.exception.BadRequestException import com.ffii.core.support.AbstractBaseEntityService import com.ffii.core.support.JdbcDao @@ -1249,7 +1251,7 @@ open class StockInLineService( val poLabel = JasperCompileManager.compileReport(inputStream) val qrCodeInfo = stockInLineRepository.findStockInLineInfoByIdInAndDeletedFalse(request.stockInLineIds).toMutableList() if (qrCodeInfo.isEmpty()) { - throw IllegalArgumentException("找不到來貨記錄,無法產生二維碼") + throw IllegalArgumentException(UiText.t("找不到來貨記錄,無法產生二維碼", "No receipt record was found, so a QR code cannot be created")) } val fields = mutableListOf>() diff --git a/src/main/resources/DeliveryNote/DeliveryNoteCartonLabelsPDF.jrxml b/src/main/resources/DeliveryNote/DeliveryNoteCartonLabelsPDF.jrxml index c0fa026a..887ead09 100644 --- a/src/main/resources/DeliveryNote/DeliveryNoteCartonLabelsPDF.jrxml +++ b/src/main/resources/DeliveryNote/DeliveryNoteCartonLabelsPDF.jrxml @@ -43,7 +43,7 @@ - + @@ -57,10 +57,9 @@ - - - + + + @@ -70,8 +69,8 @@ - - + + @@ -114,7 +113,7 @@ - + @@ -129,9 +128,9 @@ - - - + + + @@ -143,8 +142,8 @@ - - + + @@ -184,7 +183,7 @@ - + @@ -199,9 +198,8 @@ - - + + diff --git a/src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml b/src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml index 90a0b24a..da3670e2 100644 --- a/src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml +++ b/src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml @@ -36,7 +36,7 @@ - + @@ -44,8 +44,8 @@ - - + + @@ -53,7 +53,7 @@ - + @@ -61,8 +61,8 @@ - - + + @@ -93,7 +93,7 @@ - + @@ -101,8 +101,8 @@ - - + + @@ -112,15 +112,15 @@ - + - - + + @@ -128,15 +128,15 @@ - + - - + + @@ -151,7 +151,7 @@ - + @@ -159,9 +159,9 @@ - - - + + + @@ -169,8 +169,8 @@ - - + + @@ -241,7 +241,7 @@ - + @@ -250,9 +250,9 @@ - - - + + + @@ -261,9 +261,9 @@ - - - + + + @@ -271,9 +271,9 @@ - - - + + + @@ -281,8 +281,8 @@ - - + + @@ -386,7 +386,7 @@ - + @@ -394,8 +394,8 @@ - - + + diff --git a/src/main/resources/DeliveryNote/TruckRoutingSummaryPDF.jrxml b/src/main/resources/DeliveryNote/TruckRoutingSummaryPDF.jrxml index cb2cf7c3..e56998ba 100644 --- a/src/main/resources/DeliveryNote/TruckRoutingSummaryPDF.jrxml +++ b/src/main/resources/DeliveryNote/TruckRoutingSummaryPDF.jrxml @@ -24,19 +24,19 @@ - + - + - - + + @@ -44,13 +44,13 @@ - + - - + + @@ -65,34 +65,34 @@ - + - - - + + + - - - + + + - - - + + + - - + + @@ -127,9 +127,9 @@ - @@ -151,7 +151,7 @@ - + diff --git a/src/main/resources/FGStockInLabel/FGStockInLabel.jrxml b/src/main/resources/FGStockInLabel/FGStockInLabel.jrxml index 64ba06c7..9432b740 100644 --- a/src/main/resources/FGStockInLabel/FGStockInLabel.jrxml +++ b/src/main/resources/FGStockInLabel/FGStockInLabel.jrxml @@ -30,7 +30,7 @@ - + @@ -38,9 +38,8 @@ - - + + @@ -51,7 +50,7 @@ - + @@ -59,9 +58,8 @@ - - + + @@ -96,7 +94,7 @@ - + @@ -104,9 +102,8 @@ - - + + @@ -117,7 +114,7 @@ - + @@ -125,9 +122,9 @@ - - - + + + @@ -135,9 +132,8 @@ - - + + diff --git a/src/main/resources/PickRecord/PickRecordPDF.jrxml b/src/main/resources/PickRecord/PickRecordPDF.jrxml index eb0bf1f8..7cc108e7 100644 --- a/src/main/resources/PickRecord/PickRecordPDF.jrxml +++ b/src/main/resources/PickRecord/PickRecordPDF.jrxml @@ -1,6 +1,6 @@ - + @@ -25,7 +25,7 @@ - + @@ -33,8 +33,8 @@ - - + + @@ -52,9 +52,9 @@ - + - + @@ -62,8 +62,8 @@ - - + + @@ -89,7 +89,7 @@ - + @@ -97,9 +97,9 @@ - - - + + + @@ -107,8 +107,8 @@ - - + + @@ -118,7 +118,7 @@ - + @@ -126,8 +126,8 @@ - - + + @@ -137,7 +137,7 @@ - + @@ -145,8 +145,8 @@ - - + + @@ -156,7 +156,7 @@ - + @@ -164,8 +164,8 @@ - - + + @@ -175,7 +175,7 @@ - + @@ -183,8 +183,8 @@ - - + + @@ -194,7 +194,7 @@ - + @@ -202,8 +202,8 @@ - - + + @@ -217,7 +217,7 @@ - + @@ -226,9 +226,9 @@ - - - + + + @@ -237,9 +237,9 @@ - - - + + + @@ -247,9 +247,9 @@ - - - + + + @@ -257,9 +257,9 @@ - - - + + + @@ -267,8 +267,8 @@ - - + + diff --git a/src/main/resources/i18n/reports.properties b/src/main/resources/i18n/reports.properties new file mode 100644 index 00000000..b650aec6 --- /dev/null +++ b/src/main/resources/i18n/reports.properties @@ -0,0 +1,134 @@ +r00041da98651=上架總計 +r00c45eb6d637=送貨路線摘要( +r03ce1d5613e8=四月 +r080fcc65b244= 頁 +r09456e5bd8c2=上架數量 +r0db83ab5ad90=材料消耗日期: +r0e9cf47b8ab4=物料出倉追蹤報告 +r0eec66363def=箱數 +r11cb5e87977f=到期日 +r129bf23950c5=送貨日期 +r14aba5f426df=三月 +r1620c7944cce=成品編號: +r16b426416823=店鋪採購單編號:\n +r16d24ae82a7d=每單交貨總數: +r16d3eb3cba57=店鋪名稱 & 地址 +r17f6b62ed896=貨品名稱 +r1814ca0dcbea=送貨單編號: +r1af196452b7a=出貨數量 +r1b1840749a63=送貨訂單編號 +r1cb52e8436a0=提料人 +r1ea2fde25c82=纍計存出量 +r1efbb116be18=批號 +r1f60da14d9c0=入庫檢查\n缺陷數量 +r26101c67a651=箱數: +r2d36c3ab208f=審核時間 +r313fd5bb330e=一月 +r330e76596a33=庫存結餘報告 +r3313eb0eab26=入庫檢查\n缺陷百分比 +r33d99bd7d7ce=錯誤輸入\n或遺失 +r37b5ae5f0edd=十二月 +r38315806410a=貨車 +r384f3abedb37=八月 +r38aac4c4ec15=最後出倉日期: +r3a4716a37bef=物料編號: +r3b9bf196effe= 箱(蛋類除外) +r3baaab225b59=瑕疵品數量 +r3d589b9b7f8a=地址: +r3dd6f613acd0=店鋪代碼 +r3e002768eca6=成品出倉日期: +r469fb16b34e6=類型 +r4876d5589e5b=盤盈虧 +r4ce0ee8cc0b2=單位 +r4d7049a1bca9=庫存盤點報告 +r4dc2870c059c=單號: +r51024df5a7a1=品質檢查類型 +r53fcb0cb5f04=庫存明細報告 +r591faed0bf21=庫存品質檢測報告 +r5b17a0615359=十月 +r5d526c7219e5=物料名稱: +r5d940d9844a6=提料備注 +r5e20027c6a98=實測值 +r5f2d6fed3a7d=盤點前存量 +r5f876eb35fb3=到期日期:\n +r604d446aa100=頁數 +r60650ec2d16b=供應商: +r60940709a8ed=DN編號 +r62feaa72467a=出庫 +r63c7976d4b5b=纍計期初\n存量 +r64838faf9fb0=工單編號 +r649d1891700e=現存存貨 +r65116ebb128b=店鋪採購單號: +r659a9e9a692d=採購訂單編號: +r6850973fd787=膠茜數目: +r6ba3e4fc4ed5=出入賬日期 +r73005a9bafd9=完成生產日期: +r78b01a0a34a1=門店名稱 +r7e6f86ce8a6b=入庫 +r7eac0bb11fb9=存貨位置 +r7f693b56a6ce=貨品編號 +r832e34b15e49=纍計存量 +r8404b57157c6=數量: +r86216baadfe7=成品名稱: +r8689d87a0ff1=收貨日期: +r8bf5d9b7ff89=QC 檢測日期: +r8d4f15f17db3=卸貨順序 +r8d61728842d0=數量 +r8f3cddd57d47=六月 +r909fd3cf3808=庫存材料消耗趨勢報告 +r92f8cd219e20=成品出倉追蹤報告 +r93e2b9f9f280=五月 +r9460041e30e6=貨品總量: +r9483d51c0380=品質檢查樣板 +r95d67f026a0c=路綫 +r9b7133939fe4=銷售單位: +r9c35fe458ba5=生產日期: +r9cd9cfdac0ff=入庫\n檢查備註 +r9e0c4587c30c=入倉追蹤報告 +r9e0c52c95eed=參考編號 +r9e2304c74f73=提料單編號 +r9e296b27523f=總和 +r9eb0df469a62=入庫\n檢查結果 +r9f96f7e891cd= 至 +ra501f0a01799=不良品棄置 +raa3a98ce3af5=纍計存入量 +rab715e1b98f0=序號 +raddfcfcdc60e=送貨日期: +raeb07bde6c88=期初存量 +raeb34e99d155=生產日期:\n +raf1eb6fab37f=數量:\n +rb2778afe0187=日期: +rb568e0135677=年份: +rb7d45e4aed38=供應商名稱 +rbad7c57898bb=盤盈虧百分比 +rbca4ea568093=總出倉量: +rbe48450caef9=品質問題 +rc23e5226dd29=計劃車次出發時間: +rc54be4c935e7=店鋪: +rcd2a4bdc0cd8=二月 +rcfb0ffd45d73=盤點數 +rd04825997b36=頁 +rd27e4a4ff359=批次編號: +rd2ec8aa96e7a=貨車: +rd3d752f0f2e4=需求數量: +rd46b470b4009=QC 類型: +rd5712c61aadb=九月 +rd62378893466=板頭紙( +rdd672eea7e72=最後入倉日期: +rdfd42b9c455a=七月 +rdfef764f339a=十一月 +re4e94fa79f81=成品出倉報告 +re6eda806c6b1=報告日期: +re79ab7530b02=已提數量 +reee4e96971fb=成品/半成品生產分析報告 +rf03632dd7521=工單編號: +rf3924122809a=報告時間: +rf433fbec9e8a=入庫檢查\n樣品數量 +rf495347d6acf=第 +rf66b3d3ead11=總上架數量: +rf79aab5b7eed=到期日: +rf9015e9ab095=車線\n +rfa354ca634f3=) +rfaf5e4a94fd5=送貨單編號 +rff67331bff3b=批次編號:\n +rffe7a1f62dab=存倉位置 diff --git a/src/main/resources/i18n/reports_en.properties b/src/main/resources/i18n/reports_en.properties new file mode 100644 index 00000000..83af2e5e --- /dev/null +++ b/src/main/resources/i18n/reports_en.properties @@ -0,0 +1,134 @@ +r00041da98651=Put-away total +r00c45eb6d637=Delivery route summary ( +r03ce1d5613e8=Apr +r080fcc65b244= +r09456e5bd8c2=Put-away qty +r0db83ab5ad90=Consumption date: +r0e9cf47b8ab4=Material outbound traceability report +r0eec66363def=Cartons +r11cb5e87977f=Expiry date +r129bf23950c5=Delivery date +r14aba5f426df=Mar +r1620c7944cce=Finished good code: +r16b426416823=Shop PO no.:\n +r16d24ae82a7d=Delivery total: +r16d3eb3cba57=Shop name and address +r17f6b62ed896=Item name +r1814ca0dcbea=Delivery note no.: +r1af196452b7a=Shipped qty +r1b1840749a63=Delivery order no. +r1cb52e8436a0=Picker +r1ea2fde25c82=Cumulative stock out +r1efbb116be18=Lot no. +r1f60da14d9c0=Stock-in check\ndefect qty +r26101c67a651=Cartons: +r2d36c3ab208f=Approved at +r313fd5bb330e=Jan +r330e76596a33=Stock balance report +r3313eb0eab26=Stock-in check\ndefect % +r33d99bd7d7ce=Mis-entry\nor loss +r37b5ae5f0edd=Dec +r38315806410a=Truck +r384f3abedb37=Aug +r38aac4c4ec15=Last stock-out date: +r3a4716a37bef=Material code: +r3b9bf196effe= Cartons (excl. eggs) +r3baaab225b59=Defect qty +r3d589b9b7f8a=Address: +r3dd6f613acd0=Shop code +r3e002768eca6=FG outbound date: +r469fb16b34e6=Type +r4876d5589e5b=Variance +r4ce0ee8cc0b2=UOM +r4d7049a1bca9=Stock take report +r4dc2870c059c=Doc no.: +r51024df5a7a1=QC type +r53fcb0cb5f04=Stock ledger report +r591faed0bf21=Stock QC report +r5b17a0615359=Oct +r5d526c7219e5=Material name: +r5d940d9844a6=Pick notes +r5e20027c6a98=Measured value +r5f2d6fed3a7d=Qty before count +r5f876eb35fb3=Expiry date:\n +r604d446aa100=Page +r60650ec2d16b=Supplier: +r60940709a8ed=DN no. +r62feaa72467a=Stock out +r63c7976d4b5b=Opening\nbalance +r64838faf9fb0=Job order no. +r649d1891700e=On hand +r65116ebb128b=Shop PO no.: +r659a9e9a692d=PO no.: +r6850973fd787=Plastic crates: +r6ba3e4fc4ed5=Ledger date +r73005a9bafd9=Production finished: +r78b01a0a34a1=Shop name +r7e6f86ce8a6b=Stock in +r7eac0bb11fb9=Stock location +r7f693b56a6ce=Item code +r832e34b15e49=Cumulative balance +r8404b57157c6=Qty: +r86216baadfe7=Finished good: +r8689d87a0ff1=Receipt date: +r8bf5d9b7ff89=QC date: +r8d4f15f17db3=Unload sequence +r8d61728842d0=Qty +r8f3cddd57d47=Jun +r909fd3cf3808=Material consumption trend report +r92f8cd219e20=FG outbound traceability report +r93e2b9f9f280=May +r9460041e30e6=Total qty: +r9483d51c0380=QC template +r95d67f026a0c=Route +r9b7133939fe4=Sales UOM: +r9c35fe458ba5=Production date: +r9cd9cfdac0ff=Stock-in\ncheck notes +r9e0c4587c30c=Stock-in traceability report +r9e0c52c95eed=Reference no. +r9e2304c74f73=Pick order no. +r9e296b27523f=Total +r9eb0df469a62=Stock-in\ncheck result +r9f96f7e891cd= to +ra501f0a01799=Damaged disposal +raa3a98ce3af5=Cumulative stock in +rab715e1b98f0=No. +raddfcfcdc60e=Delivery date: +raeb07bde6c88=Opening qty +raeb34e99d155=Production date:\n +raf1eb6fab37f=Qty:\n +rb2778afe0187=Date: +rb568e0135677=Year: +rb7d45e4aed38=Supplier name +rbad7c57898bb=Variance % +rbca4ea568093=Total outbound qty: +rbe48450caef9=Quality issue +rc23e5226dd29=Planned departure: +rc54be4c935e7=Shop: +rcd2a4bdc0cd8=Feb +rcfb0ffd45d73=Counted qty +rd04825997b36=Page +rd27e4a4ff359=Lot no.: +rd2ec8aa96e7a=Truck: +rd3d752f0f2e4=Required qty: +rd46b470b4009=QC type: +rd5712c61aadb=Sep +rd62378893466=Pallet sheet ( +rdd672eea7e72=Last stock-in date: +rdfd42b9c455a=Jul +rdfef764f339a=Nov +re4e94fa79f81=Finished goods outbound report +re6eda806c6b1=Report date: +re79ab7530b02=Picked qty +reee4e96971fb=Finished / semi-finished production report +rf03632dd7521=Job order no.: +rf3924122809a=Report time: +rf433fbec9e8a=Stock-in check\nsample qty +rf495347d6acf=Page +rf66b3d3ead11=Total put-away qty: +rf79aab5b7eed=Expiry date: +rf9015e9ab095=Truck lane\n +rfa354ca634f3=) +rfaf5e4a94fd5=Delivery note no. +rff67331bff3b=Lot no.:\n +rffe7a1f62dab=Storage location diff --git a/src/main/resources/jasper/FGDeliveryReport.jrxml b/src/main/resources/jasper/FGDeliveryReport.jrxml index eb391924..363e49c6 100644 --- a/src/main/resources/jasper/FGDeliveryReport.jrxml +++ b/src/main/resources/jasper/FGDeliveryReport.jrxml @@ -83,13 +83,13 @@ 1]]> - + - - + + @@ -129,7 +129,7 @@ - + @@ -137,8 +137,8 @@ - - + + @@ -156,7 +156,7 @@ - + @@ -164,8 +164,8 @@ - - + + @@ -173,7 +173,7 @@ - + @@ -181,9 +181,9 @@ - - - + + + @@ -191,8 +191,8 @@ - - + + @@ -207,7 +207,7 @@ - + @@ -215,8 +215,8 @@ - - + + @@ -224,9 +224,9 @@ - + - + @@ -235,9 +235,9 @@ - - - + + + @@ -246,9 +246,9 @@ - - - + + + @@ -257,9 +257,9 @@ - - - + + + @@ -268,9 +268,9 @@ - - - + + + @@ -279,9 +279,9 @@ - - - + + + @@ -290,9 +290,9 @@ - - - + + + @@ -301,9 +301,9 @@ - - - + + + @@ -312,8 +312,8 @@ - - + + diff --git a/src/main/resources/jasper/FGStockOutTraceabilityReport.jrxml b/src/main/resources/jasper/FGStockOutTraceabilityReport.jrxml index 6959ca05..6d278b74 100644 --- a/src/main/resources/jasper/FGStockOutTraceabilityReport.jrxml +++ b/src/main/resources/jasper/FGStockOutTraceabilityReport.jrxml @@ -74,7 +74,7 @@ 1]]> - + @@ -82,8 +82,8 @@ - - + + @@ -127,7 +127,7 @@ - + @@ -135,9 +135,9 @@ - - - + + + @@ -146,16 +146,16 @@ - - + + - + - + @@ -164,9 +164,9 @@ - - - + + + @@ -175,9 +175,9 @@ - - - + + + @@ -186,9 +186,9 @@ - - - + + + @@ -196,9 +196,9 @@ - - - + + + @@ -207,9 +207,9 @@ - - - + + + @@ -217,9 +217,9 @@ - - - + + + @@ -227,9 +227,9 @@ - - - + + + @@ -238,9 +238,9 @@ - - - + + + @@ -249,8 +249,8 @@ - - + + @@ -280,7 +280,7 @@ - + @@ -288,8 +288,8 @@ - - + + @@ -305,7 +305,7 @@ - + @@ -313,9 +313,9 @@ - - - + + + @@ -323,8 +323,8 @@ - - + + diff --git a/src/main/resources/jasper/ItemQCReport.jrxml b/src/main/resources/jasper/ItemQCReport.jrxml index a6c47ca3..8d3c14d1 100644 --- a/src/main/resources/jasper/ItemQCReport.jrxml +++ b/src/main/resources/jasper/ItemQCReport.jrxml @@ -97,7 +97,7 @@ - + @@ -105,9 +105,9 @@ - - - + + + @@ -116,9 +116,9 @@ - - - + + + @@ -127,9 +127,9 @@ - - - + + + @@ -138,9 +138,9 @@ - - - + + + @@ -149,9 +149,9 @@ - - - + + + @@ -160,9 +160,9 @@ - - - + + + @@ -171,9 +171,9 @@ - - - + + + @@ -182,9 +182,9 @@ - - - + + + @@ -193,8 +193,8 @@ - - + + @@ -225,7 +225,7 @@ - + @@ -233,8 +233,8 @@ - - + + @@ -242,7 +242,7 @@ - + @@ -250,8 +250,8 @@ - - + + @@ -259,7 +259,7 @@ - + @@ -267,9 +267,9 @@ - - - + + + @@ -278,14 +278,14 @@ - - + + - + @@ -294,13 +294,13 @@ - + - - + + diff --git a/src/main/resources/jasper/MaterialStockOutTraceability.jrxml b/src/main/resources/jasper/MaterialStockOutTraceability.jrxml index 6bfa86e6..7c405881 100644 --- a/src/main/resources/jasper/MaterialStockOutTraceability.jrxml +++ b/src/main/resources/jasper/MaterialStockOutTraceability.jrxml @@ -69,7 +69,7 @@ 1]]> - + @@ -77,8 +77,8 @@ - - + + @@ -123,7 +123,7 @@ - + @@ -131,9 +131,9 @@ - - - + + + @@ -141,9 +141,9 @@ - - - + + + @@ -152,9 +152,9 @@ - - - + + + @@ -163,9 +163,9 @@ - - - + + + @@ -174,9 +174,9 @@ - - - + + + @@ -184,9 +184,9 @@ - - - + + + @@ -195,9 +195,9 @@ - - - + + + @@ -205,9 +205,9 @@ - - - + + + @@ -216,8 +216,8 @@ - - + + @@ -248,7 +248,7 @@ - + @@ -256,8 +256,8 @@ - - + + @@ -265,7 +265,7 @@ - + @@ -273,8 +273,8 @@ - - + + @@ -282,7 +282,7 @@ - + @@ -290,9 +290,9 @@ - - - + + + @@ -301,14 +301,14 @@ - - + + - + diff --git a/src/main/resources/jasper/SemiFGProductionAnalysisReport.jrxml b/src/main/resources/jasper/SemiFGProductionAnalysisReport.jrxml index e581426a..09b80a7b 100644 --- a/src/main/resources/jasper/SemiFGProductionAnalysisReport.jrxml +++ b/src/main/resources/jasper/SemiFGProductionAnalysisReport.jrxml @@ -366,7 +366,7 @@ - + @@ -374,8 +374,8 @@ - - + + @@ -393,7 +393,7 @@ - + @@ -401,8 +401,8 @@ - - + + @@ -410,7 +410,7 @@ - + @@ -418,9 +418,9 @@ - - - + + + @@ -428,8 +428,8 @@ - - + + @@ -444,7 +444,7 @@ - + @@ -452,8 +452,8 @@ - - + + @@ -461,9 +461,9 @@ - + - + @@ -472,8 +472,8 @@ - - + + @@ -488,16 +488,16 @@ - + - - - + + + @@ -505,9 +505,9 @@ - - - + + + @@ -515,9 +515,9 @@ - - - + + + @@ -525,9 +525,9 @@ - - - + + + @@ -535,9 +535,9 @@ - - - + + + @@ -545,9 +545,9 @@ - - - + + + @@ -555,9 +555,9 @@ - - - + + + @@ -565,9 +565,9 @@ - - - + + + @@ -575,9 +575,9 @@ - - - + + + @@ -585,9 +585,9 @@ - - - + + + @@ -595,9 +595,9 @@ - - - + + + @@ -605,9 +605,9 @@ - - - + + + @@ -615,14 +615,14 @@ - - + + - + @@ -630,9 +630,9 @@ - - - + + + @@ -640,9 +640,9 @@ - - - + + + @@ -650,8 +650,8 @@ - - + + diff --git a/src/main/resources/jasper/StockBalanceReport.jrxml b/src/main/resources/jasper/StockBalanceReport.jrxml index 2feb7101..7a3af743 100644 --- a/src/main/resources/jasper/StockBalanceReport.jrxml +++ b/src/main/resources/jasper/StockBalanceReport.jrxml @@ -200,7 +200,7 @@ - + @@ -208,9 +208,9 @@ - - - + + + @@ -219,9 +219,9 @@ - - - + + + @@ -229,9 +229,9 @@ - - - + + + @@ -240,10 +240,9 @@ - - - + + + @@ -252,8 +251,8 @@ - - + + @@ -261,7 +260,7 @@ - + @@ -269,14 +268,14 @@ - - + + - + @@ -284,8 +283,8 @@ - - + + @@ -310,7 +309,7 @@ - + @@ -318,8 +317,8 @@ - - + + @@ -338,7 +337,7 @@ - + @@ -347,9 +346,9 @@ - - - + + + @@ -357,9 +356,9 @@ - - - + + + @@ -368,9 +367,9 @@ - - - + + + @@ -379,9 +378,9 @@ - - - + + + @@ -390,9 +389,9 @@ - - - + + + @@ -401,8 +400,8 @@ - - + + diff --git a/src/main/resources/jasper/StockInTraceabilityReport.jrxml b/src/main/resources/jasper/StockInTraceabilityReport.jrxml index 6ee11df2..129f4806 100644 --- a/src/main/resources/jasper/StockInTraceabilityReport.jrxml +++ b/src/main/resources/jasper/StockInTraceabilityReport.jrxml @@ -78,7 +78,7 @@ 1]]> - + @@ -87,8 +87,8 @@ - - + + @@ -140,7 +140,7 @@ - + @@ -148,9 +148,9 @@ - - - + + + @@ -159,9 +159,9 @@ - - - + + + @@ -170,9 +170,9 @@ - - - + + + @@ -181,9 +181,9 @@ - - - + + + @@ -192,9 +192,9 @@ - - - + + + @@ -203,10 +203,9 @@ - - - + + + @@ -215,10 +214,9 @@ - - - + + + @@ -227,9 +225,9 @@ - - - + + + @@ -238,10 +236,9 @@ - - - + + + @@ -250,9 +247,9 @@ - - - + + + @@ -261,10 +258,9 @@ - - - + + + @@ -273,9 +269,8 @@ - - + + @@ -288,7 +283,7 @@ - + @@ -296,8 +291,8 @@ - - + + @@ -308,9 +303,9 @@ - + - + @@ -318,8 +313,8 @@ - - + + @@ -344,7 +339,7 @@ - + @@ -352,9 +347,9 @@ - - - + + + @@ -363,8 +358,8 @@ - - + + diff --git a/src/main/resources/jasper/StockItemConsumptionTrendReport.jrxml b/src/main/resources/jasper/StockItemConsumptionTrendReport.jrxml index 1ca277dd..aafa8cb1 100644 --- a/src/main/resources/jasper/StockItemConsumptionTrendReport.jrxml +++ b/src/main/resources/jasper/StockItemConsumptionTrendReport.jrxml @@ -366,7 +366,7 @@ - + @@ -374,8 +374,8 @@ - - + + @@ -393,7 +393,7 @@ - + @@ -401,8 +401,8 @@ - - + + @@ -410,7 +410,7 @@ - + @@ -418,9 +418,9 @@ - - - + + + @@ -428,8 +428,8 @@ - - + + @@ -444,7 +444,7 @@ - + @@ -452,8 +452,8 @@ - - + + @@ -461,9 +461,9 @@ - + - + @@ -472,8 +472,8 @@ - - + + @@ -488,16 +488,16 @@ - + - - - + + + @@ -505,9 +505,9 @@ - - - + + + @@ -515,9 +515,9 @@ - - - + + + @@ -525,9 +525,9 @@ - - - + + + @@ -535,9 +535,9 @@ - - - + + + @@ -545,9 +545,9 @@ - - - + + + @@ -555,9 +555,9 @@ - - - + + + @@ -565,9 +565,9 @@ - - - + + + @@ -575,9 +575,9 @@ - - - + + + @@ -585,9 +585,9 @@ - - - + + + @@ -595,9 +595,9 @@ - - - + + + @@ -605,9 +605,9 @@ - - - + + + @@ -615,14 +615,14 @@ - - + + - + @@ -630,9 +630,9 @@ - - - + + + @@ -640,9 +640,9 @@ - - - + + + @@ -650,8 +650,8 @@ - - + + diff --git a/src/main/resources/jasper/StockLedgarReport.jrxml b/src/main/resources/jasper/StockLedgarReport.jrxml index a85ee22a..f0b0aaa5 100644 --- a/src/main/resources/jasper/StockLedgarReport.jrxml +++ b/src/main/resources/jasper/StockLedgarReport.jrxml @@ -89,7 +89,7 @@ 1]]> - + @@ -98,8 +98,8 @@ - - + + @@ -151,7 +151,7 @@ - + @@ -159,8 +159,8 @@ - - + + @@ -171,7 +171,7 @@ - + @@ -179,9 +179,9 @@ - - - + + + @@ -190,9 +190,9 @@ - - - + + + @@ -201,9 +201,9 @@ - - - + + + @@ -212,9 +212,9 @@ - - - + + + @@ -222,9 +222,9 @@ - - - + + + @@ -232,9 +232,9 @@ - - - + + + @@ -243,9 +243,9 @@ - - - + + + @@ -254,9 +254,9 @@ - - - + + + @@ -264,10 +264,9 @@ - - - + + + @@ -275,8 +274,8 @@ - - + + @@ -296,7 +295,7 @@ - + @@ -304,8 +303,8 @@ - - + + @@ -331,7 +330,7 @@ - + @@ -339,9 +338,9 @@ - - - + + + @@ -349,8 +348,8 @@ - - + + diff --git a/src/main/resources/jasper/StockTakeVarianceReport.jrxml b/src/main/resources/jasper/StockTakeVarianceReport.jrxml index 04020a3a..be9fb504 100644 --- a/src/main/resources/jasper/StockTakeVarianceReport.jrxml +++ b/src/main/resources/jasper/StockTakeVarianceReport.jrxml @@ -152,7 +152,7 @@ - + @@ -160,8 +160,8 @@ - - + + @@ -181,7 +181,7 @@ - + @@ -211,7 +211,7 @@ - + @@ -219,9 +219,9 @@ - - - + + + @@ -229,13 +229,13 @@ - - + + - + @@ -244,9 +244,9 @@ - - - + + + @@ -255,9 +255,9 @@ - - - + + + @@ -266,9 +266,9 @@ - - - + + + @@ -277,9 +277,9 @@ - - - + + + @@ -288,9 +288,9 @@ - - - + + + @@ -299,9 +299,9 @@ - - - + + + @@ -310,9 +310,9 @@ - - - + + + @@ -321,14 +321,14 @@ - - + + - + @@ -337,8 +337,8 @@ - - + + diff --git a/src/main/resources/qrCodeLabel/poItemPDF.jrxml b/src/main/resources/qrCodeLabel/poItemPDF.jrxml index fa0c8ddd..f9b371f1 100644 --- a/src/main/resources/qrCodeLabel/poItemPDF.jrxml +++ b/src/main/resources/qrCodeLabel/poItemPDF.jrxml @@ -38,20 +38,20 @@ - + - - - + + + - - + + @@ -62,13 +62,13 @@ - + - - + + @@ -79,13 +79,13 @@ - + - - + + @@ -103,15 +103,15 @@ - + - - + + @@ -122,13 +122,13 @@ - + - - + + @@ -139,13 +139,13 @@ - + - - + + @@ -156,13 +156,13 @@ - + - - + + diff --git a/src/main/resources/qrCodeLabel/poItemPDF_v2.jrxml b/src/main/resources/qrCodeLabel/poItemPDF_v2.jrxml index d4a57f3d..8feb56c6 100644 --- a/src/main/resources/qrCodeLabel/poItemPDF_v2.jrxml +++ b/src/main/resources/qrCodeLabel/poItemPDF_v2.jrxml @@ -30,13 +30,13 @@ - + - - + + @@ -44,13 +44,13 @@ - + - - + + @@ -58,13 +58,13 @@ - + - - + + @@ -72,20 +72,20 @@ - + - - - + + + - - + + @@ -109,13 +109,13 @@ - + - - + + @@ -127,13 +127,13 @@ - + - - + + @@ -141,13 +141,13 @@ - + - - + + diff --git a/src/main/resources/qrCodeLabel/poItemPDF_v3.jrxml b/src/main/resources/qrCodeLabel/poItemPDF_v3.jrxml index 47c4aec3..6ff1a4b8 100644 --- a/src/main/resources/qrCodeLabel/poItemPDF_v3.jrxml +++ b/src/main/resources/qrCodeLabel/poItemPDF_v3.jrxml @@ -30,13 +30,13 @@ - + - - + + @@ -44,13 +44,13 @@ - + - - + + @@ -58,13 +58,13 @@ - + - - + + @@ -72,20 +72,20 @@ - + - - - + + + - - + + @@ -109,13 +109,13 @@ - + - - + + @@ -127,13 +127,13 @@ - + - - + + @@ -141,13 +141,13 @@ - + - - + + diff --git a/src/main/resources/qrCodeLabel/poItemPDF_v4.jrxml b/src/main/resources/qrCodeLabel/poItemPDF_v4.jrxml index 31e29cd3..576c79c0 100644 --- a/src/main/resources/qrCodeLabel/poItemPDF_v4.jrxml +++ b/src/main/resources/qrCodeLabel/poItemPDF_v4.jrxml @@ -30,13 +30,13 @@ - + - - + + @@ -44,13 +44,13 @@ - + - - + + @@ -58,20 +58,20 @@ - + - - - + + + - - + + @@ -95,13 +95,13 @@ - + - - + + @@ -113,13 +113,13 @@ - + - - + + @@ -127,13 +127,13 @@ - + - - + + @@ -141,13 +141,13 @@ - + - - + +