浏览代码

stockbalancereport

master
父节点
当前提交
33ac59b976
共有 2 个文件被更改,包括 313 次插入12 次删除
  1. +305
    -10
      src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt
  2. +8
    -2
      src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt

+ 305
- 10
src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt 查看文件

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

import org.springframework.stereotype.Service
import net.sf.jasperreports.engine.*
@@ -272,6 +272,246 @@ 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 getStockTakeRoundOptions(): List<Map<String, Any>> {
val sql = """
SELECT
CAST(st.stockTakeRoundId AS CHAR) AS value,
CONCAT(
'Round ',
st.stockTakeRoundId,
' (',
DATE_FORMAT(MIN(st.planStart), '%Y-%m-%d'),
')'
) AS label
FROM stock_take st
WHERE st.deleted = 0
AND st.stockTakeRoundId IS NOT NULL
GROUP BY st.stockTakeRoundId
ORDER BY MIN(st.planStart) DESC
""".trimIndent()

return jdbcDao.queryForList(sql, emptyMap<String, Any>())
}

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.
@@ -791,16 +1031,61 @@ return result
lastInDateEnd: String?,
lastOutDateStart: String?,
lastOutDateEnd: String?,
stockTakeRoundId: Long,
reportPeriodStart: String? = null,
reportPeriodEnd: String? = null
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
val fromDate = reportPeriodStart?.replace("/", "-")?.takeIf { it.isNotBlank() }
?: java.time.LocalDate.now().withDayOfYear(1).toString()
val toDate = reportPeriodEnd?.replace("/", "-")?.takeIf { it.isNotBlank() }
?: java.time.LocalDate.now().toString()
args["fromDate"] = fromDate
args["toDate"] = toDate

fun toLocalDate(value: Any?): java.time.LocalDate? = when (value) {
is java.sql.Timestamp -> value.toLocalDateTime().toLocalDate()
is java.time.LocalDateTime -> value.toLocalDate()
is java.time.LocalDate -> value
else -> null
}

val (resolvedFromDate, resolvedToDate) = run {
// Fallback to existing date-range behavior (year-start -> today) when stock take round can't be resolved.
val fallbackFrom =
reportPeriodStart?.replace("/", "-")?.takeIf { it.isNotBlank() }
?: java.time.LocalDate.now().withDayOfYear(1).toString()
val fallbackTo =
reportPeriodEnd?.replace("/", "-")?.takeIf { it.isNotBlank() }
?: java.time.LocalDate.now().toString()

val currentPlanStartAny = jdbcDao.queryForList(
"""
SELECT MIN(planStart) AS planStart
FROM stock_take
WHERE deleted = 0
AND stockTakeRoundId = :stockTakeRoundId
""".trimIndent(),
mapOf("stockTakeRoundId" to stockTakeRoundId)
).firstOrNull()?.get("planStart")

val currentPlanStartDate = toLocalDate(currentPlanStartAny)
if (currentPlanStartDate == null) {
fallbackFrom to fallbackTo
} else {
val nextPlanStartAny = jdbcDao.queryForList(
"""
SELECT MIN(planStart) AS planStart
FROM stock_take
WHERE deleted = 0
AND planStart > :currentPlanStart
""".trimIndent(),
mapOf("currentPlanStart" to currentPlanStartAny)
).firstOrNull()?.get("planStart")

val nextPlanStartDate = toLocalDate(nextPlanStartAny)
val from = currentPlanStartDate.toString()
val to = nextPlanStartDate?.minusDays(1)?.toString() ?: java.time.LocalDate.now().toString()
from to to
}
}

args["fromDate"] = resolvedFromDate
args["toDate"] = resolvedToDate

val stockCategorySql = buildMultiValueExactClause(stockCategory, "it.type", "stockCategory", args)
val itemCodeSql = buildMultiValueLikeClause(itemCode, "sl.itemCode", "itemCode", args)
@@ -868,10 +1153,18 @@ return result
sl.itemCode,
sl.itemId,
COALESCE(il_in.id, il_out.id) AS lotId,
SUM(CASE WHEN DATE(sl.date) < :fromDate THEN COALESCE(sl.inQty, 0) - COALESCE(sl.outQty, 0) ELSE 0 END) AS openingBalance,
SUM(
CASE
WHEN DATE(sl.date) BETWEEN :fromDate AND :toDate
WHEN DATE(sl.date) <= :fromDate
THEN COALESCE(sl.inQty, 0) - COALESCE(sl.outQty, 0)
ELSE 0
END
) AS openingBalance,
SUM(
CASE
WHEN DATE(sl.date) > :fromDate
AND DATE(sl.date) <= :toDate
AND UPPER(TRIM(COALESCE(sl.type, ''))) <> 'TKE'
AND sil.stockTakeLineId IS NULL
THEN COALESCE(sl.inQty, 0)
ELSE 0
@@ -879,8 +1172,10 @@ return result
) AS cumStockIn,
SUM(
CASE
WHEN DATE(sl.date) BETWEEN :fromDate AND :toDate
WHEN DATE(sl.date) > :fromDate
AND DATE(sl.date) <= :toDate
AND COALESCE(sl.outQty, 0) > 0
AND UPPER(TRIM(COALESCE(sl.type, ''))) <> 'TKE'
AND NOT (
LOWER(TRIM(COALESCE(sl.type, ''))) = 'stocktake'
OR (


+ 8
- 2
src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt 查看文件

@@ -17,6 +17,10 @@ class ReportController(
private val reportService: ReportService,
) {

@GetMapping("/stock-take-rounds")
fun getStockTakeRounds(): List<Map<String, Any>> =
reportService.getStockTakeRoundOptions()

@GetMapping("/print-report1")
fun generateReport1(
@RequestParam fromDate: String,
@@ -175,7 +179,8 @@ class ReportController(
@RequestParam(required = false) lastInDateStart: String?,
@RequestParam(required = false) lastInDateEnd: String?,
@RequestParam(required = false) lastOutDateStart: String?,
@RequestParam(required = false) lastOutDateEnd: String?
@RequestParam(required = false) lastOutDateEnd: String?,
@RequestParam stockTakeRoundId: Long
): ResponseEntity<ByteArray> {
val parameters = mutableMapOf<String, Any>()
parameters["stockCategory"] = stockCategory ?: "All"
@@ -199,7 +204,8 @@ class ReportController(
lastInDateStart,
lastInDateEnd,
lastOutDateStart,
lastOutDateEnd
lastOutDateEnd,
stockTakeRoundId
)

val pdfBytes = reportService.createPdfResponse(


正在加载...
取消
保存