Bläddra i källkod

MaterialStockOutTraceabilityReport Update

master
B.E.N.S.O.N 1 dag sedan
förälder
incheckning
07cc0e8389
4 ändrade filer med 311 tillägg och 239 borttagningar
  1. +235
    -0
      src/main/java/com/ffii/fpsms/modules/report/service/MaterialStockOutTraceabilityReportService.kt
  2. +0
    -187
      src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt
  3. +76
    -0
      src/main/java/com/ffii/fpsms/modules/report/web/MaterialStockOutTraceabilityReportController.kt
  4. +0
    -52
      src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt

+ 235
- 0
src/main/java/com/ffii/fpsms/modules/report/service/MaterialStockOutTraceabilityReportService.kt Visa fil

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

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

@Service
class MaterialStockOutTraceabilityReportService(
private val jdbcDao: JdbcDao,
) {
fun getDistinctHandlersForMaterialStockOutTraceability(): List<String> {
val sql = """
SELECT DISTINCT COALESCE(picker_user.name, jpo_handler_user.name, created_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 = 'job'
INNER JOIN pick_order_line pol ON sol.pickOrderLineId = pol.id AND pol.deleted = 0
INNER JOIN pick_order po ON pol.poId = po.id AND po.deleted = 0 AND po.type IN ('jo', 'JOB_ORDER') AND po.joId IS NOT NULL
LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
LEFT JOIN jo_pick_order jpo ON po.id = jpo.pick_order_id AND pol.itemId = jpo.item_id AND jpo.deleted = 0
LEFT JOIN user jpo_handler_user ON jpo.handled_by = jpo_handler_user.id AND jpo_handler_user.deleted = 0
LEFT JOIN user created_user ON sol.createdBy = created_user.username AND created_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 searchMaterialStockOutTraceabilityReport(
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 filtering (items.type)
val stockCategorySql = buildMultiValueExactClause(
stockCategory,
"it.type",
"stockCategory",
args,
)

// Keep as-is (no filter yet)
val stockSubCategorySql = ""

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

// Filter by sol.endTime year
val yearSql = if (!year.isNullOrBlank()) {
args["year"] = year
"AND YEAR(sol.endTime) = :year"
} else ""

val lastOutDateStartSql = if (!lastOutDateStart.isNullOrBlank()) {
val formattedDate = lastOutDateStart.replace("/", "-")
args["lastOutDateStart"] = formattedDate
"AND DATE(sol.endTime) >= DATE(:lastOutDateStart)"
} else ""

val lastOutDateEndSql = if (!lastOutDateEnd.isNullOrBlank()) {
val formattedDate = lastOutDateEnd.replace("/", "-")
args["lastOutDateEnd"] = formattedDate
"AND DATE(sol.endTime) <= DATE(:lastOutDateEnd)"
} else ""

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

val sql = """
SELECT
IFNULL(it.code, '') AS itemNo,
IFNULL(it.name, '') AS itemName,
IFNULL(it.categoryId, 0) AS stockSubCategory,
IFNULL(uc.udfudesc, '') AS unitOfMeasure,
IFNULL(jo.code, '') AS jobOrderNo,
IFNULL(po.consoCode, '') 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,
IFNULL(po.code, '') AS materialPickOrderNo,
COALESCE(
picker_user.name,
jpo_handler_user.name,
created_user.name,
modified_user.name,
''
) AS handler,
COALESCE(wh.code, '') AS storeLocation,
'' AS pickRemark,
FORMAT(
ROUND(SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code), 0), 0
) AS totalStockOutQty
FROM stock_out_line sol
INNER JOIN stock_out so
ON sol.stockOutId = so.id
AND so.deleted = 0
AND so.type = 'job'
INNER JOIN pick_order_line pol
ON sol.pickOrderLineId = pol.id
AND pol.deleted = 0
INNER JOIN pick_order po
ON pol.poId = po.id
AND po.deleted = 0
AND po.type IN ('jo', 'JOB_ORDER')
AND po.joId IS NOT NULL
LEFT JOIN job_order jo
ON po.joId = jo.id
AND jo.deleted = 0
INNER JOIN items it
ON sol.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 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 jo_pick_order jpo
ON po.id = jpo.pick_order_id
AND pol.itemId = jpo.item_id
AND jpo.deleted = 0
LEFT JOIN user jpo_handler_user
ON jpo.handled_by = jpo_handler_user.id
AND jpo_handler_user.deleted = 0
LEFT JOIN user created_user
ON sol.createdBy = created_user.username
AND created_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
AND (sol.inventoryLotLineId IS NULL OR ill.id IS NOT NULL)
$stockCategorySql
$stockSubCategorySql
$itemCodeSql
$yearSql
$lastOutDateStartSql
$lastOutDateEndSql
$handlerSql
ORDER BY
it.code,
il.lotNo,
sol.endTime
""".trimIndent()

val result = jdbcDao.queryForList(sql, args)

println("=== Material Stock Out Traceability (Total: ${result.size} rows) ===")
result.take(50).forEachIndexed { index, row ->
println("Row $index:")
println(" itemNo: ${row["itemNo"]}")
println(" itemName: ${row["itemName"]}")
println(" jobOrderNo: ${row["jobOrderNo"]}")
println(" stockOutQty: ${row["stockOutQty"]}")
println(" totalStockOutQty: ${row["totalStockOutQty"]}")
println(" materialPickOrderNo: ${row["materialPickOrderNo"]}")
println(" handler: ${row["handler"]}")
println(" storeLocation: ${row["storeLocation"]}")
println(" ---")
}

return result
}

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
- 187
src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt Visa fil

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

fun getDistinctHandlersForMaterialStockOutTraceability(): List<String> {
val sql = """
SELECT DISTINCT COALESCE(picker_user.name, jpo_handler_user.name, created_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 = 'job'
INNER JOIN pick_order_line pol ON sol.pickOrderLineId = pol.id AND pol.deleted = 0
INNER JOIN pick_order po ON pol.poId = po.id AND po.deleted = 0 AND po.type IN ('jo', 'JOB_ORDER') AND po.joId IS NOT NULL
LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0
LEFT JOIN jo_pick_order jpo ON po.id = jpo.pick_order_id AND pol.itemId = jpo.item_id AND jpo.deleted = 0
LEFT JOIN user jpo_handler_user ON jpo.handled_by = jpo_handler_user.id AND jpo_handler_user.deleted = 0
LEFT JOIN user created_user ON sol.createdBy = created_user.username AND created_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?,
@@ -506,175 +488,6 @@ return result

return result
}
fun searchMaterialStockOutTraceabilityReport(
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 = buildMultiValueExactClause(
stockCategory,
"it.type",
"stockCategory",
args
)

val stockSubCategorySql = ""

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

// 年份过滤:使用 sol.endTime 的年份
val yearSql = if (!year.isNullOrBlank()) {
args["year"] = year
"AND YEAR(sol.endTime) = :year"
} else {
""
}

val lastOutDateStartSql = if (!lastOutDateStart.isNullOrBlank()) {
val formattedDate = lastOutDateStart.replace("/", "-")
args["lastOutDateStart"] = formattedDate
"AND DATE(sol.endTime) >= DATE(:lastOutDateStart)"
} else {
""
}

val lastOutDateEndSql = if (!lastOutDateEnd.isNullOrBlank()) {
val formattedDate = lastOutDateEnd.replace("/", "-")
args["lastOutDateEnd"] = formattedDate
"AND DATE(sol.endTime) <= DATE(:lastOutDateEnd)"
} else {
""
}

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

val sql = """
SELECT
IFNULL(it.code, '') AS itemNo,
IFNULL(it.name, '') AS itemName,
IFNULL(it.categoryId, 0) AS stockSubCategory,
IFNULL(uc.udfudesc, '') AS unitOfMeasure,
IFNULL(jo.code, '') AS jobOrderNo,
IFNULL(po.consoCode, '') 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,
IFNULL(po.code, '') AS materialPickOrderNo,
COALESCE(
picker_user.name,
jpo_handler_user.name,
created_user.name,
modified_user.name,
''
) AS handler,
COALESCE(wh.code, '') AS storeLocation,
'' AS pickRemark,
FORMAT(
ROUND(SUM(IFNULL(sol.qty, 0)) OVER (PARTITION BY it.code), 0), 0
) AS totalStockOutQty
FROM stock_out_line sol
INNER JOIN stock_out so
ON sol.stockOutId = so.id
AND so.deleted = 0
AND so.type = 'job'
INNER JOIN pick_order_line pol
ON sol.pickOrderLineId = pol.id
AND pol.deleted = 0
INNER JOIN pick_order po
ON pol.poId = po.id
AND po.deleted = 0
AND po.type IN ('jo', 'JOB_ORDER')
AND po.joId IS NOT NULL
LEFT JOIN job_order jo
ON po.joId = jo.id
AND jo.deleted = 0
INNER JOIN items it
ON sol.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 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 jo_pick_order jpo
ON po.id = jpo.pick_order_id
AND pol.itemId = jpo.item_id
AND jpo.deleted = 0
LEFT JOIN user jpo_handler_user
ON jpo.handled_by = jpo_handler_user.id
AND jpo_handler_user.deleted = 0
LEFT JOIN user created_user
ON sol.createdBy = created_user.username
AND created_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
AND (sol.inventoryLotLineId IS NULL OR ill.id IS NOT NULL)
$stockCategorySql
$stockSubCategorySql
$itemCodeSql
$yearSql
$lastOutDateStartSql
$lastOutDateEndSql
$handlerSql
ORDER BY
it.code,
il.lotNo,
sol.endTime
""".trimIndent()
val result = jdbcDao.queryForList(sql, args)

println("=== Material Stock Out Traceability (Total: ${result.size} rows) ===")
result.take(50).forEachIndexed { index, row ->
println("Row $index:")
println(" itemNo: ${row["itemNo"]}")
println(" itemName: ${row["itemName"]}")
println(" jobOrderNo: ${row["jobOrderNo"]}")
println(" stockOutQty: ${row["stockOutQty"]}")
println(" totalStockOutQty: ${row["totalStockOutQty"]}")
println(" materialPickOrderNo: ${row["materialPickOrderNo"]}")
println(" handler: ${row["handler"]}")
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.


+ 76
- 0
src/main/java/com/ffii/fpsms/modules/report/web/MaterialStockOutTraceabilityReportController.kt Visa fil

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

import com.ffii.fpsms.modules.report.service.MaterialStockOutTraceabilityReportService
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 MaterialStockOutTraceabilityReportController(
private val reportService: ReportService,
private val materialStockOutTraceabilityReportService: MaterialStockOutTraceabilityReportService,
) {
@GetMapping("/material-stock-out-traceability-handlers")
fun getMaterialStockOutTraceabilityHandlers(): List<String> =
materialStockOutTraceabilityReportService.getDistinctHandlersForMaterialStockOutTraceability()

@GetMapping("/print-material-stock-out-traceability")
fun generateMaterialStockOutTraceabilityReport(
@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 = materialStockOutTraceabilityReportService.searchMaterialStockOutTraceabilityReport(
stockCategory,
stockSubCategory,
itemCode,
year,
lastOutDateStart,
lastOutDateEnd,
handler,
)

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

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

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


+ 0
- 52
src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt Visa fil

@@ -168,10 +168,6 @@ class ReportController(
fun getFGStockOutTraceabilityHandlers(): List<String> =
reportService.getDistinctHandlersForFGStockOutTraceability()

@GetMapping("/material-stock-out-traceability-handlers")
fun getMaterialStockOutTraceabilityHandlers(): List<String> =
reportService.getDistinctHandlersForMaterialStockOutTraceability()

@GetMapping("/print-fg-stock-out-traceability")
fun generateFGStockOutTraceabilityReport(
@RequestParam(required = false) stockCategory: String?,
@@ -220,54 +216,6 @@ class ReportController(
return ResponseEntity(pdfBytes, headers, HttpStatus.OK)
}
@GetMapping("/print-material-stock-out-traceability")
fun generateMaterialStockOutTraceabilityReport(
@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.searchMaterialStockOutTraceabilityReport(
stockCategory,
stockSubCategory,
itemCode,
year,
lastOutDateStart,
lastOutDateEnd,
handler
)
val pdfBytes = reportService.createPdfResponse(
"/jasper/MaterialStockOutTraceability.jrxml",
parameters,
dbData
)
val headers = HttpHeaders().apply {
contentType = MediaType.APPLICATION_PDF
setContentDispositionFormData("attachment", "MaterialStockOutTraceabilityReport.pdf")
set("filename", "MaterialStockOutTraceabilityReport.pdf")
}
return ResponseEntity(pdfBytes, headers, HttpStatus.OK)
}
@GetMapping("/print-stock-balance")
fun generateStockBalanceReport(
@RequestParam(required = false) stockCategory: String?,


Laddar…
Avbryt
Spara