|
|
@@ -68,7 +68,8 @@ open class ChartService( |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Purchase orders: count by status (pending, receiving, completed). |
|
|
* Purchase orders: count by status (pending, receiving, completed). |
|
|
* targetDate: when set, only POs whose orderDate is on that date; when null, all POs. |
|
|
|
|
|
|
|
|
* targetDate: when set, include POs whose estimatedArrivalDate is on that date OR have stock-in |
|
|
|
|
|
* activity (completeDate/receiptDate/created) on that date; when null, all POs. |
|
|
* Optional multi-filters: supplierIds, itemCodes (line itemNo), purchaseOrderNos (po.code). |
|
|
* Optional multi-filters: supplierIds, itemCodes (line itemNo), purchaseOrderNos (po.code). |
|
|
*/ |
|
|
*/ |
|
|
fun getPurchaseOrderByStatus( |
|
|
fun getPurchaseOrderByStatus( |
|
|
@@ -78,10 +79,7 @@ open class ChartService( |
|
|
purchaseOrderNos: List<String>?, |
|
|
purchaseOrderNos: List<String>?, |
|
|
): List<Map<String, Any>> { |
|
|
): List<Map<String, Any>> { |
|
|
val args = mutableMapOf<String, Any>() |
|
|
val args = mutableMapOf<String, Any>() |
|
|
val dateSql = if (targetDate != null) { |
|
|
|
|
|
args["targetDate"] = targetDate.toString() |
|
|
|
|
|
"AND DATE(po.orderDate) = :targetDate" |
|
|
|
|
|
} else "" |
|
|
|
|
|
|
|
|
val dateSql = buildPurchaseOrderActualSearchDateSql(targetDate, args) |
|
|
val multiSql = buildPoMultiFiltersSql(supplierIds, itemCodes, purchaseOrderNos, null, args) |
|
|
val multiSql = buildPoMultiFiltersSql(supplierIds, itemCodes, purchaseOrderNos, null, args) |
|
|
val sql = """ |
|
|
val sql = """ |
|
|
SELECT |
|
|
SELECT |
|
|
@@ -96,9 +94,7 @@ open class ChartService( |
|
|
return jdbcDao.queryForList(sql, args) |
|
|
return jdbcDao.queryForList(sql, args) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Filter options for purchase chart (distinct suppliers / items / PO numbers on orderDate). |
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
/** Filter options for purchase chart on the same date scope as 實際已送貨. */ |
|
|
fun getPurchaseOrderFilterOptions(targetDate: LocalDate?): Map<String, Any> { |
|
|
fun getPurchaseOrderFilterOptions(targetDate: LocalDate?): Map<String, Any> { |
|
|
if (targetDate == null) { |
|
|
if (targetDate == null) { |
|
|
return mapOf( |
|
|
return mapOf( |
|
|
@@ -107,12 +103,13 @@ open class ChartService( |
|
|
"poNos" to emptyList<Any>(), |
|
|
"poNos" to emptyList<Any>(), |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|
val args = mutableMapOf<String, Any>("targetDate" to targetDate.toString()) |
|
|
|
|
|
|
|
|
val args = mutableMapOf<String, Any>() |
|
|
|
|
|
val dateSql = buildPurchaseOrderActualSearchDateSql(targetDate, args) |
|
|
val suppliersSql = """ |
|
|
val suppliersSql = """ |
|
|
SELECT DISTINCT po.supplierId AS supplierId, s.code AS code, COALESCE(s.name, '') AS name |
|
|
SELECT DISTINCT po.supplierId AS supplierId, s.code AS code, COALESCE(s.name, '') AS name |
|
|
FROM purchase_order po |
|
|
FROM purchase_order po |
|
|
LEFT JOIN shop s ON s.id = po.supplierId AND s.deleted = 0 |
|
|
LEFT JOIN shop s ON s.id = po.supplierId AND s.deleted = 0 |
|
|
WHERE po.deleted = 0 AND DATE(po.orderDate) = :targetDate AND po.supplierId IS NOT NULL |
|
|
|
|
|
|
|
|
WHERE po.deleted = 0 $dateSql AND po.supplierId IS NOT NULL |
|
|
ORDER BY s.code |
|
|
ORDER BY s.code |
|
|
""".trimIndent() |
|
|
""".trimIndent() |
|
|
val itemsSql = """ |
|
|
val itemsSql = """ |
|
|
@@ -120,13 +117,13 @@ open class ChartService( |
|
|
FROM purchase_order_line pol |
|
|
FROM purchase_order_line pol |
|
|
INNER JOIN purchase_order po ON po.id = pol.purchaseOrderId AND po.deleted = 0 |
|
|
INNER JOIN purchase_order po ON po.id = pol.purchaseOrderId AND po.deleted = 0 |
|
|
LEFT JOIN items it ON it.id = pol.itemId AND it.deleted = 0 |
|
|
LEFT JOIN items it ON it.id = pol.itemId AND it.deleted = 0 |
|
|
WHERE pol.deleted = 0 AND DATE(po.orderDate) = :targetDate |
|
|
|
|
|
|
|
|
WHERE pol.deleted = 0 $dateSql |
|
|
ORDER BY pol.itemNo |
|
|
ORDER BY pol.itemNo |
|
|
""".trimIndent() |
|
|
""".trimIndent() |
|
|
val poNosSql = """ |
|
|
val poNosSql = """ |
|
|
SELECT DISTINCT po.code AS poNo |
|
|
SELECT DISTINCT po.code AS poNo |
|
|
FROM purchase_order po |
|
|
FROM purchase_order po |
|
|
WHERE po.deleted = 0 AND DATE(po.orderDate) = :targetDate |
|
|
|
|
|
|
|
|
WHERE po.deleted = 0 $dateSql |
|
|
ORDER BY po.code |
|
|
ORDER BY po.code |
|
|
""".trimIndent() |
|
|
""".trimIndent() |
|
|
return mapOf( |
|
|
return mapOf( |
|
|
@@ -377,14 +374,40 @@ open class ChartService( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Date scope for 實際已送貨: |
|
|
|
|
|
* - planned side: PO estimatedArrivalDate = targetDate |
|
|
|
|
|
* - actual side: any stock-in activity date = targetDate |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun buildPurchaseOrderActualSearchDateSql( |
|
|
|
|
|
targetDate: LocalDate?, |
|
|
|
|
|
args: MutableMap<String, Any>, |
|
|
|
|
|
): String { |
|
|
|
|
|
if (targetDate == null) return "" |
|
|
|
|
|
args["targetDate"] = targetDate.toString() |
|
|
|
|
|
return """ |
|
|
|
|
|
AND ( |
|
|
|
|
|
(po.estimatedArrivalDate IS NOT NULL AND DATE(po.estimatedArrivalDate) = :targetDate) |
|
|
|
|
|
OR EXISTS ( |
|
|
|
|
|
SELECT 1 |
|
|
|
|
|
FROM purchase_order_line polDate |
|
|
|
|
|
INNER JOIN stock_in_line silDate ON silDate.purchaseOrderLineId = polDate.id AND silDate.deleted = 0 |
|
|
|
|
|
INNER JOIN stock_in siDate ON siDate.id = silDate.stockInId AND siDate.deleted = 0 |
|
|
|
|
|
WHERE polDate.purchaseOrderId = po.id |
|
|
|
|
|
AND polDate.deleted = 0 |
|
|
|
|
|
AND DATE(COALESCE(siDate.completeDate, silDate.receiptDate, siDate.created)) = :targetDate |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
""".trimIndent() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Drill-down rows for 預計送貨 must match [getPurchaseOrderEstimatedArrivalSummary] (estimated arrival date only). |
|
|
* Drill-down rows for 預計送貨 must match [getPurchaseOrderEstimatedArrivalSummary] (estimated arrival date only). |
|
|
* If we also applied [buildPurchaseOrderTargetDateSql], many POs (orderDate ≠ 預計到貨日) would drop out and |
|
|
* If we also applied [buildPurchaseOrderTargetDateSql], many POs (orderDate ≠ 預計到貨日) would drop out and |
|
|
* supplier / item charts would collapse incorrectly. |
|
|
* supplier / item charts would collapse incorrectly. |
|
|
* |
|
|
* |
|
|
* For 實際已送貨 (no bucket), [getPurchaseOrderByStatus] is always filtered by **order date** only. If drill used |
|
|
|
|
|
* [dateFilter] = complete, rows would be restricted to completeDate = targetDate and supplier/item charts would |
|
|
|
|
|
* collapse to a tiny set (often one supplier) vs the donut counts. |
|
|
|
|
|
|
|
|
* For 實際已送貨 (no bucket), drill must follow [buildPurchaseOrderActualSearchDateSql] to keep lower charts |
|
|
|
|
|
* consistent with the top donut data scope. |
|
|
*/ |
|
|
*/ |
|
|
private fun buildPurchaseOrderDrillOrderDateSql( |
|
|
private fun buildPurchaseOrderDrillOrderDateSql( |
|
|
estimatedArrivalBucket: String?, |
|
|
estimatedArrivalBucket: String?, |
|
|
@@ -395,7 +418,7 @@ open class ChartService( |
|
|
if (!estimatedArrivalBucket.isNullOrBlank() && targetDate != null) { |
|
|
if (!estimatedArrivalBucket.isNullOrBlank() && targetDate != null) { |
|
|
return "" |
|
|
return "" |
|
|
} |
|
|
} |
|
|
return buildPurchaseOrderTargetDateSql(targetDate, "order", args) |
|
|
|
|
|
|
|
|
return buildPurchaseOrderActualSearchDateSql(targetDate, args) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
|