Browse Source

stock take update

production
CANCERYS\kw093 3 weeks ago
parent
commit
2383b62ad0
5 changed files with 445 additions and 28 deletions
  1. +5
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/models/SaveWarehouseRequest.kt
  2. +5
    -1
      src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt
  3. +303
    -8
      src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt
  4. +78
    -18
      src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt
  5. +54
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedgerRepository.kt

+ 5
- 1
src/main/java/com/ffii/fpsms/modules/master/web/models/SaveWarehouseRequest.kt View File

@@ -41,7 +41,11 @@ data class NewWarehouseRequest(
data class StockTakeSectionInfo( data class StockTakeSectionInfo(
val stockTakeSection: String, val stockTakeSection: String,
val stockTakeSectionDescription: String?, val stockTakeSectionDescription: String?,
val warehouseCount: Long
val warehouseCount: Long,
/** 該盤點區域所屬樓層/店別(取自倉庫 `store_id`,多筆時取第一筆非空) */
val storeId: String? = null,
/** 倉庫 `area`(多筆時取第一筆非空),對應列表卡片上的區域欄位 */
val warehouseArea: String? = null,
) )






+ 5
- 1
src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt View File

@@ -279,7 +279,11 @@ return result
SELECT SELECT
CAST(st.stockTakeRoundId AS CHAR) AS value, CAST(st.stockTakeRoundId AS CHAR) AS value,
CONCAT( CONCAT(
CAST(st.stockTakeRoundId AS CHAR),
CASE
WHEN NULLIF(TRIM(MAX(st.stockTakeRoundName)), '') IS NULL
THEN CONCAT('盤點輪次', CAST(st.stockTakeRoundId AS CHAR))
ELSE TRIM(MAX(st.stockTakeRoundName))
END,
' — ', ' — ',
DATE_FORMAT(MIN(st.planStart), '%Y-%m-%d') DATE_FORMAT(MIN(st.planStart), '%Y-%m-%d')
) AS label ) AS label


+ 303
- 8
src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt View File

@@ -565,16 +565,311 @@ ORDER BY
return jdbcDao.queryForList(sql, args) return jdbcDao.queryForList(sql, args)
} }


/**
* V2 Overall:每個 (lotId, warehouseId) 取 **status=completed** 且 **stockTakeRoundId 最大** 之一筆盤點紀錄
* (同輪多筆時取 id 較大者)。期初/累計區間依該筆所屬輪次之 MIN~MAX(`date`)。
* @param limitedToRoundIds null=全系統所有輪次;非 null=僅在指定輪次內取最新已完成
*/
fun searchStockTakeVarianceReportV2Overall(
itemCode: String?,
storeId: String?,
limitedToRoundIds: List<Long>? = null,
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()

val roundIdsFilterSql = if (limitedToRoundIds.isNullOrEmpty()) {
""
} else {
val inClause = limitedToRoundIds.mapIndexed { index, id ->
val key = "overallRoundId_$index"
args[key] = id
":$key"
}.joinToString(", ")
"AND s.stockTakeRoundId IN ($inClause)"
}

val storeIdSql = run {
val normalized = storeId?.trim()
if (normalized.isNullOrBlank() || normalized.equals("all", ignoreCase = true)) {
""
} else {
args["storeId"] = normalized
"""
AND REPLACE(COALESCE(wh.store_id, ''), '/', '') = REPLACE(:storeId, '/', '')
""".trimIndent()
}
}
val itemCodeSql = buildMultiValueLikeClause(
itemCode,
"it.code",
"itemCode",
args
)

val sql = """
WITH round_bounds AS (
SELECT
s.stockTakeRoundId,
COALESCE(MIN(s.date), CURRENT_DATE) AS fromDate,
COALESCE(MAX(s.date), CURRENT_DATE) AS toDate
FROM stocktakerecord s
WHERE s.deleted = 0
$roundIdsFilterSql
GROUP BY s.stockTakeRoundId
),
latest_str AS (
SELECT
str.lotId,
str.warehouseId,
str.stockTakeRoundId,
str.bookQty,
str.varianceQty,
str.approverStockTakeQty,
str.date AS strDate,
str.id,
str.approverTime,
str.status AS stockTakeRecordStatus
FROM stocktakerecord str
INNER JOIN (
SELECT
s.lotId,
s.warehouseId,
MAX(s.stockTakeRoundId) AS maxRound
FROM stocktakerecord s
WHERE s.deleted = 0
AND s.status = 'completed'
$roundIdsFilterSql
GROUP BY s.lotId, s.warehouseId
) mx ON mx.lotId = str.lotId
AND mx.warehouseId = str.warehouseId
AND mx.maxRound = str.stockTakeRoundId
WHERE str.deleted = 0
AND str.status = 'completed'
AND NOT EXISTS (
SELECT 1
FROM stocktakerecord str2
WHERE str2.deleted = 0
AND str2.status = 'completed'
AND str2.lotId = str.lotId
AND str2.warehouseId = str.warehouseId
AND str2.stockTakeRoundId = str.stockTakeRoundId
AND str2.id > str.id
)
),
line_bounds AS (
SELECT
ill.id AS inventoryLotLineId,
ls.lotId,
ls.warehouseId,
rb.fromDate,
rb.toDate,
ls.bookQty,
ls.varianceQty,
ls.approverStockTakeQty,
ls.strDate,
ls.approverTime,
ls.stockTakeRecordStatus
FROM latest_str ls
INNER JOIN round_bounds rb ON rb.stockTakeRoundId = ls.stockTakeRoundId
INNER JOIN inventory_lot il ON ls.lotId = il.id AND il.deleted = 0
INNER JOIN inventory_lot_line ill ON ill.inventoryLotId = il.id
AND ill.warehouseId = ls.warehouseId
AND ill.deleted = 0
),
in_agg AS (
SELECT
lb.inventoryLotLineId,
SUM(CASE WHEN DATE(sil.receiptDate) < lb.fromDate THEN
CASE WHEN sil.purchaseOrderLineId IS NOT NULL
THEN COALESCE(sil.acceptedQty, 0)
WHEN iu_purchase.id IS NOT NULL AND iu_stock.id IS NOT NULL
THEN COALESCE(sil.acceptedQty, 0) * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu_stock.ratioN / NULLIF(iu_stock.ratioD, 0))
ELSE COALESCE(sil.acceptedQty, 0)
END
ELSE 0 END) AS inBefore,
SUM(CASE WHEN DATE(sil.receiptDate) BETWEEN lb.fromDate AND lb.toDate THEN
CASE WHEN sil.purchaseOrderLineId IS NOT NULL
THEN COALESCE(sil.acceptedQty, 0)
WHEN iu_purchase.id IS NOT NULL AND iu_stock.id IS NOT NULL
THEN COALESCE(sil.acceptedQty, 0) * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu_stock.ratioN / NULLIF(iu_stock.ratioD, 0))
ELSE COALESCE(sil.acceptedQty, 0)
END
ELSE 0 END) AS inDuring,
MAX(CASE WHEN sil.receiptDate IS NOT NULL THEN DATE(sil.receiptDate) END) AS lastInDate
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
INNER JOIN items it ON il.itemId = it.id AND it.deleted = 0
LEFT JOIN stock_in_line sil
ON sil.inventoryLotLineId = ill.id
AND sil.deleted = 0
AND sil.status = 'completed'
LEFT JOIN item_uom iu_purchase
ON it.id = iu_purchase.itemId
AND iu_purchase.purchaseUnit = 1
AND iu_purchase.deleted = 0
LEFT JOIN item_uom iu_stock
ON it.id = iu_stock.itemId
AND iu_stock.stockUnit = 1
AND iu_stock.deleted = 0
WHERE ill.deleted = 0
GROUP BY lb.inventoryLotLineId
),
out_agg AS (
SELECT
lb.inventoryLotLineId,
SUM(CASE WHEN DATE(sol.endTime) < lb.fromDate THEN COALESCE(sol.qty, 0) ELSE 0 END) AS outBefore,
SUM(CASE WHEN DATE(sol.endTime) BETWEEN lb.fromDate AND lb.toDate THEN COALESCE(sol.qty, 0) ELSE 0 END) AS outDuring,
MAX(CASE WHEN sol.endTime IS NOT NULL THEN DATE(sol.endTime) END) AS lastOutDate
FROM line_bounds lb
INNER JOIN inventory_lot_line ill ON ill.id = lb.inventoryLotLineId
LEFT JOIN stock_out_line sol
ON sol.inventoryLotLineId = ill.id
AND sol.deleted = 0
AND sol.status = 'completed'
WHERE ill.deleted = 0
GROUP BY lb.inventoryLotLineId
),
in_out AS (
SELECT
i.inventoryLotLineId,
COALESCE(i.inBefore, 0) AS inBefore,
COALESCE(o.outBefore, 0) AS outBefore,
COALESCE(i.inDuring, 0) AS inDuring,
COALESCE(o.outDuring, 0) AS outDuring,
i.lastInDate,
o.lastOutDate
FROM in_agg i
LEFT JOIN out_agg o ON o.inventoryLotLineId = i.inventoryLotLineId
),
data AS (
SELECT
it.type AS stockSubCategory,
it.code AS itemNo,
it.name AS itemName,
uc.udfudesc AS unitOfMeasure,

il.lotNo AS lotNo,
COALESCE(DATE_FORMAT(il.expiryDate, '%Y-%m-%d'), '') AS expiryDate,
wh.code AS storeLocation,

(COALESCE(io.inBefore, 0) - COALESCE(io.outBefore, 0)) AS openingQty,
COALESCE(io.inDuring, 0) AS inQty,
COALESCE(io.outDuring, 0) AS outQty,
((COALESCE(io.inBefore, 0) - COALESCE(io.outBefore, 0)) + COALESCE(io.inDuring, 0) - COALESCE(io.outDuring, 0)) AS currentQty,

io.lastInDate AS lastInDateRaw,
io.lastOutDate AS lastOutDateRaw,

lb.bookQty AS stkBookQty,
lb.approverStockTakeQty AS stkApproverQty,
lb.varianceQty AS stkVarianceQty,
lb.strDate AS stockTakeDateRaw,
lb.approverTime AS approvalDateTimeRaw,
lb.stockTakeRecordStatus AS stockTakeRecordStatus
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
INNER JOIN items it ON il.itemId = it.id AND it.deleted = 0
INNER JOIN warehouse wh ON wh.id = lb.warehouseId AND wh.deleted = 0

LEFT JOIN item_uom iu
ON it.id = iu.itemId
AND iu.stockUnit = 1
AND iu.deleted = 0
LEFT JOIN uom_conversion uc
ON iu.uomId = uc.id

LEFT JOIN in_out io
ON io.inventoryLotLineId = ill.id

WHERE 1=1
$itemCodeSql
$storeIdSql
)

SELECT
stockSubCategory,
itemNo,
itemName,
unitOfMeasure,
lotNo,
expiryDate,
storeLocation,

CASE WHEN COALESCE(openingQty, 0) < 0 THEN CONCAT('(', FORMAT(-openingQty, 0), ')') ELSE FORMAT(COALESCE(openingQty, 0), 0) END AS openingBalance,
CASE WHEN COALESCE(inQty, 0) < 0 THEN CONCAT('(', FORMAT(-inQty, 0), ')') ELSE FORMAT(COALESCE(inQty, 0), 0) END AS cumStockIn,
CASE WHEN COALESCE(outQty, 0) < 0 THEN CONCAT('(', FORMAT(-outQty, 0), ')') ELSE FORMAT(COALESCE(outQty, 0), 0) END AS cumStockOut,
CASE WHEN COALESCE(stkBookQty, 0) < 0 THEN CONCAT('(', FORMAT(-stkBookQty, 0), ')') ELSE FORMAT(COALESCE(stkBookQty, 0), 0) END AS currentBookBalance,

COALESCE(DATE_FORMAT(lastInDateRaw, '%Y-%m-%d'), '') AS lastInDate,
COALESCE(DATE_FORMAT(lastOutDateRaw, '%Y-%m-%d'), '') AS lastOutDate,
COALESCE(
DATE_FORMAT(approvalDateTimeRaw, '%Y-%m-%d %H:%i:%s'),
COALESCE(DATE_FORMAT(stockTakeDateRaw, '%Y-%m-%d'), '')
) AS stockTakeDate,

CASE
WHEN stkApproverQty IS NULL THEN '0'
WHEN COALESCE(stkApproverQty, 0) < 0 THEN CONCAT('(', FORMAT(-stkApproverQty, 0), ')')
ELSE FORMAT(COALESCE(stkApproverQty, 0), 0)
END AS stockTakeQty,

CASE
WHEN COALESCE(stockTakeRecordStatus, '') <> 'completed' THEN '0'
WHEN stkVarianceQty IS NULL THEN '0'
WHEN COALESCE(stkVarianceQty, 0) < 0 THEN CONCAT('(', FORMAT(-stkVarianceQty, 0), ')')
ELSE FORMAT(COALESCE(stkVarianceQty, 0), 0)
END AS variance,

CASE
WHEN COALESCE(stockTakeRecordStatus, '') <> 'completed' THEN '0%'
WHEN stkVarianceQty IS NULL THEN '0%'
WHEN COALESCE(stkBookQty, 0) = 0 THEN '0%'
WHEN (COALESCE(stkVarianceQty, 0) / stkBookQty) * 100 < 0 THEN CONCAT('(', FORMAT(-(COALESCE(stkVarianceQty, 0) / stkBookQty) * 100, 0), '%)')
ELSE CONCAT(FORMAT((COALESCE(stkVarianceQty, 0) / stkBookQty) * 100, 0), '%')
END AS variancePercentage,

CASE WHEN SUM(COALESCE(openingQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(openingQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(openingQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalOpeningBalance,
CASE WHEN SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCumStockIn,
CASE WHEN SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCumStockOut,
CASE WHEN SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCurrentBalance,
CASE WHEN SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalStockTakeQty

FROM data
ORDER BY
itemNo,
lotNo,
storeLocation
""".trimIndent()

return jdbcDao.queryForList(sql, args)
}

/** Overall 模式報表副標題(全系統輪次,舊 API 相容) */
fun getStockTakeVarianceOverallCaption(): String =
"全輪次(各批號/倉別最新已完成盤點)"

/** 多選輪次 Overall 模式報表副標題 */
fun getStockTakeVarianceMultiRoundCaption(stockTakeRoundIds: List<Long>): String {
if (stockTakeRoundIds.isEmpty()) return getStockTakeVarianceOverallCaption()
val parts = stockTakeRoundIds.map { getStockTakeRoundCaption(it) }
return "已選輪次(各批號/倉別最新已完成盤點):${parts.joinToString(";")}"
}

/** 報表表頭:盤點輪次說明(與 /report/stock-take-rounds 選項格式一致) */ /** 報表表頭:盤點輪次說明(與 /report/stock-take-rounds 選項格式一致) */
fun getStockTakeRoundCaption(stockTakeRoundId: Long): String { fun getStockTakeRoundCaption(stockTakeRoundId: Long): String {
val sql = """ val sql = """
SELECT CONCAT(
'Round ',
CAST(st.stockTakeRoundId AS CHAR),
' (',
DATE_FORMAT(MIN(st.planStart), '%Y-%m-%d'),
')'
) AS cap
SELECT
CONCAT(
CASE
WHEN NULLIF(TRIM(MAX(st.stockTakeRoundName)), '') IS NULL
THEN CONCAT('盤點輪次', CAST(st.stockTakeRoundId AS CHAR))
ELSE TRIM(MAX(st.stockTakeRoundName))
END,
' — ',
DATE_FORMAT(MIN(st.planStart), '%Y-%m-%d')
) AS cap
FROM stock_take st FROM stock_take st
WHERE st.deleted = 0 WHERE st.deleted = 0
AND st.stockTakeRoundId = :stockTakeRoundId AND st.stockTakeRoundId = :stockTakeRoundId
@@ -585,7 +880,7 @@ ORDER BY
mapOf("stockTakeRoundId" to stockTakeRoundId) mapOf("stockTakeRoundId" to stockTakeRoundId)
).firstOrNull() ).firstOrNull()
val cap = row?.get("cap") as? String val cap = row?.get("cap") as? String
return if (!cap.isNullOrBlank()) cap else "Round $stockTakeRoundId"
return if (!cap.isNullOrBlank()) cap else "盤點輪次$stockTakeRoundId"
} }


/** LIKE 多值工具方法 */ /** LIKE 多值工具方法 */


+ 78
- 18
src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt View File

@@ -141,11 +141,14 @@ class StockTakeVarianceReportController(
*/ */
@GetMapping("/print-stock-take-variance-v2") @GetMapping("/print-stock-take-variance-v2")
fun generateStockTakeVarianceReportV2( fun generateStockTakeVarianceReportV2(
@RequestParam stockTakeRoundId: Long,
@RequestParam(required = false) overall: Boolean?,
@RequestParam(required = false) stockTakeRoundId: String?,
@RequestParam(required = false) itemCode: String?, @RequestParam(required = false) itemCode: String?,
@RequestParam(required = false, name = "store_id") storeId: String?, @RequestParam(required = false, name = "store_id") storeId: String?,
@RequestParam(required = false) status: String?, @RequestParam(required = false) status: String?,
): ResponseEntity<ByteArray> { ): ResponseEntity<ByteArray> {
val rep012 = resolveRep012ReportData(overall, stockTakeRoundId, itemCode, storeId, status)

val parameters = mutableMapOf<String, Any>() val parameters = mutableMapOf<String, Any>()


parameters["stockCategory"] = "All" parameters["stockCategory"] = "All"
@@ -164,16 +167,10 @@ class StockTakeVarianceReportController(
parameters["lastInDateEnd"] = "" parameters["lastInDateEnd"] = ""
parameters["lastOutDateEnd"] = "" parameters["lastOutDateEnd"] = ""


parameters["stockTakeFilterCaption"] =
stockTakeVarianceReportService.getStockTakeRoundCaption(stockTakeRoundId)
parameters["stockTakeConditionLabel"] = "盤點輪次:"
parameters["stockTakeFilterCaption"] = rep012.caption
parameters["stockTakeConditionLabel"] = rep012.conditionLabel


val dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2(
stockTakeRoundId = stockTakeRoundId,
itemCode = itemCode,
storeId = storeId,
status = status,
)
val dbData = rep012.dbData
val stockTakeDateDisplay = dbData val stockTakeDateDisplay = dbData
.mapNotNull { it["stockTakeDate"] as? String } .mapNotNull { it["stockTakeDate"] as? String }
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
@@ -198,18 +195,15 @@ class StockTakeVarianceReportController(


@GetMapping("/print-stock-take-variance-v2-excel") @GetMapping("/print-stock-take-variance-v2-excel")
fun exportStockTakeVarianceReportV2Excel( fun exportStockTakeVarianceReportV2Excel(
@RequestParam stockTakeRoundId: Long,
@RequestParam(required = false) overall: Boolean?,
@RequestParam(required = false) stockTakeRoundId: String?,
@RequestParam(required = false) itemCode: String?, @RequestParam(required = false) itemCode: String?,
@RequestParam(required = false, name = "store_id") storeId: String?, @RequestParam(required = false, name = "store_id") storeId: String?,
@RequestParam(required = false) status: String?, @RequestParam(required = false) status: String?,
): ResponseEntity<ByteArray> { ): ResponseEntity<ByteArray> {
val cap = stockTakeVarianceReportService.getStockTakeRoundCaption(stockTakeRoundId)
val dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2(
stockTakeRoundId = stockTakeRoundId,
itemCode = itemCode,
storeId = storeId,
status = status,
)
val rep012 = resolveRep012ReportData(overall, stockTakeRoundId, itemCode, storeId, status)
val cap = rep012.caption
val dbData = rep012.dbData


val excelBytes = createStockTakeVarianceExcel( val excelBytes = createStockTakeVarianceExcel(
dbData = dbData, dbData = dbData,
@@ -579,5 +573,71 @@ class StockTakeVarianceReportController(
val abs = kotlin.math.abs(v).toLong() val abs = kotlin.math.abs(v).toLong()
return "%,d".format(abs) return "%,d".format(abs)
} }

private data class Rep012ReportData(
val caption: String,
val conditionLabel: String,
val dbData: List<Map<String, Any>>,
)

private fun parseStockTakeRoundIds(raw: String?): List<Long> =
raw
?.split(",")
?.mapNotNull { it.trim().toLongOrNull() }
?.distinct()
?: emptyList()

/**
* rep-012:單輪=單輪查詢;多輪=已選輪次內各批號/倉最新已完成(status 固定 completed)。
* overall=true 且未傳輪次時保留舊「全系統輪次」行為。
*/
private fun resolveRep012ReportData(
overall: Boolean?,
stockTakeRoundId: String?,
itemCode: String?,
storeId: String?,
status: String?,
): Rep012ReportData {
val roundIds = parseStockTakeRoundIds(stockTakeRoundId)
val useLegacyOverall = overall == true && roundIds.isEmpty()
require(useLegacyOverall || roundIds.isNotEmpty()) {
"stockTakeRoundId is required"
}

if (useLegacyOverall) {
return Rep012ReportData(
caption = stockTakeVarianceReportService.getStockTakeVarianceOverallCaption(),
conditionLabel = "盤點條件:",
dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2Overall(
itemCode = itemCode,
storeId = storeId,
),
)
}

if (roundIds.size == 1) {
val id = roundIds.first()
return Rep012ReportData(
caption = stockTakeVarianceReportService.getStockTakeRoundCaption(id),
conditionLabel = "盤點輪次:",
dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2(
stockTakeRoundId = id,
itemCode = itemCode,
storeId = storeId,
status = status,
),
)
}

return Rep012ReportData(
caption = stockTakeVarianceReportService.getStockTakeVarianceMultiRoundCaption(roundIds),
conditionLabel = "盤點條件:",
dbData = stockTakeVarianceReportService.searchStockTakeVarianceReportV2Overall(
itemCode = itemCode,
storeId = storeId,
limitedToRoundIds = roundIds,
),
)
}
} }



+ 54
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockLedgerRepository.kt View File

@@ -6,6 +6,7 @@ import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.Query import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param import org.springframework.data.repository.query.Param
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
@Repository @Repository
interface StockLedgerRepository: AbstractRepository<StockLedger, Long> { interface StockLedgerRepository: AbstractRepository<StockLedger, Long> {
@@ -59,4 +60,57 @@ interface StockLedgerRepository: AbstractRepository<StockLedger, Long> {
""") """)
fun findLatestByItemId(@Param("itemId") itemId: Long): List<StockLedger> fun findLatestByItemId(@Param("itemId") itemId: Long): List<StockLedger>
fun findFirstByItemIdAndDeletedFalseOrderByDateDescIdDesc(itemId: Long): StockLedger? fun findFirstByItemIdAndDeletedFalseOrderByDateDescIdDesc(itemId: Long): StockLedger?

@Query("""
SELECT sl FROM StockLedger sl
LEFT JOIN sl.stockOutLine sol
LEFT JOIN sol.inventoryLotLine ill
LEFT JOIN ill.inventoryLot il
LEFT JOIN sl.inventory inv
LEFT JOIN inv.item i
WHERE sl.deleted = false
AND sl.type = :type
AND sl.outQty IS NOT NULL AND sl.outQty > 0
AND (:itemCode IS NULL OR sl.itemCode LIKE CONCAT('%', :itemCode, '%'))
AND (:itemName IS NULL OR i.name LIKE CONCAT('%', :itemName, '%'))
AND (:lotNo IS NULL OR il.lotNo LIKE CONCAT('%', :lotNo, '%'))
AND (:startDate IS NULL OR sl.date >= :startDate)
AND (:endDateExclusive IS NULL OR sl.date < :endDateExclusive)
ORDER BY sl.date DESC, sl.id DESC
""")
fun findStockIssueHandleRecords(
@Param("type") type: String,
@Param("itemCode") itemCode: String?,
@Param("itemName") itemName: String?,
@Param("lotNo") lotNo: String?,
@Param("startDate") startDate: LocalDate?,
@Param("endDateExclusive") endDateExclusive: LocalDate?,
pageable: Pageable,
): Page<StockLedger>

@Query("""
SELECT sl FROM StockLedger sl
LEFT JOIN sl.stockOutLine sol
LEFT JOIN sol.inventoryLotLine ill
LEFT JOIN ill.inventoryLot il
LEFT JOIN sl.inventory inv
LEFT JOIN inv.item i
WHERE sl.deleted = false
AND sl.type = 'Expiry'
AND sl.outQty IS NOT NULL AND sl.outQty > 0
AND (:itemCode IS NULL OR sl.itemCode LIKE CONCAT('%', :itemCode, '%'))
AND (:itemName IS NULL OR i.name LIKE CONCAT('%', :itemName, '%'))
AND (:lotNo IS NULL OR il.lotNo LIKE CONCAT('%', :lotNo, '%'))
AND (:startDate IS NULL OR il.expiryDate >= :startDate)
AND (:endDateExclusive IS NULL OR il.expiryDate < :endDateExclusive)
ORDER BY sl.date DESC, sl.id DESC
""")
fun findExpiryItemHandleRecords(
@Param("itemCode") itemCode: String?,
@Param("itemName") itemName: String?,
@Param("lotNo") lotNo: String?,
@Param("startDate") startDate: LocalDate?,
@Param("endDateExclusive") endDateExclusive: LocalDate?,
pageable: Pageable,
): Page<StockLedger>
} }

Loading…
Cancel
Save