Selaa lähdekoodia

FG STOCK OUT TRACEABILITY REPORT EXTRACT

master
B.E.N.S.O.N 11 tuntia sitten
vanhempi
commit
e3e9fe41c5
4 muutettua tiedostoa jossa 326 lisäystä ja 271 poistoa
  1. +252
    -0
      src/main/java/com/ffii/fpsms/modules/report/service/FGStockOutTraceabilityReportService.kt
  2. +0
    -219
      src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt
  3. +74
    -0
      src/main/java/com/ffii/fpsms/modules/report/web/FGStockOutTraceabilityReportController.kt
  4. +0
    -52
      src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt

+ 252
- 0
src/main/java/com/ffii/fpsms/modules/report/service/FGStockOutTraceabilityReportService.kt Näytä tiedosto

@@ -0,0 +1,252 @@
package com.ffii.fpsms.modules.report.service

import com.ffii.core.support.JdbcDao
import org.springframework.stereotype.Service

@Service
class FGStockOutTraceabilityReportService(
private val jdbcDao: JdbcDao,
) {
fun getDistinctHandlersForFGStockOutTraceability(): List<String> {
val sql = """
SELECT DISTINCT COALESCE(picker_user.name, modified_user.name, '') AS handler
FROM stock_out_line sol
INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0 AND so.type = 'do'
LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
LEFT JOIN user modified_user ON sol.modifiedBy = modified_user.staffNo AND modified_user.deleted = 0 AND sol.handled_by IS NULL
WHERE sol.deleted = 0
ORDER BY handler
""".trimIndent()

return jdbcDao
.queryForList(sql, emptyMap<String, Any>())
.map { row -> (row["handler"]?.toString() ?: "").trim() }
.filter { it.isNotBlank() }
}

fun searchFGStockOutTraceabilityReport(
stockCategory: String?,
stockSubCategory: String?,
itemCode: String?,
year: String?,
lastOutDateStart: String?,
lastOutDateEnd: String?,
handler: String?,
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()

val stockCategorySql = buildMultiValueExactClause(
stockCategory,
"it.type",
"stockCategory",
args,
)

// Keep parameter for API compatibility; currently no SQL filter is needed.
val stockSubCategorySql = ""

val itemCodeSql = buildMultiValueLikeClause(
itemCode,
"it.code",
"itemCode",
args,
)

val yearSql = if (!year.isNullOrBlank()) {
args["year"] = year
"AND YEAR(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) = :year"
} else {
""
}

val lastOutDateStartSql = if (!lastOutDateStart.isNullOrBlank()) {
val formattedDate = lastOutDateStart.replace("/", "-")
args["lastOutDateStart"] = formattedDate
"AND DATE(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) >= DATE(:lastOutDateStart)"
} else {
""
}

val lastOutDateEndSql = if (!lastOutDateEnd.isNullOrBlank()) {
val formattedDate = lastOutDateEnd.replace("/", "-")
args["lastOutDateEnd"] = formattedDate
"AND DATE(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) <= DATE(:lastOutDateEnd)"
} else {
""
}

val handlerSql = buildMultiValueExactClause(
handler,
"COALESCE(picker_user.name, modified_user.name, '')",
"handler",
args,
)

val sql = """
SELECT
IFNULL(DATE_FORMAT(
IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate),
'%Y-%m-%d'
), '') AS deliveryDate,
IFNULL(it.code, '') AS itemNo,
IFNULL(it.name, '') AS itemName,
IFNULL(uc.udfudesc, '') AS unitOfMeasure,
IFNULL(dpor.deliveryNoteCode, '') AS dnNo,
CAST(IFNULL(sp.id, 0) AS CHAR) AS customerId,
IFNULL(sp.name, '') AS customerName,
FORMAT(
ROUND(SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code), 0), 0
) AS qtyNumeric,
FORMAT(ROUND(IFNULL(sol.qty, 0), 0), 0) AS qty,
'' AS truckNo,
'' AS driver,
IFNULL(do.code, '') AS deliveryOrderNo,
IFNULL(po.code, '') AS fgPickOrderNo,
IFNULL(po.code, '') AS stockReqNo,
IFNULL(il.lotNo, '') AS lotNo,
IFNULL(DATE_FORMAT(il.expiryDate, '%Y-%m-%d'), '') AS expiryDate,
FORMAT(ROUND(IFNULL(sol.qty, 0), 0), 0) AS stockOutQty,
COALESCE(
picker_user.name,
modified_user.name,
''
) AS handler,
COALESCE(
picker_user.name,
modified_user.name,
''
) AS pickedBy,
GROUP_CONCAT(DISTINCT wh.code ORDER BY wh.code SEPARATOR ', ') AS storeLocation,
'' AS pickRemark,
FORMAT(
ROUND(SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code), 0), 0
) AS totalStockOutQty,
0 AS stockSubCategory
FROM do_pick_order_line_record dpolr
LEFT JOIN do_pick_order_record dpor
ON dpolr.record_id = dpor.id
AND dpor.deleted = 0
AND dpor.ticket_status = 'completed'
INNER JOIN delivery_order do
ON dpolr.do_order_id = do.id
AND do.deleted = 0
LEFT JOIN shop sp
ON do.shopId = sp.id
AND sp.deleted = 0
LEFT JOIN delivery_order_line dol
ON do.id = dol.deliveryOrderId
AND dol.deleted = 0
LEFT JOIN items it
ON dol.itemId = it.id
AND it.deleted = 0
LEFT JOIN item_uom iu
ON it.id = iu.itemId
AND iu.stockUnit = 1
LEFT JOIN uom_conversion uc
ON iu.uomId = uc.id
LEFT JOIN pick_order_line pol
ON dpolr.pick_order_id = pol.poId
AND pol.itemId = it.id
AND pol.deleted = 0
LEFT JOIN pick_order po
ON pol.poId = po.id
AND po.deleted = 0
LEFT JOIN stock_out_line sol
ON pol.id = sol.pickOrderLineId
AND sol.itemId = it.id
AND sol.deleted = 0
LEFT JOIN stock_out so
ON sol.stockOutId = so.id
AND so.deleted = 0
AND so.type = 'do'
LEFT JOIN inventory_lot_line ill
ON sol.inventoryLotLineId = ill.id
AND ill.deleted = 0
LEFT JOIN inventory_lot il
ON ill.inventoryLotId = il.id
AND il.deleted = 0
LEFT JOIN warehouse wh
ON ill.warehouseId = wh.id
AND wh.deleted = 0
LEFT JOIN user picker_user
ON sol.handled_by = picker_user.id
AND picker_user.deleted = 0
LEFT JOIN user modified_user
ON sol.modifiedBy = modified_user.staffNo
AND modified_user.deleted = 0
AND sol.handled_by IS NULL
WHERE
dpolr.deleted = 0
$stockCategorySql
$stockSubCategorySql
$itemCodeSql
$yearSql
$lastOutDateStartSql
$lastOutDateEndSql
$handlerSql
GROUP BY
sol.id,
dpor.RequiredDeliveryDate,
do.estimatedArrivalDate,
it.code,
it.name,
uc.udfudesc,
dpor.deliveryNoteCode,
sp.id,
sp.name,
sol.qty,
picker_user.name,
modified_user.name,
po.code,
do.code,
il.lotNo,
il.expiryDate
ORDER BY
it.code,
deliveryDate,
il.lotNo
""".trimIndent()

return jdbcDao.queryForList(sql, args)
}

private fun buildMultiValueLikeClause(
paramValue: String?,
columnName: String,
paramPrefix: String,
args: MutableMap<String, Any>,
): String {
if (paramValue.isNullOrBlank()) return ""

val values = paramValue.split(",").map { it.trim() }.filter { it.isNotBlank() }
if (values.isEmpty()) return ""

val conditions = values.mapIndexed { index, value ->
val paramName = "${paramPrefix}_$index"
args[paramName] = "%$value%"
"$columnName LIKE :$paramName"
}

return "AND (${conditions.joinToString(" OR ")})"
}

private fun buildMultiValueExactClause(
paramValue: String?,
columnName: String,
paramPrefix: String,
args: MutableMap<String, Any>,
): String {
if (paramValue.isNullOrBlank()) return ""

val values = paramValue.split(",").map { it.trim() }.filter { it.isNotBlank() }
if (values.isEmpty()) return ""

val conditions = values.mapIndexed { index, value ->
val paramName = "${paramPrefix}_$index"
args[paramName] = value
"$columnName = :$paramName"
}

return "AND (${conditions.joinToString(" OR ")})"
}
}

+ 0
- 219
src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt Näytä tiedosto

@@ -272,225 +272,6 @@ return result
}

fun getDistinctHandlersForFGStockOutTraceability(): List<String> {
val sql = """
SELECT DISTINCT COALESCE(picker_user.name, modified_user.name, '') AS handler
FROM stock_out_line sol
INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0 AND so.type = 'do'
LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
LEFT JOIN user modified_user ON sol.modifiedBy = modified_user.staffNo AND modified_user.deleted = 0 AND sol.handled_by IS NULL
WHERE sol.deleted = 0
ORDER BY handler
""".trimIndent()
return jdbcDao.queryForList(sql, emptyMap<String, Any>()).map { row -> (row["handler"]?.toString() ?: "").trim() }.filter { it.isNotBlank() }
}

fun searchFGStockOutTraceabilityReport(
stockCategory: String?,
stockSubCategory: String?,
itemCode: String?,
year: String?,
lastOutDateStart: String?,
lastOutDateEnd: String?,
handler: String?
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
// Stock Category 过滤:通过 items.type
val stockCategorySql = if (!stockCategory.isNullOrBlank()) {
val categories = stockCategory.split(",").map { it.trim() }.filter { it.isNotBlank() }
if (categories.isNotEmpty()) {
val conditions = categories.mapIndexed { index, cat ->
val paramName = "stockCategory_$index"
args[paramName] = cat
"it.type = :$paramName"
}
"AND (${conditions.joinToString(" OR ")})"
} else {
""
}
} else {
""
}
// 移除 stockSubCategory 过滤(不需要)
val itemCodeSql = buildMultiValueLikeClause(itemCode, "it.code", "itemCode", args)
val yearSql = if (!year.isNullOrBlank()) {
args["year"] = year
"AND YEAR(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) = :year"
} else {
""
}
val lastOutDateStartSql = if (!lastOutDateStart.isNullOrBlank()) {
val formattedDate = lastOutDateStart.replace("/", "-")
args["lastOutDateStart"] = formattedDate
"AND DATE(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) >= DATE(:lastOutDateStart)"
} else ""
val lastOutDateEndSql = if (!lastOutDateEnd.isNullOrBlank()) {
val formattedDate = lastOutDateEnd.replace("/", "-")
args["lastOutDateEnd"] = formattedDate
"AND DATE(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) <= DATE(:lastOutDateEnd)"
} else ""

val handlerSql = buildMultiValueExactClause(
handler,
"COALESCE(picker_user.name, modified_user.name, '')",
"handler",
args
)

val sql = """
SELECT
IFNULL(DATE_FORMAT(
IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate),
'%Y-%m-%d'
), '') AS deliveryDate,
IFNULL(it.code, '') AS itemNo,
IFNULL(it.name, '') AS itemName,
IFNULL(uc.udfudesc, '') AS unitOfMeasure,
IFNULL(dpor.deliveryNoteCode, '') AS dnNo,
CAST(IFNULL(sp.id, 0) AS CHAR) AS customerId,
IFNULL(sp.name, '') AS customerName,
FORMAT(
ROUND(SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code), 0), 0
) AS qtyNumeric,
FORMAT(ROUND(IFNULL(sol.qty, 0), 0), 0) AS qty,
'' AS truckNo,
'' AS driver,
IFNULL(do.code, '') AS deliveryOrderNo,
IFNULL(po.code, '') AS fgPickOrderNo,
IFNULL(po.code, '') AS stockReqNo,
IFNULL(il.lotNo, '') AS lotNo,
IFNULL(DATE_FORMAT(il.expiryDate, '%Y-%m-%d'), '') AS expiryDate,
FORMAT(ROUND(IFNULL(sol.qty, 0), 0), 0) AS stockOutQty,
COALESCE(
picker_user.name,
modified_user.name,
''
) AS handler,
COALESCE(
picker_user.name,
modified_user.name,
''
) AS pickedBy,
GROUP_CONCAT(DISTINCT wh.code ORDER BY wh.code SEPARATOR ', ') AS storeLocation,
'' AS pickRemark,
FORMAT(
ROUND(SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code), 0), 0
) AS totalStockOutQty,
0 AS stockSubCategory
FROM do_pick_order_line_record dpolr
LEFT JOIN do_pick_order_record dpor
ON dpolr.record_id = dpor.id
AND dpor.deleted = 0
AND dpor.ticket_status = 'completed'
INNER JOIN delivery_order do
ON dpolr.do_order_id = do.id
AND do.deleted = 0
LEFT JOIN shop sp
ON do.shopId = sp.id
AND sp.deleted = 0
LEFT JOIN delivery_order_line dol
ON do.id = dol.deliveryOrderId
AND dol.deleted = 0
LEFT JOIN items it
ON dol.itemId = it.id
AND it.deleted = 0
LEFT JOIN item_uom iu
ON it.id = iu.itemId
AND iu.stockUnit = 1
LEFT JOIN uom_conversion uc
ON iu.uomId = uc.id
LEFT JOIN pick_order_line pol
ON dpolr.pick_order_id = pol.poId
AND pol.itemId = it.id
AND pol.deleted = 0
LEFT JOIN pick_order po
ON pol.poId = po.id
AND po.deleted = 0
LEFT JOIN stock_out_line sol
ON pol.id = sol.pickOrderLineId
AND sol.itemId = it.id
AND sol.deleted = 0
LEFT JOIN stock_out so
ON sol.stockOutId = so.id
AND so.deleted = 0
AND so.type = 'do'
LEFT JOIN inventory_lot_line ill
ON sol.inventoryLotLineId = ill.id
AND ill.deleted = 0
LEFT JOIN inventory_lot il
ON ill.inventoryLotId = il.id
AND il.deleted = 0
LEFT JOIN warehouse wh
ON ill.warehouseId = wh.id
AND wh.deleted = 0
LEFT JOIN user picker_user
ON sol.handled_by = picker_user.id
AND picker_user.deleted = 0
LEFT JOIN user modified_user
ON sol.modifiedBy = modified_user.staffNo
AND modified_user.deleted = 0
AND sol.handled_by IS NULL
WHERE
dpolr.deleted = 0
$stockCategorySql
$itemCodeSql
$yearSql
$lastOutDateStartSql
$lastOutDateEndSql
$handlerSql
GROUP BY
sol.id,
dpor.RequiredDeliveryDate,
do.estimatedArrivalDate,
it.code,
it.name,
uc.udfudesc,
dpor.deliveryNoteCode,
sp.id,
sp.name,
sol.qty,
picker_user.name,
modified_user.name,
po.code,
do.code,
il.lotNo,
il.expiryDate
ORDER BY
it.code,
deliveryDate,
il.lotNo
""".trimIndent()

val result = jdbcDao.queryForList(sql, args)
// 打印查询结果
println("=== Query Result (Total: ${result.size} rows) ===")
result.take(50).forEachIndexed { index, row ->
println("Row $index:")
println(" deliveryDate: ${row["deliveryDate"]}")
println(" itemNo: ${row["itemNo"]}")
println(" itemName: ${row["itemName"]}")
println(" qty: ${row["qty"]}")
println(" qtyNumeric: ${row["qtyNumeric"]}")
println(" deliveryOrderNo: ${row["deliveryOrderNo"]}")
println(" dnNo: ${row["dnNo"]}")
println(" fgPickOrderNo: ${row["fgPickOrderNo"]}")
println(" pickedBy: ${row["pickedBy"]}")
println(" storeLocation: ${row["storeLocation"]}")
println(" ---")
}
if (result.size > 50) {
println("... (showing first 50 rows, total ${result.size} rows)")
}

return result
}
/**
* Helper function to build SQL clause for comma-separated values.
* Supports multiple values like "val1, val2, val3" and generates OR conditions with LIKE.


+ 74
- 0
src/main/java/com/ffii/fpsms/modules/report/web/FGStockOutTraceabilityReportController.kt Näytä tiedosto

@@ -0,0 +1,74 @@
package com.ffii.fpsms.modules.report.web

import com.ffii.fpsms.modules.report.service.FGStockOutTraceabilityReportService
import com.ffii.fpsms.modules.report.service.ReportService
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter

@RestController
@RequestMapping("/report")
class FGStockOutTraceabilityReportController(
private val reportService: ReportService,
private val fgStockOutTraceabilityReportService: FGStockOutTraceabilityReportService,
) {
@GetMapping("/fg-stock-out-traceability-handlers")
fun getFGStockOutTraceabilityHandlers(): List<String> =
fgStockOutTraceabilityReportService.getDistinctHandlersForFGStockOutTraceability()

@GetMapping("/print-fg-stock-out-traceability")
fun generateFGStockOutTraceabilityReport(
@RequestParam(required = false) stockCategory: String?,
@RequestParam(required = false) stockSubCategory: String?,
@RequestParam(required = false) itemCode: String?,
@RequestParam(required = false) year: String?,
@RequestParam(required = false) lastOutDateStart: String?,
@RequestParam(required = false) lastOutDateEnd: String?,
@RequestParam(required = false) handler: String?,
): ResponseEntity<ByteArray> {
val parameters = mutableMapOf<String, Any>()

parameters["stockCategory"] = stockCategory ?: "All"
parameters["stockSubCategory"] = stockSubCategory ?: "All"
parameters["itemNo"] = itemCode ?: "All"
parameters["year"] = year ?: LocalDate.now().year.toString()
parameters["reportDate"] = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
parameters["reportTime"] = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
parameters["lastOutDateStart"] = lastOutDateStart ?: ""
parameters["lastOutDateEnd"] = lastOutDateEnd ?: ""
parameters["deliveryPeriodStart"] = ""
parameters["deliveryPeriodEnd"] = ""

val dbData = fgStockOutTraceabilityReportService.searchFGStockOutTraceabilityReport(
stockCategory,
stockSubCategory,
itemCode,
year,
lastOutDateStart,
lastOutDateEnd,
handler,
)

val pdfBytes = reportService.createPdfResponse(
"/jasper/FGStockOutTraceabilityReport.jrxml",
parameters,
dbData,
)

val headers = HttpHeaders().apply {
contentType = MediaType.APPLICATION_PDF
setContentDispositionFormData("attachment", "FGStockOutTraceabilityReport.pdf")
set("filename", "FGStockOutTraceabilityReport.pdf")
}

return ResponseEntity(pdfBytes, headers, HttpStatus.OK)
}
}

+ 0
- 52
src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt Näytä tiedosto

@@ -165,58 +165,6 @@ class ReportController(
return ResponseEntity(pdfBytes, headers, HttpStatus.OK)
}
@GetMapping("/fg-stock-out-traceability-handlers")
fun getFGStockOutTraceabilityHandlers(): List<String> =
reportService.getDistinctHandlersForFGStockOutTraceability()

@GetMapping("/print-fg-stock-out-traceability")
fun generateFGStockOutTraceabilityReport(
@RequestParam(required = false) stockCategory: String?,
@RequestParam(required = false) stockSubCategory: String?,
@RequestParam(required = false) itemCode: String?,
@RequestParam(required = false) year: String?,
@RequestParam(required = false) lastOutDateStart: String?,
@RequestParam(required = false) lastOutDateEnd: String?,
@RequestParam(required = false) handler: String?
): ResponseEntity<ByteArray> {
val parameters = mutableMapOf<String, Any>()
// Set report header parameters
parameters["stockCategory"] = stockCategory ?: "All"
parameters["stockSubCategory"] = stockSubCategory ?: "All"
parameters["itemNo"] = itemCode ?: "All"
parameters["year"] = year ?: LocalDate.now().year.toString()
parameters["reportDate"] = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
parameters["reportTime"] = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
parameters["lastOutDateStart"] = lastOutDateStart ?: ""
parameters["lastOutDateEnd"] = lastOutDateEnd ?: ""
parameters["deliveryPeriodStart"] = ""
parameters["deliveryPeriodEnd"] = ""
val dbData = reportService.searchFGStockOutTraceabilityReport(
stockCategory,
stockSubCategory,
itemCode,
year,
lastOutDateStart,
lastOutDateEnd,
handler
)
val pdfBytes = reportService.createPdfResponse(
"/jasper/FGStockOutTraceabilityReport.jrxml",
parameters,
dbData
)
val headers = HttpHeaders().apply {
contentType = MediaType.APPLICATION_PDF
setContentDispositionFormData("attachment", "FGStockOutTraceabilityReport.pdf")
set("filename", "FGStockOutTraceabilityReport.pdf")
}
return ResponseEntity(pdfBytes, headers, HttpStatus.OK)
}
@GetMapping("/print-stock-balance")
fun generateStockBalanceReport(
@RequestParam(required = false) stockCategory: String?,


Ladataan…
Peruuta
Tallenna