diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 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 55c546b7..9e2eee36 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 @@ -111,28 +111,14 @@ SELECT reOrderLevel, reOrderQty, - CASE WHEN inQty < 0 THEN CONCAT('(', FORMAT(-inQty, 0), ')') ELSE FORMAT(inQty, 0) END AS stockIn, - CASE WHEN outQty < 0 THEN CONCAT('(', FORMAT(-outQty, 0), ')') ELSE FORMAT(outQty, 0) END AS stockOut, - - CASE WHEN cumOpeningBalRaw < 0 - THEN CONCAT('(', FORMAT(-cumOpeningBalRaw, 0), ')') - ELSE FORMAT(cumOpeningBalRaw, 0) END AS cumOpeningBal, - CASE WHEN bal < 0 - THEN CONCAT('(', FORMAT(-bal, 0), ')') - ELSE FORMAT(bal, 0) END AS cumBalance, - - CASE WHEN totalCumOpeningBalRaw < 0 - THEN CONCAT('(', FORMAT(-totalCumOpeningBalRaw, 0), ')') - ELSE FORMAT(totalCumOpeningBalRaw, 0) END AS totalCumOpeningBal, - CASE WHEN totalStockInRaw < 0 - THEN CONCAT('(', FORMAT(-totalStockInRaw, 0), ')') - ELSE FORMAT(totalStockInRaw, 0) END AS totalStockIn, - CASE WHEN totalStockOutRaw < 0 - THEN CONCAT('(', FORMAT(-totalStockOutRaw, 0), ')') - ELSE FORMAT(totalStockOutRaw, 0) END AS totalStockOut, - CASE WHEN totalCumBalanceRaw < 0 - THEN CONCAT('(', FORMAT(-totalCumBalanceRaw, 0), ')') - ELSE FORMAT(totalCumBalanceRaw, 0) END AS totalCumBalance + ${signedQtySql("inQty")} AS stockIn, + ${signedQtySql("outQty")} AS stockOut, + ${signedQtySql("cumOpeningBalRaw")} AS cumOpeningBal, + ${signedQtySql("bal")} AS cumBalance, + ${signedQtySql("totalCumOpeningBalRaw")} AS totalCumOpeningBal, + ${signedQtySql("totalStockInRaw")} AS totalStockIn, + ${signedQtySql("totalStockOutRaw")} AS totalStockOut, + ${signedQtySql("totalCumBalanceRaw")} AS totalCumBalance FROM ( SELECT @@ -278,6 +264,15 @@ ORDER BY return jdbcDao.queryForList(sql, args) } + /** + * Qty display: up to 2 decimal places, trailing zeros stripped so whole numbers stay whole. + * Negatives use accounting parentheses, matching the rest of the report. + */ + private fun signedQtySql(expr: String): String = + """CASE WHEN $expr < 0 + THEN CONCAT('(', TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(-($expr), 2))), ')') + ELSE TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT($expr, 2))) END""" + /** LIKE 多值工具方法 */ private fun buildMultiValueLikeClause( paramValue: String?, 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 53450321..cd20bacf 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 @@ -19,6 +19,7 @@ import org.apache.poi.ss.util.CellRangeAddress import org.apache.poi.ss.util.WorkbookUtil import org.apache.poi.xssf.streaming.SXSSFWorkbook import java.io.ByteArrayOutputStream +import java.util.Locale @RestController @RequestMapping("/report") @@ -32,7 +33,7 @@ class StockLedgerReportController( val header: CellStyle, val text: CellStyle, val center: CellStyle, - val int: CellStyle, + val number: CellStyle, val dash: CellStyle, val sumQty: CellStyle, val sumLabel: CellStyle, @@ -166,7 +167,7 @@ class StockLedgerReportController( borderLeft = BorderStyle.THIN borderRight = BorderStyle.THIN } - val intStyle = workbook.createCellStyle().apply { + val numberStyle = workbook.createCellStyle().apply { alignment = HorizontalAlignment.RIGHT verticalAlignment = VerticalAlignment.CENTER borderTop = BorderStyle.THIN @@ -174,7 +175,7 @@ class StockLedgerReportController( borderLeft = BorderStyle.THIN borderRight = BorderStyle.THIN val df: DataFormat = workbook.createDataFormat() - dataFormat = df.getFormat("#,##0") + dataFormat = df.getFormat("#,##0.##") } val dashStyle = workbook.createCellStyle().apply { alignment = HorizontalAlignment.RIGHT @@ -188,7 +189,7 @@ class StockLedgerReportController( alignment = HorizontalAlignment.RIGHT verticalAlignment = VerticalAlignment.CENTER val df: DataFormat = workbook.createDataFormat() - dataFormat = df.getFormat("#,##0") + dataFormat = df.getFormat("#,##0.##") borderTop = BorderStyle.THICK borderBottom = BorderStyle.THICK borderLeft = BorderStyle.THIN @@ -230,7 +231,7 @@ class StockLedgerReportController( header = headerStyle, text = textStyle, center = centerStyle, - int = intStyle, + number = numberStyle, dash = dashStyle, sumQty = sumQty, sumLabel = sumLabel, @@ -255,11 +256,11 @@ class StockLedgerReportController( return if (negative) -n else n } - private fun setIntCellFromFormatted( + private fun setNumberCellFromFormatted( row: Row, col: Int, value: Any?, - intStyle: CellStyle, + numberStyle: CellStyle, dashStyle: CellStyle, ) { val cell = row.createCell(col) @@ -270,15 +271,20 @@ class StockLedgerReportController( return } if (parsed < 0) { - val s = "%,d".format(kotlin.math.abs(parsed).toLong()) - cell.setCellValue("($s)") + cell.setCellValue("(${formatAbsQty(parsed)})") cell.cellStyle = dashStyle } else { cell.setCellValue(parsed) - cell.cellStyle = intStyle + cell.cellStyle = numberStyle } } + /** Up to 2 dp; strip trailing zeros so whole numbers stay whole. */ + private fun formatAbsQty(v: Double): String { + val formatted = "%,.2f".format(Locale.US, kotlin.math.abs(v)) + return formatted.trimEnd('0').trimEnd('.') + } + /** * SXSSF keeps only a sliding window of rows in memory to avoid OOM on large exports. */ @@ -353,9 +359,9 @@ class StockLedgerReportController( r.createCell(c).apply { setCellValue(""); cellStyle = styles.sumEmpty } } r.getCell(7).apply { setCellValue("貨品總量:"); 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) + setNumberCellFromFormatted(r, 8, totalIn, styles.sumQty, styles.dash) + setNumberCellFromFormatted(r, 9, totalOut, styles.sumQty, styles.dash) + setNumberCellFromFormatted(r, 10, totalBal, styles.sumQty, styles.dash) } if (dbData.isEmpty()) { @@ -397,10 +403,10 @@ class StockLedgerReportController( setTextCell(r, 4, typeText, styles.center) setTextCell(r, 5, m["lotNo"], styles.text) setTextCell(r, 6, m["expiryDate"], styles.center) - setIntCellFromFormatted(r, 7, m["cumOpeningBal"], styles.int, styles.dash) - setIntCellFromFormatted(r, 8, m["stockIn"], styles.int, styles.dash) - setIntCellFromFormatted(r, 9, m["stockOut"], styles.int, styles.dash) - setIntCellFromFormatted(r, 10, m["cumBalance"], styles.int, styles.dash) + setNumberCellFromFormatted(r, 7, m["cumOpeningBal"], styles.number, styles.dash) + setNumberCellFromFormatted(r, 8, m["stockIn"], styles.number, styles.dash) + setNumberCellFromFormatted(r, 9, m["stockOut"], styles.number, styles.dash) + setNumberCellFromFormatted(r, 10, m["cumBalance"], styles.number, styles.dash) setTextCell(r, 11, m["orderRefNo"], styles.text) setTextCell(r, 12, m["storeLocation"], styles.center)