Ver a proveniência

update job order record

stable1
CANCERYS\kw093 há 2 semanas
ascendente
cometimento
a4a6e81833
5 ficheiros alterados com 307 adições e 2 eliminações
  1. +227
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt
  2. +1
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  3. +52
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt
  4. +17
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderRepository.kt
  5. +10
    -0
      src/main/resources/db/changelog/changes/20260420_01_Enson/02_alter_stock_take.sql

+ 227
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt Ver ficheiro

@@ -1702,7 +1702,233 @@ open fun getCompletedJobOrderPickOrders(completedDate: LocalDate?): List<Map<Str
emptyList()
}
}
// ... rest of the code ...
open fun getJobOrderPickOrders(date: LocalDate?, status: PickOrderStatus?): List<Map<String, Any?>> {
println("=== getJobOrderPickOrders ===")
println("date: $date, status: $status")
return try {
val allPickOrders =
when {
date != null -> {
val from = date.atStartOfDay()
val toExclusive = date.plusDays(1).atStartOfDay()
pickOrderRepository.findByJobOrderPlanStartOnDayAndOptionalStatus(from, toExclusive, status)
}
status != null -> {
pickOrderRepository.findAllByStatusAndDeletedFalse(status)
.filter { it.jobOrder != null && it.jobOrder?.isHidden != true }
}
else -> {
pickOrderRepository.findAllByStatusIn(PickOrderStatus.entries.toList())
.filter { !it.deleted && it.jobOrder != null && it.jobOrder?.isHidden != true }
}
}
val pickOrderIds = allPickOrders.mapNotNull { it.id }.distinct()
val joByPickOrderId: Map<Long, List<JoPickOrder>> =
if (pickOrderIds.isNotEmpty()) {
joPickOrderRepository.findByPickOrderIdIn(pickOrderIds)
.filterNot { it.deleted }
.groupBy { it.pickOrderId ?: 0L }
.filterKeys { it != 0L }
} else {
emptyMap()
}
allPickOrders.mapNotNull { pickOrder ->
val jobOrder = pickOrder.jobOrder ?: return@mapNotNull null
val poId = pickOrder.id ?: return@mapNotNull null
val joPickOrders = joByPickOrderId[poId].orEmpty()
val secondScanCompleted = joPickOrders.isNotEmpty() &&
joPickOrders.all { it.matchStatus == com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus.completed }
mapOf(
"id" to pickOrder.id,
"pickOrderId" to pickOrder.id,
"pickOrderCode" to pickOrder.code,
"pickOrderConsoCode" to pickOrder.consoCode,
"pickOrderTargetDate" to pickOrder.targetDate?.let {
"${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}"
},
"pickOrderStatus" to pickOrder.status,
"completedDate" to jobOrder.planStart,
"jobOrderId" to jobOrder.id,
"jobOrderCode" to jobOrder.code,
"jobOrderName" to jobOrder.bom?.name,
"reqQty" to jobOrder.reqQty,
"uom" to jobOrder.bom?.uom?.code,
"planStart" to jobOrder.planStart,
"planEnd" to jobOrder.planEnd,
"secondScanCompleted" to secondScanCompleted,
"totalItems" to joPickOrders.size,
"completedItems" to joPickOrders.count {
it.matchStatus == com.ffii.fpsms.modules.jobOrder.enums.JoPickOrderStatus.completed
},
)
}
} catch (e: Exception) {
println("❌ Error in getJobOrderPickOrders: ${e.message}")
e.printStackTrace()
emptyList()
}
}
open fun getJobOrderPickOrderLotDetails(
pickOrderId: Long,
status: String? = null, // 可選,不傳=全部狀態
): List<Map<String, Any?>> {
println("=== getJobOrderPickOrderLotDetails (all status) ===")
println("pickOrderId: $pickOrderId, status: $status")
return try {
val sql = """
SELECT
-- Pick Order Information
po.id as pickOrderId,
po.code as pickOrderCode,
po.consoCode as pickOrderConsoCode,
DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate,
po.status as pickOrderStatus,
-- Job Order Information
jo.id as jobOrderId,
jo.code as jobOrderCode,
-- Pick Order Line Information
pol.id as pickOrderLineId,
pol.qty as pickOrderLineRequiredQty,
pol.status as pickOrderLineStatus,
-- Item Information
i.id as itemId,
i.code as itemCode,
i.name as itemName,
uc.code as uomCode,
uc.udfudesc as uomDesc,
uc.udfShortDesc as uomShortDesc,
-- Lot Information
ill.id as lotId,
il.lotNo,
DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate,
w.name as location,
COALESCE(uc.udfudesc, 'N/A') as stockUnit,
-- Router Information
w.id as routerId,
w.`order` as routerIndex,
w.code as routerRoute,
w.code as routerArea,
-- Set quantities to NULL for rejected lots
CASE
WHEN sol.status = 'rejected' THEN NULL
ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0))
END as availableQty,
-- Required quantity for this lot
COALESCE(spl.qty, 0) as requiredQty,
-- Actual picked quantity
COALESCE(sol.qty, 0) as actualPickQty,
-- Suggested pick lot information
spl.id as suggestedPickLotId,
ill.status as lotStatus,
-- Stock out line information
sol.id as stockOutLineId,
sol.status as stockOutLineStatus,
COALESCE(sol.qty, 0) as stockOutLineQty,
-- Additional detailed fields
COALESCE(ill.inQty, 0) as inQty,
COALESCE(ill.outQty, 0) as outQty,
COALESCE(ill.holdQty, 0) as holdQty,
-- Calculate total picked quantity by ALL pick orders for this lot
COALESCE((
SELECT SUM(sol_all.qty)
FROM fpsmsdb.stock_out_line sol_all
WHERE sol_all.inventoryLotLineId = ill.id
AND sol_all.deleted = false
AND sol_all.status IN ('pending', 'checked', 'partially_completed', 'completed', 'picking')
), 0) as totalPickedByAllPickOrders,
-- Calculate remaining available quantity correctly
(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as remainingAfterAllPickOrders,
-- Lot availability status
CASE
WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired'
WHEN sol.status = 'rejected' THEN 'rejected'
WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock'
WHEN ill.status = 'unavailable' THEN 'status_unavailable'
ELSE 'available'
END as lotAvailability,
-- Processing status (all-status friendly)
CASE
WHEN sol.status = 'completed' THEN 'completed'
WHEN sol.status = 'rejected' THEN 'rejected'
WHEN sol.status IN ('checked', 'partially_completed', 'picking') THEN 'in_progress'
WHEN sol.status IN ('created', 'pending') THEN 'pending'
ELSE COALESCE(sol.status, 'pending')
END as processingStatus,
-- JoPickOrder second scan status
jpo.match_status as match_status,
jpo.match_by as match_by,
jpo.match_qty as match_qty
FROM fpsmsdb.pick_order po
JOIN fpsmsdb.job_order jo ON jo.id = po.joId
JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id
JOIN fpsmsdb.items i ON i.id = pol.itemId
LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId
LEFT JOIN fpsmsdb.suggested_pick_lot spl ON pol.id = spl.pickOrderLineId
LEFT JOIN fpsmsdb.inventory_lot_line ill ON spl.suggestedLotLineId = ill.id
LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId
LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId
-- no-lot:ill.id 為 null 時,匹配 sol.inventoryLotLineId IS NULL
LEFT JOIN fpsmsdb.stock_out_line sol ON sol.pickOrderLineId = pol.id AND sol.deleted = false
AND (
sol.inventoryLotLineId = ill.id
OR (sol.inventoryLotLineId IS NULL AND ill.id IS NULL)
)
LEFT JOIN fpsmsdb.jo_pick_order jpo ON jpo.pick_order_id = po.id AND jpo.item_id = pol.itemId
WHERE po.deleted = false
AND po.id = :pickOrderId
AND pol.deleted = false
AND (:status IS NULL OR po.status = :status)
-- ill / il 可能為 null(no-lot),避免過濾掉 no-lot 記錄
AND (ill.id IS NULL OR ill.deleted = false)
AND (il.id IS NULL OR il.deleted = false)
AND (spl.pickOrderLineId IS NOT NULL OR sol.pickOrderLineId IS NOT NULL)
ORDER BY
CASE WHEN sol.status = 'rejected' THEN 0 ELSE 1 END,
COALESCE(w.`order`, 999999) ASC,
po.code ASC,
i.code ASC,
il.expiryDate ASC,
il.lotNo ASC
""".trimIndent()
val params = mapOf(
"pickOrderId" to pickOrderId,
"status" to status?.trim()?.takeIf { it.isNotEmpty() }, // null = all status
)
println("With parameters: $params")
val results = jdbcDao.queryForList(sql, params)
println("Found ${results.size} lot details for pickOrderId=$pickOrderId")
results
} catch (e: Exception) {
println("Error in getJobOrderPickOrderLotDetails: ${e.message}")
e.printStackTrace()
emptyList()
}
}

open fun getCompletedJobOrderPickOrderLotDetailsForCompletedPick(pickOrderId: Long): List<Map<String, Any?>> {
println("=== getCompletedJobOrderPickOrderLotDetailsForCompletedPick ===")


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt Ver ficheiro

@@ -731,7 +731,7 @@ open class JobOrderService(
}
val inputStream = resource.inputStream
val pickRecord = JasperCompileManager.compileReport(inputStream)
val pickRecordInfo = joPickOrderService.getCompletedJobOrderPickOrderLotDetails(request.pickOrderIds).toMutableList()
val pickRecordInfo = joPickOrderService.getJobOrderPickOrderLotDetails(request.pickOrderIds).toMutableList()


val fields = mutableListOf<MutableMap<String, Any>>()


+ 52
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt Ver ficheiro

@@ -20,6 +20,11 @@ import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService
import com.ffii.fpsms.modules.jobOrder.service.JoWorkbenchMainService
import com.ffii.fpsms.modules.jobOrder.service.JoWorkbenchReleaseService
import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus

import org.springframework.format.annotation.DateTimeFormat
import com.ffii.fpsms.modules.productProcess.service.ProductProcessService
import com.ffii.fpsms.modules.jobOrder.web.model.*
import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest
@@ -50,6 +55,8 @@ class JobOrderController(
private val jobOrderBomMaterialService: JobOrderBomMaterialService,
private val jobOrderProcessService: JobOrderProcessService,
private val joPickOrderService: JoPickOrderService,
private val joWorkbenchMainService: JoWorkbenchMainService,
private val joWorkbenchReleaseService: JoWorkbenchReleaseService,
private val productProcessService: ProductProcessService,
private val jobOrderCreationService: com.ffii.fpsms.modules.jobOrder.service.JobOrderCreationService
) {
@@ -82,6 +89,18 @@ class JobOrderController(
return jobOrderService.releaseJobOrder(request)
}

/** Workbench: same search as [allJobOrdersByPage] but dedicated route for future divergence. */
@GetMapping("/workbench/getRecordByPage")
fun allJobOrdersByPageForWorkbench(@ModelAttribute request: SearchJobOrderInfoRequest): RecordsRes<JobOrderInfoWithTypeName> {
return jobOrderService.allJobOrdersByPageWithTypeName(request)
}

/** Workbench: release without stock out / SPL / SOL until first pick-order assign. */
@PostMapping("/workbench/release")
fun releaseJobOrderForWorkbench(@Valid @RequestBody request: JobOrderCommonActionRequest): MessageResponse {
return joWorkbenchReleaseService.releaseJobOrderForWorkbench(request)
}

@PostMapping("/set-hidden")
fun setJobOrderHidden(@Valid @RequestBody request: SetJobOrderHiddenRequest): MessageResponse {
return jobOrderService.setJobOrderHidden(request)
@@ -128,6 +147,15 @@ class JobOrderController(
): MessageResponse {
return joPickOrderService.assignJobOrderPickOrderToUser(pickOrderId, userId)
}

/** Workbench: assign + prime SPL/SOL when release was deferred (see [JoWorkbenchMainService]). */
@PostMapping("/workbench/assign-job-order-pick-order/{pickOrderId}/{userId}")
fun assignJobOrderPickOrderToUserForWorkbench(
@PathVariable pickOrderId: Long,
@PathVariable userId: Long
): MessageResponse {
return joWorkbenchMainService.assignJobOrderPickOrderToUserForWorkbench(pickOrderId, userId)
}
@PostMapping("/unassign-job-order-pick-order/{pickOrderId}")
fun unAssignJobOrderPickOrderToUser(
@PathVariable pickOrderId: Long
@@ -241,6 +269,24 @@ fun recordSecondScanIssue(
): List<Map<String, Any?>> {
return joPickOrderService.getCompletedJobOrderPickOrders(completedDate)
}
@GetMapping("/job-order-pick-orders")
fun getJobOrderPickOrders(
@RequestParam(name = "date", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
date: LocalDate?,
@RequestParam(name = "status", required = false)
status: PickOrderStatus?,
): List<Map<String, Any?>> {
return joPickOrderService.getJobOrderPickOrders(date, status)
}

@GetMapping("/job-order-pick-order-lot-details/{pickOrderId}")
fun getJobOrderPickOrderLotDetails(
@PathVariable pickOrderId: Long,
@RequestParam(name = "status", required = false) status: String?,
): List<Map<String, Any?>> {
return joPickOrderService.getJobOrderPickOrderLotDetails(pickOrderId, status)
}
@GetMapping("/joForPrintQrCode/{date}")
fun getJoForPrintQrCode(@PathVariable date: String): List<JobOrderListForPrintQrCodeResponse> {
return joPickOrderService.getJobOrderListForPrintQrCode(LocalDate.parse(date))
@@ -264,6 +310,12 @@ fun recordSecondScanIssue(
fun getJobOrderLotsHierarchicalByPickOrderId(@PathVariable pickOrderId: Long): JobOrderLotsHierarchicalResponse {
return joPickOrderService.getJobOrderLotsHierarchicalByPickOrderId(pickOrderId)
}

/** Workbench: available qty uses in−out (matches scan-pick); stockouts include suggested SPL qty when matched. */
@GetMapping("/all-lots-hierarchical-by-pick-order-workbench/{pickOrderId}")
fun getJobOrderLotsHierarchicalByPickOrderIdWorkbench(@PathVariable pickOrderId: Long): JobOrderLotsHierarchicalResponse {
return joWorkbenchMainService.getJobOrderLotsHierarchicalByPickOrderIdWorkbench(pickOrderId)
}
@PostMapping("/update-jo-pick-order-handled-by")
fun updateJoPickOrderHandledBy(@Valid @RequestBody request: UpdateJoPickOrderHandledByRequest): MessageResponse {
try {


+ 17
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickOrderRepository.kt Ver ficheiro

@@ -179,4 +179,21 @@ fun findReleasedIdsForPickExecution(
@Param("status") status: PickOrderStatus,
@Param("excludedTypes") excludedTypes: List<PickOrderType>,
): List<Long>

@Query("""
SELECT po
FROM PickOrder po
JOIN po.jobOrder jo
WHERE po.deleted = false
AND po.jobOrder IS NOT NULL
AND (jo.isHidden = false OR jo.isHidden IS NULL)
AND jo.planStart >= :from
AND jo.planStart < :toExclusive
AND (:status IS NULL OR po.status = :status)
""")
fun findByJobOrderPlanStartOnDayAndOptionalStatus(
@Param("from") from: LocalDateTime,
@Param("toExclusive") toExclusive: LocalDateTime,
@Param("status") status: PickOrderStatus?,
): List<PickOrder>
}

+ 10
- 0
src/main/resources/db/changelog/changes/20260420_01_Enson/02_alter_stock_take.sql Ver ficheiro

@@ -0,0 +1,10 @@
--liquibase formatted sql

--changeset Enson:20260422-01
CREATE INDEX idx_jo_planstart_ishidden ON job_order (planStart, isHidden, id);

--changeset Enson:20260422-02
CREATE INDEX idx_po_status_deleted_joid ON pick_order (status, deleted, joId);

--changeset Enson:20260422-03
CREATE INDEX idx_jpo_pickorder_deleted_match ON jo_pick_order (pick_order_id, deleted, match_status);

Carregando…
Cancelar
Guardar