|
|
|
@@ -962,6 +962,535 @@ open class ChartService( |
|
|
|
return jdbcDao.queryForList(sql, args) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Equipment usage board: equipment usage for the selected calendar day. |
|
|
|
* |
|
|
|
* **Legacy:** one row per [job_order_process_detail] when the parent step is closed |
|
|
|
* ([job_order_process.endTime] or job **completed**) and the row matches the day. |
|
|
|
* |
|
|
|
* **Production flow (工藝流程):** one row per [productprocessline] with equipment (id, detail, or free-text |
|
|
|
* [equipment_name]) when the line is done (Pass/Completed / [endTime]) or the step/job is closed—aligned with |
|
|
|
* `/jo/edit` line display. [jopdId] is **negative** for line-sourced rows (stable client key; not a real jopd id). |
|
|
|
* |
|
|
|
* Day filter: `COALESCE(operatingEnd, operatingStart, …)` for jopd; for lines |
|
|
|
* `COALESCE(line.endTime, line.startTime, jop.endTime, planStart)`. |
|
|
|
* Each row includes **usageMinutes** (jopd: operating span; line: start–end minutes or [processingTime] when Pass). |
|
|
|
* - [targetDate] null: uses server **LocalDate.now** (prefer passing the client calendar date from the UI). |
|
|
|
*/ |
|
|
|
fun getEquipmentUsageBoardRows(targetDate: LocalDate?): List<Map<String, Any>> { |
|
|
|
val args = mutableMapOf<String, Any>() |
|
|
|
val effectiveDate = targetDate ?: LocalDate.now() |
|
|
|
args["eqUsageDate"] = effectiveDate.toString() |
|
|
|
val sql = """ |
|
|
|
SELECT |
|
|
|
t.jopdId AS jopdId, |
|
|
|
t.equipmentId AS equipmentId, |
|
|
|
t.equipmentCode AS equipmentCode, |
|
|
|
t.equipmentName AS equipmentName, |
|
|
|
t.jobOrderId AS jobOrderId, |
|
|
|
t.jobOrderCode AS jobOrderCode, |
|
|
|
t.jobPlanStart AS jobPlanStart, |
|
|
|
t.processCode AS processCode, |
|
|
|
t.processName AS processName, |
|
|
|
t.operatingStart AS operatingStart, |
|
|
|
t.operatingEnd AS operatingEnd, |
|
|
|
t.usageMinutes AS usageMinutes, |
|
|
|
t.workingNow AS workingNow, |
|
|
|
t.operatorUsername AS operatorUsername, |
|
|
|
t.operatorName AS operatorName |
|
|
|
FROM ( |
|
|
|
SELECT |
|
|
|
jopd.id AS jopdId, |
|
|
|
e.id AS equipmentId, |
|
|
|
e.code AS equipmentCode, |
|
|
|
e.name AS equipmentName, |
|
|
|
jo.id AS jobOrderId, |
|
|
|
jo.code AS jobOrderCode, |
|
|
|
DATE_FORMAT(jo.planStart, '%Y-%m-%d %H:%i:%s') AS jobPlanStart, |
|
|
|
p.code AS processCode, |
|
|
|
p.name AS processName, |
|
|
|
DATE_FORMAT(jopd.operatingStart, '%Y-%m-%d %H:%i:%s') AS operatingStart, |
|
|
|
DATE_FORMAT(jopd.operatingEnd, '%Y-%m-%d %H:%i:%s') AS operatingEnd, |
|
|
|
CASE |
|
|
|
WHEN jopd.operatingStart IS NOT NULL AND jopd.operatingEnd IS NOT NULL |
|
|
|
THEN GREATEST(0, TIMESTAMPDIFF(MINUTE, jopd.operatingStart, jopd.operatingEnd)) |
|
|
|
ELSE 0 |
|
|
|
END AS usageMinutes, |
|
|
|
CASE |
|
|
|
WHEN jopd.operatingStart IS NOT NULL AND jopd.operatingEnd IS NULL THEN 1 |
|
|
|
ELSE 0 |
|
|
|
END AS workingNow, |
|
|
|
COALESCE(u.username, '') AS operatorUsername, |
|
|
|
COALESCE(NULLIF(TRIM(COALESCE(u.fullname, '')), ''), u.name, u.username, '') AS operatorName, |
|
|
|
COALESCE(jopd.operatingEnd, jopd.operatingStart, jop.endTime, jo.planStart) AS _sort |
|
|
|
FROM job_order_process_detail jopd |
|
|
|
INNER JOIN equipment e ON e.id = jopd.equipmentId AND e.deleted = 0 |
|
|
|
INNER JOIN job_order_process jop ON jop.id = jopd.jopId AND jop.deleted = 0 |
|
|
|
INNER JOIN job_order jo ON jo.id = jop.joId AND jo.deleted = 0 |
|
|
|
INNER JOIN process p ON p.id = jop.processId AND p.deleted = 0 |
|
|
|
LEFT JOIN user u ON u.id = jopd.operatorId AND u.deleted = 0 |
|
|
|
WHERE jopd.deleted = 0 |
|
|
|
AND ( |
|
|
|
jop.endTime IS NOT NULL |
|
|
|
OR LOWER(TRIM(COALESCE(jo.status, ''))) = 'completed' |
|
|
|
) |
|
|
|
AND COALESCE(jopd.operatingEnd, jopd.operatingStart, jop.endTime, jo.planStart) IS NOT NULL |
|
|
|
AND DATE(COALESCE(jopd.operatingEnd, jopd.operatingStart, jop.endTime, jo.planStart)) = :eqUsageDate |
|
|
|
UNION ALL |
|
|
|
SELECT |
|
|
|
-ppl.id AS jopdId, |
|
|
|
COALESCE(ppl.equipmentId, 0) AS equipmentId, |
|
|
|
COALESCE(NULLIF(TRIM(e.code), ''), '') AS equipmentCode, |
|
|
|
COALESCE( |
|
|
|
NULLIF(TRIM(CONCAT_WS('-', |
|
|
|
NULLIF(TRIM(ppl.equipment_name), ''), |
|
|
|
NULLIF(TRIM(COALESCE(e.name, '')), ''), |
|
|
|
NULLIF(TRIM(COALESCE(ed.code, '')), '') |
|
|
|
)), ''), |
|
|
|
NULLIF(TRIM(COALESCE(e.name, '')), ''), |
|
|
|
NULLIF(TRIM(ppl.equipment_name), ''), |
|
|
|
'—' |
|
|
|
) AS equipmentName, |
|
|
|
jo.id AS jobOrderId, |
|
|
|
jo.code AS jobOrderCode, |
|
|
|
DATE_FORMAT(jo.planStart, '%Y-%m-%d %H:%i:%s') AS jobPlanStart, |
|
|
|
p.code AS processCode, |
|
|
|
p.name AS processName, |
|
|
|
DATE_FORMAT(ppl.startTime, '%Y-%m-%d %H:%i:%s') AS operatingStart, |
|
|
|
DATE_FORMAT(ppl.endTime, '%Y-%m-%d %H:%i:%s') AS operatingEnd, |
|
|
|
CASE |
|
|
|
WHEN ppl.startTime IS NOT NULL AND ppl.endTime IS NOT NULL |
|
|
|
THEN GREATEST(0, TIMESTAMPDIFF(MINUTE, ppl.startTime, ppl.endTime)) |
|
|
|
WHEN ppl.startTime IS NOT NULL |
|
|
|
AND LOWER(REPLACE(REPLACE(TRIM(COALESCE(ppl.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
THEN GREATEST(0, COALESCE(ppl.processingTime, 0)) |
|
|
|
ELSE 0 |
|
|
|
END AS usageMinutes, |
|
|
|
CASE |
|
|
|
WHEN ppl.startTime IS NOT NULL AND ppl.endTime IS NULL THEN 1 |
|
|
|
ELSE 0 |
|
|
|
END AS workingNow, |
|
|
|
COALESCE(u2.username, '') AS operatorUsername, |
|
|
|
COALESCE(NULLIF(TRIM(COALESCE(u2.fullname, '')), ''), u2.name, u2.username, '') AS operatorName, |
|
|
|
COALESCE(ppl.endTime, ppl.startTime, jop.endTime, jo.planStart) AS _sort |
|
|
|
FROM productprocessline ppl |
|
|
|
INNER JOIN productprocess pp ON pp.id = ppl.productprocessid AND pp.deleted = 0 |
|
|
|
INNER JOIN job_order jo ON jo.id = pp.jobOrderId AND jo.deleted = 0 |
|
|
|
INNER JOIN bom_process bp ON bp.id = ppl.bomProcessId AND bp.deleted = 0 AND bp.bomId = pp.bomId |
|
|
|
INNER JOIN job_order_process jop ON jop.joId = jo.id |
|
|
|
AND jop.processId = bp.processId |
|
|
|
AND jop.seqNo = bp.seqNo |
|
|
|
AND jop.deleted = 0 |
|
|
|
INNER JOIN process p ON p.id = jop.processId AND p.deleted = 0 |
|
|
|
LEFT JOIN equipment e ON e.id = ppl.equipmentId AND e.deleted = 0 |
|
|
|
LEFT JOIN equipment_detail ed ON ed.id = ppl.equipmentDetailId AND ed.deleted = 0 |
|
|
|
LEFT JOIN user u2 ON u2.id = ppl.operatorId AND u2.deleted = 0 |
|
|
|
WHERE ppl.deleted = 0 |
|
|
|
AND ( |
|
|
|
jop.endTime IS NOT NULL |
|
|
|
OR ppl.endTime IS NOT NULL |
|
|
|
OR LOWER(REPLACE(REPLACE(TRIM(COALESCE(ppl.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
OR LOWER(TRIM(COALESCE(jo.status, ''))) = 'completed' |
|
|
|
) |
|
|
|
AND ( |
|
|
|
ppl.equipmentId IS NOT NULL |
|
|
|
OR ppl.equipmentDetailId IS NOT NULL |
|
|
|
OR NULLIF(TRIM(COALESCE(ppl.equipment_name, '')), '') IS NOT NULL |
|
|
|
) |
|
|
|
AND COALESCE(ppl.endTime, ppl.startTime, jop.endTime, jo.planStart) IS NOT NULL |
|
|
|
AND DATE(COALESCE(ppl.endTime, ppl.startTime, jop.endTime, jo.planStart)) = :eqUsageDate |
|
|
|
) t |
|
|
|
ORDER BY t.workingNow DESC, t._sort DESC |
|
|
|
""".trimIndent() |
|
|
|
return jdbcDao.queryForList(sql, args) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Process board: one row per [job_order_process] with master [process] and parent [job_order]. |
|
|
|
* |
|
|
|
* [boardStatus] aligns with the job-order 工藝流程 UI (product process lines): we aggregate |
|
|
|
* [productprocessline] rows for the same job order, BOM step ([bom_process] processId + seqNo), |
|
|
|
* and fall back to [JobOrderProcess.startTime]/[endTime] when no lines match (legacy). |
|
|
|
* Also returns line step name, description, equipment (type-name-code), and operator display name |
|
|
|
* via [GROUP_CONCAT] when multiple lines match one [job_order_process] row. |
|
|
|
* |
|
|
|
* Same optional filters as [getJobOrderBoardRows]: plan-start day and incomplete jobs only. |
|
|
|
*/ |
|
|
|
fun getProcessBoardRows(targetDate: LocalDate?, incompleteOnly: Boolean): List<Map<String, Any>> { |
|
|
|
val args = mutableMapOf<String, Any>() |
|
|
|
val dateSql = if (targetDate != null) { |
|
|
|
args["procBoardDate"] = targetDate.toString() |
|
|
|
"AND jo.planStart IS NOT NULL AND DATE(jo.planStart) = :procBoardDate" |
|
|
|
} else "" |
|
|
|
val incompleteSql = if (incompleteOnly) { |
|
|
|
"AND LOWER(TRIM(COALESCE(jo.status, ''))) <> 'completed'" |
|
|
|
} else "" |
|
|
|
val sql = """ |
|
|
|
SELECT |
|
|
|
jop.id AS jopId, |
|
|
|
jo.id AS jobOrderId, |
|
|
|
jo.code AS jobOrderCode, |
|
|
|
COALESCE(jo.status, 'unknown') AS jobOrderStatus, |
|
|
|
p.id AS processId, |
|
|
|
p.code AS processCode, |
|
|
|
p.name AS processName, |
|
|
|
jop.seqNo AS seqNo, |
|
|
|
COALESCE(jop.status, '') AS rowStatus, |
|
|
|
DATE_FORMAT(jo.planStart, '%Y-%m-%d %H:%i:%s') AS jobPlanStart, |
|
|
|
DATE_FORMAT(COALESCE(jop.startTime, lineAgg.minLineStart), '%Y-%m-%d %H:%i:%s') AS startTime, |
|
|
|
DATE_FORMAT(COALESCE(jop.endTime, lineAgg.maxLineEndAllDone), '%Y-%m-%d %H:%i:%s') AS endTime, |
|
|
|
COALESCE(lineAgg.lineStepName, p.name) AS lineStepName, |
|
|
|
COALESCE(lineAgg.lineDescription, '') AS lineDescription, |
|
|
|
COALESCE(lineAgg.lineEquipmentLabel, '') AS lineEquipmentLabel, |
|
|
|
COALESCE(lineAgg.lineOperatorInfo, '') AS lineOperatorInfo, |
|
|
|
CASE |
|
|
|
WHEN jop.endTime IS NOT NULL THEN 'completed' |
|
|
|
WHEN COALESCE(lineAgg.lineCount, 0) = 0 THEN |
|
|
|
CASE |
|
|
|
WHEN jop.startTime IS NOT NULL THEN 'in_progress' |
|
|
|
ELSE 'pending' |
|
|
|
END |
|
|
|
WHEN lineAgg.notDoneCount = 0 THEN 'completed' |
|
|
|
WHEN COALESCE(lineAgg.inProgressCount, 0) > 0 OR jop.startTime IS NOT NULL THEN 'in_progress' |
|
|
|
ELSE 'pending' |
|
|
|
END AS boardStatus, |
|
|
|
COALESCE(it.code, '') AS itemCode, |
|
|
|
COALESCE(it.name, '') AS itemName, |
|
|
|
COALESCE(jt.name, '') AS jobTypeName, |
|
|
|
jo.reqQty AS reqQty, |
|
|
|
COALESCE(b.outputQtyUom, '') AS outputQtyUom, |
|
|
|
( |
|
|
|
SELECT DATE_FORMAT(ppd.date, '%Y-%m-%d') |
|
|
|
FROM productprocess ppd |
|
|
|
WHERE ppd.jobOrderId = jo.id AND ppd.deleted = 0 |
|
|
|
ORDER BY ppd.id ASC |
|
|
|
LIMIT 1 |
|
|
|
) AS productionDate, |
|
|
|
( |
|
|
|
SELECT COALESCE(SUM(COALESCE(pplp.processingTime, 0)), 0) |
|
|
|
FROM productprocessline pplp |
|
|
|
INNER JOIN productprocess ppp ON ppp.id = pplp.productprocessid AND ppp.deleted = 0 |
|
|
|
WHERE ppp.jobOrderId = jo.id AND pplp.deleted = 0 |
|
|
|
) AS planProcessingMinsTotal, |
|
|
|
( |
|
|
|
SELECT COALESCE(SUM( |
|
|
|
COALESCE(ppls.setupTime, 0) + COALESCE(ppls.changeoverTime, 0) |
|
|
|
), 0) |
|
|
|
FROM productprocessline ppls |
|
|
|
INNER JOIN productprocess pps ON pps.id = ppls.productprocessid AND pps.deleted = 0 |
|
|
|
WHERE pps.jobOrderId = jo.id AND ppls.deleted = 0 |
|
|
|
) AS planSetupChangeoverMinsTotal, |
|
|
|
( |
|
|
|
SELECT DATE_FORMAT(MIN(ppst.startTime), '%Y-%m-%d %H:%i:%s') |
|
|
|
FROM productprocess ppst |
|
|
|
WHERE ppst.jobOrderId = jo.id AND ppst.deleted = 0 AND ppst.startTime IS NOT NULL |
|
|
|
) AS productProcessStart, |
|
|
|
( |
|
|
|
SELECT ROUND(COALESCE(SUM( |
|
|
|
CASE |
|
|
|
WHEN pplx.startTime IS NOT NULL AND pplx.endTime IS NOT NULL |
|
|
|
THEN GREATEST(0, TIMESTAMPDIFF(SECOND, pplx.startTime, pplx.endTime)) |
|
|
|
WHEN pplx.startTime IS NOT NULL |
|
|
|
AND LOWER(REPLACE(REPLACE(TRIM(COALESCE(pplx.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
THEN GREATEST(0, COALESCE(pplx.processingTime, 0) * 60) |
|
|
|
ELSE 0 |
|
|
|
END |
|
|
|
), 0) / 60.0, 2) |
|
|
|
FROM productprocessline pplx |
|
|
|
INNER JOIN productprocess ppx ON ppx.id = pplx.productprocessid AND ppx.deleted = 0 |
|
|
|
WHERE ppx.jobOrderId = jo.id AND pplx.deleted = 0 |
|
|
|
) AS actualLineMinsTotal, |
|
|
|
COALESCE(lineAgg.stepPlanMins, 0) AS stepPlanMins, |
|
|
|
COALESCE(lineAgg.stepActualMins, 0) AS stepActualMins |
|
|
|
FROM job_order_process jop |
|
|
|
INNER JOIN job_order jo ON jo.id = jop.joId AND jo.deleted = 0 |
|
|
|
INNER JOIN process p ON p.id = jop.processId AND p.deleted = 0 |
|
|
|
LEFT JOIN bom b ON b.id = jo.bomId AND b.deleted = 0 |
|
|
|
LEFT JOIN items it ON it.id = b.itemId AND it.deleted = 0 |
|
|
|
LEFT JOIN jobtype jt ON jt.id = jo.jobTypeId AND jt.deleted = 0 |
|
|
|
LEFT JOIN ( |
|
|
|
SELECT |
|
|
|
pp.jobOrderId AS joId, |
|
|
|
bp.processId AS processId, |
|
|
|
bp.seqNo AS seqNo, |
|
|
|
COUNT(*) AS lineCount, |
|
|
|
SUM( |
|
|
|
CASE |
|
|
|
WHEN LOWER(REPLACE(REPLACE(TRIM(COALESCE(ppl.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
OR ppl.endTime IS NOT NULL |
|
|
|
THEN 0 ELSE 1 |
|
|
|
END |
|
|
|
) AS notDoneCount, |
|
|
|
SUM( |
|
|
|
CASE |
|
|
|
WHEN ( |
|
|
|
LOWER(REPLACE(REPLACE(TRIM(COALESCE(ppl.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
OR ppl.endTime IS NOT NULL |
|
|
|
) THEN 0 |
|
|
|
WHEN ( |
|
|
|
LOWER(REPLACE(REPLACE(TRIM(COALESCE(ppl.status, '')), '_', ''), ' ', '')) IN ('inprogress', 'paused') |
|
|
|
OR (ppl.startTime IS NOT NULL AND ppl.endTime IS NULL) |
|
|
|
) THEN 1 |
|
|
|
ELSE 0 |
|
|
|
END |
|
|
|
) AS inProgressCount, |
|
|
|
MIN(ppl.startTime) AS minLineStart, |
|
|
|
CASE |
|
|
|
WHEN SUM( |
|
|
|
CASE |
|
|
|
WHEN LOWER(REPLACE(REPLACE(TRIM(COALESCE(ppl.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
OR ppl.endTime IS NOT NULL |
|
|
|
THEN 0 ELSE 1 |
|
|
|
END |
|
|
|
) = 0 AND COUNT(*) > 0 |
|
|
|
THEN MAX(ppl.endTime) |
|
|
|
ELSE NULL |
|
|
|
END AS maxLineEndAllDone, |
|
|
|
GROUP_CONCAT( |
|
|
|
NULLIF(TRIM(ppl.name), '') |
|
|
|
ORDER BY ppl.seqNo ASC, ppl.id ASC |
|
|
|
SEPARATOR ' | ' |
|
|
|
) AS lineStepName, |
|
|
|
GROUP_CONCAT( |
|
|
|
NULLIF(TRIM(ppl.description), '') |
|
|
|
ORDER BY ppl.seqNo ASC, ppl.id ASC |
|
|
|
SEPARATOR ' | ' |
|
|
|
) AS lineDescription, |
|
|
|
GROUP_CONCAT( |
|
|
|
NULLIF(TRIM(COALESCE( |
|
|
|
NULLIF(TRIM(ed.code), ''), |
|
|
|
NULLIF(TRIM(ppl.equipment_name), ''), |
|
|
|
NULLIF(TRIM(e.name), '') |
|
|
|
)), '') |
|
|
|
ORDER BY ppl.seqNo ASC, ppl.id ASC |
|
|
|
SEPARATOR ' | ' |
|
|
|
) AS lineEquipmentLabel, |
|
|
|
GROUP_CONCAT( |
|
|
|
NULLIF(TRIM(COALESCE(NULLIF(TRIM(COALESCE(u.fullname, '')), ''), u.name, u.username, '')), '') |
|
|
|
ORDER BY ppl.seqNo ASC, ppl.id ASC |
|
|
|
SEPARATOR ' | ' |
|
|
|
) AS lineOperatorInfo, |
|
|
|
COALESCE(SUM( |
|
|
|
COALESCE(ppl.processingTime, 0) + COALESCE(ppl.setupTime, 0) + COALESCE(ppl.changeoverTime, 0) |
|
|
|
), 0) AS stepPlanMins, |
|
|
|
ROUND(COALESCE(SUM( |
|
|
|
CASE |
|
|
|
WHEN ppl.startTime IS NOT NULL AND ppl.endTime IS NOT NULL |
|
|
|
THEN GREATEST(0, TIMESTAMPDIFF(SECOND, ppl.startTime, ppl.endTime)) |
|
|
|
WHEN ppl.startTime IS NOT NULL |
|
|
|
AND LOWER(REPLACE(REPLACE(TRIM(COALESCE(ppl.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
THEN GREATEST(0, COALESCE(ppl.processingTime, 0) * 60) |
|
|
|
ELSE 0 |
|
|
|
END |
|
|
|
), 0) / 60.0, 2) AS stepActualMins |
|
|
|
FROM productprocessline ppl |
|
|
|
INNER JOIN productprocess pp ON pp.id = ppl.productprocessid AND pp.deleted = 0 |
|
|
|
INNER JOIN bom_process bp ON bp.id = ppl.bomProcessId AND bp.deleted = 0 AND bp.bomId = pp.bomId |
|
|
|
LEFT JOIN equipment e ON e.id = ppl.equipmentId AND e.deleted = 0 |
|
|
|
LEFT JOIN equipment_detail ed ON ed.id = ppl.equipmentDetailId AND ed.deleted = 0 |
|
|
|
LEFT JOIN user u ON u.id = ppl.operatorId AND u.deleted = 0 |
|
|
|
WHERE ppl.deleted = 0 |
|
|
|
GROUP BY pp.jobOrderId, bp.processId, bp.seqNo |
|
|
|
) lineAgg ON lineAgg.joId = jo.id |
|
|
|
AND lineAgg.processId = jop.processId |
|
|
|
AND lineAgg.seqNo = jop.seqNo |
|
|
|
WHERE jop.deleted = 0 |
|
|
|
$dateSql |
|
|
|
$incompleteSql |
|
|
|
ORDER BY jo.planStart DESC, jo.code ASC, jop.seqNo ASC, jop.id ASC |
|
|
|
""".trimIndent() |
|
|
|
return jdbcDao.queryForList(sql, args) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Job order board: one row per JO for visualization (planStart date filter when [targetDate] set). |
|
|
|
* Stock-in metrics: only lines on jobs whose BOM is FG/WIP; splits QC 中 / 已驗待入庫 / 已入庫 by [stock_in_line].status. |
|
|
|
* Material picked: status completed / linked stock out / or JO completed (BOM rows often stay pending if never bulk-updated). |
|
|
|
* Process done: [job_order_process.endTime], or all [productprocessline] rows for the step Pass/Completed, or JO completed fallback. |
|
|
|
*/ |
|
|
|
fun getJobOrderBoardRows(targetDate: LocalDate?, incompleteOnly: Boolean): List<Map<String, Any>> { |
|
|
|
val args = mutableMapOf<String, Any>() |
|
|
|
val dateSql = if (targetDate != null) { |
|
|
|
args["boardTargetDate"] = targetDate.toString() |
|
|
|
"AND jo.planStart IS NOT NULL AND DATE(jo.planStart) = :boardTargetDate" |
|
|
|
} else "" |
|
|
|
val incompleteSql = if (incompleteOnly) { |
|
|
|
"AND LOWER(TRIM(COALESCE(jo.status, ''))) <> 'completed'" |
|
|
|
} else "" |
|
|
|
val sql = """ |
|
|
|
SELECT |
|
|
|
jo.id AS jobOrderId, |
|
|
|
jo.code AS code, |
|
|
|
COALESCE(jo.status, 'unknown') AS status, |
|
|
|
DATE_FORMAT(jo.planStart, '%Y-%m-%d %H:%i:%s') AS planStart, |
|
|
|
DATE_FORMAT(jo.actualStart, '%Y-%m-%d %H:%i:%s') AS actualStart, |
|
|
|
DATE_FORMAT(jo.planEnd, '%Y-%m-%d %H:%i:%s') AS planEnd, |
|
|
|
DATE_FORMAT(jo.actualEnd, '%Y-%m-%d %H:%i:%s') AS actualEnd, |
|
|
|
GREATEST(0, COALESCE(mat.matTotal, 0) - COALESCE(mat.pickedCnt, 0)) AS materialPendingCount, |
|
|
|
COALESCE(mat.pickedCnt, 0) AS materialPickedCount, |
|
|
|
COALESCE(proc.totalCnt, 0) AS processTotalCount, |
|
|
|
COALESCE(proc.doneCnt, 0) AS processCompletedCount, |
|
|
|
( |
|
|
|
SELECT p.code |
|
|
|
FROM job_order_process jop2 |
|
|
|
INNER JOIN process p ON p.id = jop2.processId AND p.deleted = 0 |
|
|
|
WHERE jop2.joId = jo.id AND jop2.deleted = 0 AND jop2.endTime IS NULL |
|
|
|
AND LOWER(TRIM(COALESCE(jo.status, ''))) <> 'completed' |
|
|
|
ORDER BY jop2.seqNo ASC, jop2.id ASC |
|
|
|
LIMIT 1 |
|
|
|
) AS currentProcessCode, |
|
|
|
( |
|
|
|
SELECT p.name |
|
|
|
FROM job_order_process jop2 |
|
|
|
INNER JOIN process p ON p.id = jop2.processId AND p.deleted = 0 |
|
|
|
WHERE jop2.joId = jo.id AND jop2.deleted = 0 AND jop2.endTime IS NULL |
|
|
|
AND LOWER(TRIM(COALESCE(jo.status, ''))) <> 'completed' |
|
|
|
ORDER BY jop2.seqNo ASC, jop2.id ASC |
|
|
|
LIMIT 1 |
|
|
|
) AS currentProcessName, |
|
|
|
( |
|
|
|
SELECT DATE_FORMAT(jop2.startTime, '%Y-%m-%d %H:%i:%s') |
|
|
|
FROM job_order_process jop2 |
|
|
|
WHERE jop2.joId = jo.id AND jop2.deleted = 0 AND jop2.endTime IS NULL |
|
|
|
AND LOWER(TRIM(COALESCE(jo.status, ''))) <> 'completed' |
|
|
|
ORDER BY jop2.seqNo ASC, jop2.id ASC |
|
|
|
LIMIT 1 |
|
|
|
) AS currentProcessStartTime, |
|
|
|
COALESCE(silAgg.stockInAcceptedQtyTotal, 0) AS stockInAcceptedQtyTotal, |
|
|
|
COALESCE(silAgg.fgReadyToStockInCount, 0) AS fgReadyToStockInCount, |
|
|
|
COALESCE(silAgg.fgReadyToStockInQty, 0) AS fgReadyToStockInQty, |
|
|
|
COALESCE(silAgg.fgInQcLineCount, 0) AS fgInQcLineCount, |
|
|
|
COALESCE(silAgg.fgInQcQty, 0) AS fgInQcQty, |
|
|
|
COALESCE(silAgg.fgStockedQty, 0) AS fgStockedQty, |
|
|
|
COALESCE(it.code, '') AS itemCode, |
|
|
|
COALESCE(it.name, '') AS itemName, |
|
|
|
COALESCE(jt.name, '') AS jobTypeName, |
|
|
|
jo.reqQty AS reqQty, |
|
|
|
COALESCE(b.outputQtyUom, '') AS outputQtyUom, |
|
|
|
( |
|
|
|
SELECT DATE_FORMAT(ppd.date, '%Y-%m-%d') |
|
|
|
FROM productprocess ppd |
|
|
|
WHERE ppd.jobOrderId = jo.id AND ppd.deleted = 0 |
|
|
|
ORDER BY ppd.id ASC |
|
|
|
LIMIT 1 |
|
|
|
) AS productionDate, |
|
|
|
( |
|
|
|
SELECT COALESCE(SUM(COALESCE(pplp.processingTime, 0)), 0) |
|
|
|
FROM productprocessline pplp |
|
|
|
INNER JOIN productprocess ppp ON ppp.id = pplp.productprocessid AND ppp.deleted = 0 |
|
|
|
WHERE ppp.jobOrderId = jo.id AND pplp.deleted = 0 |
|
|
|
) AS planProcessingMinsTotal, |
|
|
|
( |
|
|
|
SELECT COALESCE(SUM( |
|
|
|
COALESCE(ppls.setupTime, 0) + COALESCE(ppls.changeoverTime, 0) |
|
|
|
), 0) |
|
|
|
FROM productprocessline ppls |
|
|
|
INNER JOIN productprocess pps ON pps.id = ppls.productprocessid AND pps.deleted = 0 |
|
|
|
WHERE pps.jobOrderId = jo.id AND ppls.deleted = 0 |
|
|
|
) AS planSetupChangeoverMinsTotal, |
|
|
|
( |
|
|
|
SELECT DATE_FORMAT(MIN(ppst.startTime), '%Y-%m-%d %H:%i:%s') |
|
|
|
FROM productprocess ppst |
|
|
|
WHERE ppst.jobOrderId = jo.id AND ppst.deleted = 0 AND ppst.startTime IS NOT NULL |
|
|
|
) AS productProcessStart, |
|
|
|
( |
|
|
|
SELECT ROUND(COALESCE(SUM( |
|
|
|
CASE |
|
|
|
WHEN pplx.startTime IS NOT NULL AND pplx.endTime IS NOT NULL |
|
|
|
THEN GREATEST(0, TIMESTAMPDIFF(SECOND, pplx.startTime, pplx.endTime)) |
|
|
|
WHEN pplx.startTime IS NOT NULL |
|
|
|
AND LOWER(REPLACE(REPLACE(TRIM(COALESCE(pplx.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
THEN GREATEST(0, COALESCE(pplx.processingTime, 0) * 60) |
|
|
|
ELSE 0 |
|
|
|
END |
|
|
|
), 0) / 60.0, 2) |
|
|
|
FROM productprocessline pplx |
|
|
|
INNER JOIN productprocess ppx ON ppx.id = pplx.productprocessid AND ppx.deleted = 0 |
|
|
|
WHERE ppx.jobOrderId = jo.id AND pplx.deleted = 0 |
|
|
|
) AS actualLineMinsTotal |
|
|
|
FROM job_order jo |
|
|
|
LEFT JOIN bom b ON b.id = jo.bomId AND b.deleted = 0 |
|
|
|
LEFT JOIN items it ON it.id = b.itemId AND it.deleted = 0 |
|
|
|
LEFT JOIN jobtype jt ON jt.id = jo.jobTypeId AND jt.deleted = 0 |
|
|
|
LEFT JOIN ( |
|
|
|
SELECT |
|
|
|
jobm.jobOrderId AS joId, |
|
|
|
COUNT(jobm.id) AS matTotal, |
|
|
|
COALESCE(SUM(CASE |
|
|
|
WHEN LOWER(TRIM(COALESCE(jobm.status, ''))) IN ('completed', 'complete') |
|
|
|
OR jobm.stockOutLineId IS NOT NULL |
|
|
|
OR LOWER(TRIM(COALESCE(joMat.status, ''))) = 'completed' |
|
|
|
THEN 1 ELSE 0 END), 0) AS pickedCnt |
|
|
|
FROM job_order_bom_material jobm |
|
|
|
INNER JOIN job_order joMat ON joMat.id = jobm.jobOrderId AND joMat.deleted = 0 |
|
|
|
WHERE jobm.deleted = 0 |
|
|
|
GROUP BY jobm.jobOrderId |
|
|
|
) mat ON mat.joId = jo.id |
|
|
|
LEFT JOIN ( |
|
|
|
SELECT |
|
|
|
jop.joId AS joId, |
|
|
|
COUNT(jop.id) AS totalCnt, |
|
|
|
COALESCE(SUM(CASE |
|
|
|
WHEN jop.endTime IS NOT NULL THEN 1 |
|
|
|
WHEN COALESCE(lineAgg.lineCount, 0) > 0 AND lineAgg.notDoneCount = 0 THEN 1 |
|
|
|
WHEN LOWER(TRIM(COALESCE(joProc.status, ''))) = 'completed' THEN 1 |
|
|
|
ELSE 0 END), 0) AS doneCnt |
|
|
|
FROM job_order_process jop |
|
|
|
INNER JOIN job_order joProc ON joProc.id = jop.joId AND joProc.deleted = 0 |
|
|
|
LEFT JOIN ( |
|
|
|
SELECT |
|
|
|
pp.jobOrderId AS joId, |
|
|
|
bp.processId AS processId, |
|
|
|
bp.seqNo AS seqNo, |
|
|
|
COUNT(*) AS lineCount, |
|
|
|
SUM( |
|
|
|
CASE |
|
|
|
WHEN LOWER(REPLACE(REPLACE(TRIM(COALESCE(ppl.status, '')), '_', ''), ' ', '')) IN ('completed', 'pass') |
|
|
|
OR ppl.endTime IS NOT NULL |
|
|
|
THEN 0 ELSE 1 |
|
|
|
END |
|
|
|
) AS notDoneCount |
|
|
|
FROM productprocessline ppl |
|
|
|
INNER JOIN productprocess pp ON pp.id = ppl.productprocessid AND pp.deleted = 0 |
|
|
|
INNER JOIN bom_process bp ON bp.id = ppl.bomProcessId AND bp.deleted = 0 AND bp.bomId = pp.bomId |
|
|
|
WHERE ppl.deleted = 0 |
|
|
|
GROUP BY pp.jobOrderId, bp.processId, bp.seqNo |
|
|
|
) lineAgg ON lineAgg.joId = jop.joId |
|
|
|
AND lineAgg.processId = jop.processId |
|
|
|
AND lineAgg.seqNo = jop.seqNo |
|
|
|
WHERE jop.deleted = 0 |
|
|
|
GROUP BY jop.joId |
|
|
|
) proc ON proc.joId = jo.id |
|
|
|
LEFT JOIN ( |
|
|
|
SELECT |
|
|
|
sil.jobOrderId AS joId, |
|
|
|
COALESCE(SUM(COALESCE(sil.acceptedQty, 0)), 0) AS stockInAcceptedQtyTotal, |
|
|
|
COALESCE(SUM(CASE WHEN LOWER(COALESCE(sil.status, '')) IN ('receiving', 'received') THEN 1 ELSE 0 END), 0) AS fgReadyToStockInCount, |
|
|
|
COALESCE(SUM(CASE WHEN LOWER(COALESCE(sil.status, '')) IN ('receiving', 'received') THEN COALESCE(sil.acceptedQty, 0) ELSE 0 END), 0) AS fgReadyToStockInQty, |
|
|
|
COALESCE(SUM(CASE WHEN LOWER(COALESCE(sil.status, '')) IN ( |
|
|
|
'pending', 'qc1', 'qc2', 'qc3', 'qc', 'escalated', |
|
|
|
'determine1', 'determine2', 'determine3' |
|
|
|
) THEN 1 ELSE 0 END), 0) AS fgInQcLineCount, |
|
|
|
COALESCE(SUM(CASE WHEN LOWER(COALESCE(sil.status, '')) IN ( |
|
|
|
'pending', 'qc1', 'qc2', 'qc3', 'qc', 'escalated', |
|
|
|
'determine1', 'determine2', 'determine3' |
|
|
|
) THEN COALESCE(sil.acceptedQty, 0) ELSE 0 END), 0) AS fgInQcQty, |
|
|
|
COALESCE(SUM(CASE WHEN LOWER(COALESCE(sil.status, '')) IN ('completed', 'complete', 'partially_completed') THEN COALESCE(sil.acceptedQty, 0) ELSE 0 END), 0) AS fgStockedQty |
|
|
|
FROM stock_in_line sil |
|
|
|
INNER JOIN job_order joSil ON joSil.id = sil.jobOrderId AND joSil.deleted = 0 |
|
|
|
INNER JOIN bom b ON b.id = joSil.bomId AND b.deleted = 0 |
|
|
|
AND UPPER(TRIM(COALESCE(b.description, ''))) IN ('FG', 'WIP') |
|
|
|
WHERE sil.deleted = 0 AND sil.jobOrderId IS NOT NULL |
|
|
|
GROUP BY sil.jobOrderId |
|
|
|
) silAgg ON silAgg.joId = jo.id |
|
|
|
WHERE jo.deleted = 0 |
|
|
|
$dateSql |
|
|
|
$incompleteSql |
|
|
|
ORDER BY jo.planStart DESC, jo.code |
|
|
|
""".trimIndent() |
|
|
|
return jdbcDao.queryForList(sql, args) |
|
|
|
} |
|
|
|
|
|
|
|
// ---------- Forecast & planning reports ---------- |
|
|
|
|
|
|
|
/** |
|
|
|
|