Просмотр исходного кода

revert/ just complete do pick order

master
CANCERYS\kw093 9 часов назад
Родитель
Сommit
f315e3d707
7 измененных файлов: 325 добавлений и 39 удалений
  1. +2
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecordRepository.kt
  2. +83
    -3
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt
  3. +14
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt
  4. +3
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/TicketReleaseTableResponse.kt
  5. +202
    -33
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  6. +19
    -1
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt
  7. +2
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SearchPickOrderRequest.kt

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecordRepository.kt Просмотреть файл

@@ -28,6 +28,8 @@ interface DoPickOrderRecordRepository : JpaRepository<DoPickOrderRecord, Long> {
fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn(storeId: String, requiredDeliveryDate: LocalDate, ticketStatus: List<DoPickOrderStatus>): List<DoPickOrderRecord>
fun findByHandledByAndTicketStatusAndDeletedFalse(handledBy: Long, ticketStatus: DoPickOrderStatus): List<DoPickOrderRecord>

fun findByTicketStatusAndDeletedFalse(ticketStatus: DoPickOrderStatus): List<DoPickOrderRecord>

@Query("SELECT d FROM DoPickOrderRecord d WHERE d.deleted = false ORDER BY d.ticketReleaseTime DESC NULLS LAST")
fun findAllByDeletedFalseOrderByTicketReleaseTimeDesc(): List<DoPickOrderRecord>



+ 83
- 3
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt Просмотреть файл

@@ -292,6 +292,84 @@ open class DoPickOrderService(
return doPickOrderRepository.findByPickOrderId(pickOrderId)
}

/**
* 撤銷使用者對 do_pick_order 的領取:清空 handled_by,狀態回到 pending,並解除關聯 pick_order 的 assignTo。
* 僅適用於尚未完成的單據。
*/
@Transactional
open fun revertUserAssignment(doPickOrderId: Long): MessageResponse {
val dpo = doPickOrderRepository.findById(doPickOrderId).orElse(null)
?: return MessageResponse(
id = null,
code = "NOT_FOUND",
name = null,
type = null,
message = "do_pick_order not found",
errorPosition = null,
entity = null,
)
if (dpo.ticketStatus == DoPickOrderStatus.completed) {
return MessageResponse(
id = dpo.id,
code = "INVALID_STATUS",
name = dpo.ticketNo,
type = null,
message = "Cannot revert a completed ticket",
errorPosition = null,
entity = null,
)
}
if (dpo.handledBy == null) {
return MessageResponse(
id = dpo.id,
code = "NOT_ASSIGNED",
name = dpo.ticketNo,
type = null,
message = "No user assignment to revert",
errorPosition = null,
entity = null,
)
}

dpo.handledBy = null
dpo.handlerName = null
dpo.ticketStatus = DoPickOrderStatus.pending
dpo.ticketReleaseTime = null
doPickOrderRepository.save(dpo)

val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrderId)
lines.forEach { line ->
val pid = line.pickOrderId ?: return@forEach
pickOrderRepository.findById(pid).ifPresent { po ->
po.assignTo = null
po.status = PickOrderStatus.PENDING
pickOrderRepository.save(po)
}
val records = doPickOrderRecordRepository.findByPickOrderId(pid)
records.forEach { r ->
if (r.ticketStatus != DoPickOrderStatus.completed) {
r.handledBy = null
r.handlerName = null
r.ticketStatus = DoPickOrderStatus.pending
r.ticketReleaseTime = null
}
}
if (records.isNotEmpty()) {
doPickOrderRecordRepository.saveAll(records)
}
}

return MessageResponse(
id = dpo.id,
code = "SUCCESS",
name = dpo.ticketNo,
type = null,
message = "Assignment reverted",
errorPosition = null,
entity = null,
)
}

open fun updateDoOrderIdForPickOrder(pickOrderId: Long, doOrderId: Long): List<DoPickOrder> {
val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
doPickOrders.forEach {
@@ -672,12 +750,13 @@ open class DoPickOrderService(
shopName = doPickOrder.shopName,
requiredDeliveryDate = doPickOrder.requiredDeliveryDate,
handlerName = doPickOrder.handlerName,
numberOfFGItems = countFGItems(doPickOrder)
numberOfFGItems = countFGItems(doPickOrder),
isActiveDoPickOrder = true,
)
}

val doPickOrderRecordResponses = doPickOrderRecords.map { record ->
TicketReleaseTableResponse(
TicketReleaseTableResponse(
id = record.id,
storeId = record.storeId,
ticketNo = record.ticketNo,
@@ -698,7 +777,8 @@ open class DoPickOrderService(
shopName = record.shopName,
requiredDeliveryDate = record.requiredDeliveryDate,
handlerName = record.handlerName,
numberOfFGItems = countFGItemsFromRecord(record)
numberOfFGItems = countFGItemsFromRecord(record),
isActiveDoPickOrder = false,
)
}



+ 14
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/DoPickOrderController.kt Просмотреть файл

@@ -50,7 +50,8 @@ class DoPickOrderController(
private val doReleaseCoordinatorService: DoReleaseCoordinatorService,
private val doPickOrderLineRepository: DoPickOrderLineRepository,
private val doPickOrderQueryService: DoPickOrderQueryService,
private val doPickOrderAssignmentService: DoPickOrderAssignmentService
private val doPickOrderAssignmentService: DoPickOrderAssignmentService,
private val pickOrderService: PickOrderService,
) {
@PostMapping("/assign-by-store")
fun assignPickOrderByStore(@RequestBody request: AssignByStoreRequest): MessageResponse {
@@ -134,4 +135,16 @@ class DoPickOrderController(
return doPickOrderService.getTruckScheduleDashboard(date ?: LocalDate.now())
}

/** 強制完成仍處於進行中的 do_pick_order(僅改狀態,不調整已揀數量) */
@PostMapping("/force-complete/{doPickOrderId}")
fun forceCompleteDoPickOrder(@PathVariable doPickOrderId: Long): MessageResponse {
return pickOrderService.forceCompleteDoPickOrder(doPickOrderId)
}

/** 撤銷使用者領取,使單據可再次分配 */
@PostMapping("/revert-assignment/{doPickOrderId}")
fun revertDoPickOrderAssignment(@PathVariable doPickOrderId: Long): MessageResponse {
return doPickOrderService.revertUserAssignment(doPickOrderId)
}

}

+ 3
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/TicketReleaseTableResponse.kt Просмотреть файл

@@ -25,7 +25,9 @@ data class TicketReleaseTableResponse(
val shopName: String?,
val requiredDeliveryDate: LocalDate?,
val handlerName: String?,
val numberOfFGItems: Int = 0
val numberOfFGItems: Int = 0,
/** true:資料來自進行中的 do_pick_order,id 可作 force-complete / revert-assignment;false:來自已歸檔的 record */
val isActiveDoPickOrder: Boolean = false,
)
data class TruckInfoDto(
val id: Long?,


+ 202
- 33
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt Просмотреть файл

@@ -1454,6 +1454,157 @@ open class PickOrderService(
}
}

/**
* 強制完成 do_pick_order:僅將未完成的 pick_order / pick_order_line 標記為完成(不修改 stock_out_line 數量),
* 並執行與正常完成相同的歸檔(do_pick_order → do_pick_order_record)與 delivery_order 狀態更新。
* 僅適用於仍存在于 do_pick_order 表中的單據。
*/
@Transactional(rollbackFor = [Exception::class])
open fun forceCompleteDoPickOrder(doPickOrderId: Long): MessageResponse {
val dpo = doPickOrderRepository.findById(doPickOrderId).orElse(null)
?: return MessageResponse(
id = null,
name = "Do pick order not found",
code = "NOT_FOUND",
type = "doPickOrder",
message = "do_pick_order not found (may already be archived)",
errorPosition = null,
)
if (dpo.ticketStatus == DoPickOrderStatus.completed) {
return MessageResponse(
id = dpo.id,
name = dpo.ticketNo,
code = "ALREADY_COMPLETED",
type = "doPickOrder",
message = "Do pick order already completed",
errorPosition = null,
)
}
if (dpo.ticketStatus !in listOf(DoPickOrderStatus.pending, DoPickOrderStatus.released)) {
return MessageResponse(
id = dpo.id,
name = dpo.ticketNo,
code = "INVALID_STATUS",
type = "doPickOrder",
message = "Invalid ticket status for force complete",
errorPosition = null,
)
}

val dpoLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrderId)
val pickOrderIds = dpoLines.mapNotNull { it.pickOrderId }.distinct()
if (pickOrderIds.isEmpty()) {
return MessageResponse(
id = dpo.id,
name = dpo.ticketNo,
code = "NO_PICK_ORDERS",
type = "doPickOrder",
message = "No pick orders linked to this do_pick_order",
errorPosition = null,
)
}

for (pickOrderId in pickOrderIds) {
val pols = pickOrderLineRepository.findByPickOrderId(pickOrderId).filter { !it.deleted }
pols.forEach { pol ->
pol.status = PickOrderLineStatus.COMPLETED
}
if (pols.isNotEmpty()) {
pickOrderLineRepository.saveAll(pols)
}
val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: continue
pickOrder.status = PickOrderStatus.COMPLETED
pickOrder.completeDate = LocalDateTime.now()
pickOrderRepository.save(pickOrder)

if (pickOrder.jobOrder != null) {
val joPickOrders = joPickOrderRepository.findByPickOrderId(pickOrderId)
joPickOrders.forEach { it.ticketCompleteTime = LocalDateTime.now() }
if (joPickOrders.isNotEmpty()) {
joPickOrderRepository.saveAll(joPickOrders)
}
val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(pickOrderId)
joPickOrderRecords.forEach { it.ticketCompleteTime = LocalDateTime.now() }
if (joPickOrderRecords.isNotEmpty()) {
joPickOrderRecordRepository.saveAll(joPickOrderRecords)
}
}
}

moveDoPickOrderToCompletedRecordAfterForce(dpo)

return MessageResponse(
id = dpo.id,
code = "SUCCESS",
name = dpo.ticketNo,
type = "doPickOrder",
message = "Force completed",
errorPosition = null,
entity = null,
)
}

private fun moveDoPickOrderToCompletedRecordAfterForce(dpo: DoPickOrder) {
val doOrderIdForDelivery = dpo.doOrderId
val prefix = "DN"
val midfix = CodeGenerator.DEFAULT_MIDFIX
val latestCode = doPickOrderRecordRepository.findLatestDeliveryNoteCodeByPrefix("$prefix-$midfix")
val deliveryNoteCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode)

val dpoRecord = DoPickOrderRecord(
recordId = dpo.id,
storeId = dpo.storeId ?: "",
ticketNo = dpo.ticketNo ?: "",
ticketStatus = DoPickOrderStatus.completed,
truckId = dpo.truckId,
truckDepartureTime = dpo.truckDepartureTime,
pickOrderId = dpo.pickOrderId,
doOrderId = dpo.doOrderId,
ticketReleaseTime = dpo.ticketReleaseTime,
shopId = dpo.shopId,
handlerName = dpo.handlerName,
handledBy = dpo.handledBy,
ticketCompleteDateTime = LocalDateTime.now(),
truckLanceCode = dpo.truckLanceCode,
shopCode = dpo.shopCode,
shopName = dpo.shopName,
requiredDeliveryDate = dpo.requiredDeliveryDate,
pickOrderCode = dpo.pickOrderCode,
deliveryOrderCode = dpo.deliveryOrderCode,
deliveryNoteCode = deliveryNoteCode,
loadingSequence = dpo.loadingSequence,
)
val savedHeader = doPickOrderRecordRepository.save(dpoRecord)

val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!)
val lineRecords = lines.map { l: DoPickOrderLine ->
DoPickOrderLineRecord().apply {
this.recordId = l.id
this.doPickOrderId = savedHeader.recordId
this.pickOrderId = l.pickOrderId
this.doOrderId = l.doOrderId
this.pickOrderCode = l.pickOrderCode
this.deliveryOrderCode = l.deliveryOrderCode
this.status = l.status
}
}
if (lineRecords.isNotEmpty()) {
doPickOrderLineRecordRepository.saveAll(lineRecords)
}
if (lines.isNotEmpty()) {
doPickOrderLineRepository.deleteAll(lines)
}
doPickOrderRepository.delete(dpo)

doOrderIdForDelivery?.let { doId ->
val deliveryOrder = deliveryOrderRepository.findByIdAndDeletedIsFalse(doId)
if (deliveryOrder != null && deliveryOrder.status != DeliveryOrderStatus.COMPLETED) {
deliveryOrder.status = DeliveryOrderStatus.COMPLETED
deliveryOrderRepository.save(deliveryOrder)
}
}
}

@Transactional(rollbackFor = [java.lang.Exception::class])
open fun checkAndCompletePickOrderByConsoCode(consoCode: String): MessageResponse {
try {
@@ -4100,72 +4251,73 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto
return null
}

open fun getCompletedDoPickOrders(
userId: Long,
private fun mapCompletedDoPickOrders(
baseRecords: List<DoPickOrderRecord>,
request: GetCompletedDoPickOrdersRequest
): List<CompletedDoPickOrderResponse> {
return try {
val normalizedTargetDate = request.targetDate
?.takeIf { it.isNotBlank() }
?.replace("-", "")
println("request.targetDate: $request.targetDate")
println("request.shopName: $request.shopName")
println("request.deliveryNoteCode: $request.deliveryNoteCode")
val completedRecords = doPickOrderRecordRepository
.findByHandledByAndTicketStatusAndDeletedFalse(userId, DoPickOrderStatus.completed)
.filter { record ->
val matchTargetDate = normalizedTargetDate.isNullOrBlank() ||
record.ticketCompleteDateTime
?.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
?.contains(normalizedTargetDate, ignoreCase = true) == true
val matchShop = request.shopName.isNullOrBlank() ||
record.shopName?.contains(request.shopName, ignoreCase = true) == true
val matchDeliveryNoteCode = request.deliveryNoteCode.isNullOrBlank() ||
record.deliveryNoteCode?.contains(request.deliveryNoteCode, ignoreCase = true) == true
matchTargetDate && matchShop && matchDeliveryNoteCode
}
.filter { (it.recordId ?: 0L) > 0 }
?.takeIf { it.isNotBlank() }
?.replace("-", "")
println("request.targetDate: ${request.targetDate}")
println("request.shopName: ${request.shopName}")
println("request.deliveryNoteCode: ${request.deliveryNoteCode}")
val completedRecords = baseRecords
.filter { record ->
val matchTargetDate = normalizedTargetDate.isNullOrBlank() ||
record.ticketCompleteDateTime
?.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
?.contains(normalizedTargetDate, ignoreCase = true) == true
val matchShop = request.shopName.isNullOrBlank() ||
record.shopName?.contains(request.shopName, ignoreCase = true) == true
val matchDeliveryNoteCode = request.deliveryNoteCode.isNullOrBlank() ||
record.deliveryNoteCode?.contains(request.deliveryNoteCode, ignoreCase = true) == true
val matchTruck = request.truckLanceCode.isNullOrBlank() ||
record.truckLanceCode?.contains(request.truckLanceCode!!, ignoreCase = true) == true
matchTargetDate && matchShop && matchDeliveryNoteCode && matchTruck
}
.filter { (it.recordId ?: 0L) > 0 }

if (completedRecords.isEmpty()) {
return emptyList()
}
val recordIds = completedRecords.mapNotNull { it.recordId }.distinct()
val lineRecords = doPickOrderLineRecordRepository.findByDoPickOrderIdInAndDeletedFalse(recordIds)
val lineRecordsByRecordId = lineRecords.groupBy { it.doPickOrderId }
val filteredRecords = completedRecords.filter { record ->
val lines = lineRecordsByRecordId[record.recordId] ?: emptyList()
val matchTargetDate = normalizedTargetDate.isNullOrBlank() ||
record.ticketCompleteDateTime
?.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
?.contains(normalizedTargetDate, ignoreCase = true) == true
matchTargetDate
}.sortedByDescending { it.ticketCompleteDateTime }
if (filteredRecords.isEmpty()) {
return emptyList()
}
val allPickOrderIds = lineRecords.mapNotNull { it.pickOrderId }.distinct()
val pickOrdersById = pickOrderRepository.findAllById(allPickOrderIds).associateBy { it.id!! }
val allDeliveryOrderIds = lineRecords.mapNotNull { it.doOrderId }.distinct()
val deliveryOrdersById = deliveryOrderRepository.findAllById(allDeliveryOrderIds).associateBy { it.id!! }
filteredRecords.map { record ->
val lines = lineRecordsByRecordId[record.recordId] ?: emptyList()
val pickOrderIds = lines.mapNotNull { it.pickOrderId }.distinct()
val pickOrderCodes = lines.mapNotNull { it.pickOrderCode }.distinct()
val deliveryOrderIds = lines.mapNotNull { it.doOrderId }.distinct()
val deliveryNos = lines.mapNotNull { it.deliveryOrderCode }.distinct()
val numberOfCartons = pickOrderIds.sumOf { id ->
pickOrdersById[id]?.pickOrderLines?.count { !it.deleted } ?: 0
}
val completedDateStr = record.ticketCompleteDateTime
?.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
?.format(DateTimeFormatter.ofPattern("yyyyMMdd"))

val representativePickOrder = pickOrderIds.firstOrNull()?.let { pickOrdersById[it] }
val representativeDelivery = deliveryOrderIds.firstOrNull()?.let { deliveryOrdersById[it] }
@@ -4183,7 +4335,7 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto
shopName = record.shopName,
truckLanceCode = record.truckLanceCode,
departureTime = record.truckDepartureTime,
deliveryNoteCode=record.deliveryNoteCode,
deliveryNoteCode = record.deliveryNoteCode,
pickOrderIds = pickOrderIds,
pickOrderCodes = pickOrderCodes,
deliveryOrderIds = deliveryOrderIds,
@@ -4203,12 +4355,29 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto
)
}
} catch (e: Exception) {
println("❌ Error in getCompletedDoPickOrders: ${e.message}")
println("❌ Error in mapCompletedDoPickOrders: ${e.message}")
e.printStackTrace()
emptyList()
}
}

open fun getCompletedDoPickOrders(
userId: Long,
request: GetCompletedDoPickOrdersRequest
): List<CompletedDoPickOrderResponse> {
val baseRecords = doPickOrderRecordRepository
.findByHandledByAndTicketStatusAndDeletedFalse(userId, DoPickOrderStatus.completed)
return mapCompletedDoPickOrders(baseRecords, request)
}

open fun getCompletedDoPickOrdersAll(
request: GetCompletedDoPickOrdersRequest
): List<CompletedDoPickOrderResponse> {
val baseRecords = doPickOrderRecordRepository
.findByTicketStatusAndDeletedFalse(DoPickOrderStatus.completed)
return mapCompletedDoPickOrders(baseRecords, request)
}


private fun numToBigDecimal(n: Number?): BigDecimal {
return when (n) {


+ 19
- 1
src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt Просмотреть файл

@@ -300,14 +300,32 @@ fun getCompletedDoPickOrders(
@RequestParam(required = false) shopName: String?,
@RequestParam(required = false) targetDate: String?,
@RequestParam(required = false) deliveryNoteCode: String?,
@RequestParam(required = false) truckLanceCode: String?,
): List<CompletedDoPickOrderResponse> {
val request = GetCompletedDoPickOrdersRequest(
targetDate = targetDate,
shopName = shopName,
deliveryNoteCode = deliveryNoteCode
deliveryNoteCode = deliveryNoteCode,
truckLanceCode = truckLanceCode,
)
return pickOrderService.getCompletedDoPickOrders(userId, request)
}

@GetMapping("/completed-do-pick-orders-all")
fun getCompletedDoPickOrdersAll(
@RequestParam(required = false) shopName: String?,
@RequestParam(required = false) targetDate: String?,
@RequestParam(required = false) deliveryNoteCode: String?,
@RequestParam(required = false) truckLanceCode: String?,
): List<CompletedDoPickOrderResponse> {
val request = GetCompletedDoPickOrdersRequest(
targetDate = targetDate,
shopName = shopName,
deliveryNoteCode = deliveryNoteCode,
truckLanceCode = truckLanceCode,
)
return pickOrderService.getCompletedDoPickOrdersAll(request)
}


}

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SearchPickOrderRequest.kt Просмотреть файл

@@ -19,6 +19,8 @@ data class GetCompletedDoPickOrdersRequest(
val targetDate: String? = null,
val shopName: String? = null,
val deliveryNoteCode: String? = null,
/** 卡車 / 車道代碼(模糊匹配 truck_lance_code) */
val truckLanceCode: String? = null,
)
data class CompletedDoPickOrderResponse(
val id: Long,


Загрузка…
Отмена
Сохранить