From a70a78db4b130c532c2ee3ec666769fbfbed0940 Mon Sep 17 00:00:00 2001 From: "kelvin.yau" Date: Mon, 13 Apr 2026 17:05:15 +0800 Subject: [PATCH] no message --- .../pickOrder/service/PickOrderService.kt | 129 ++++++++++-------- .../stock/service/StockOutLineService.kt | 76 +++++++---- ...01_add_unique_index_delivery_note_code.sql | 5 + 3 files changed, 123 insertions(+), 87 deletions(-) create mode 100644 src/main/resources/db/changelog/changes/20260413_01_codex/01_add_unique_index_delivery_note_code.sql diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index 0f8f380..2291c88 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -39,6 +39,7 @@ import com.ffii.fpsms.modules.stock.web.model.SuggestedPickLotForPoRequest import com.ffii.fpsms.modules.user.entity.UserRepository import com.ffii.fpsms.modules.user.service.UserService import org.springframework.data.domain.PageRequest +import org.springframework.dao.DataIntegrityViolationException import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.stereotype.Service @@ -1343,36 +1344,31 @@ open class PickOrderService( } println(" All pick orders in this do_pick_order are completed, moving to record table") - val prefix = "DN" - val midfix = CodeGenerator.DEFAULT_MIDFIX - val latestCode = doPickOrderRecordRepository.findLatestDeliveryNoteCodeByPrefix("${prefix}-${midfix}") - val deliveryNoteCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode) - // 2) 先复制 do_pick_order -> do_pick_order_record - val dpoRecord = DoPickOrderRecord( - recordId =dpo.id, - storeId = dpo.storeId ?: "", - ticketNo = dpo.ticketNo ?: "", - ticketStatus = DoPickOrderStatus.completed, - truckId = dpo.truckId, - - truckDepartureTime = dpo.truckDepartureTime, - shopId = dpo.shopId, - handledBy = dpo.handledBy, - handlerName = dpo.handlerName, - doOrderId = dpo.doOrderId, - pickOrderCode = dpo.pickOrderCode, - deliveryOrderCode = dpo.deliveryOrderCode, - deliveryNoteCode = deliveryNoteCode, - loadingSequence = dpo.loadingSequence, - ticketReleaseTime = dpo.ticketReleaseTime, - ticketCompleteDateTime = java.time.LocalDateTime.now(), - truckLanceCode = dpo.truckLanceCode, - shopCode = dpo.shopCode, - shopName = dpo.shopName, - requiredDeliveryDate = dpo.requiredDeliveryDate - ) - val savedHeader = doPickOrderRecordRepository.save(dpoRecord) + val savedHeader = saveDoPickOrderRecordWithDnRetry { deliveryNoteCode -> + DoPickOrderRecord( + recordId = dpo.id, + storeId = dpo.storeId ?: "", + ticketNo = dpo.ticketNo ?: "", + ticketStatus = DoPickOrderStatus.completed, + truckId = dpo.truckId, + truckDepartureTime = dpo.truckDepartureTime, + shopId = dpo.shopId, + handledBy = dpo.handledBy, + handlerName = dpo.handlerName, + doOrderId = dpo.doOrderId, + pickOrderCode = dpo.pickOrderCode, + deliveryOrderCode = dpo.deliveryOrderCode, + deliveryNoteCode = deliveryNoteCode, + loadingSequence = dpo.loadingSequence, + ticketReleaseTime = dpo.ticketReleaseTime, + ticketCompleteDateTime = java.time.LocalDateTime.now(), + truckLanceCode = dpo.truckLanceCode, + shopCode = dpo.shopCode, + shopName = dpo.shopName, + requiredDeliveryDate = dpo.requiredDeliveryDate + ) + } // 3) 复制行 do_pick_order_line -> do_pick_order_line_record val lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!) @@ -1548,35 +1544,31 @@ open class PickOrderService( 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 savedHeader = saveDoPickOrderRecordWithDnRetry { deliveryNoteCode -> + 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 lines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!) val lineRecords = lines.map { l: DoPickOrderLine -> @@ -1607,6 +1599,27 @@ open class PickOrderService( } } + private fun saveDoPickOrderRecordWithDnRetry( + maxAttempts: Int = 3, + buildRecord: (String) -> DoPickOrderRecord + ): DoPickOrderRecord { + val prefix = "DN" + repeat(maxAttempts) { attempt -> + val midfix = LocalDate.now().format(CodeGenerator.DEFAULT_FORMATTER) + val latestCode = doPickOrderRecordRepository.findLatestDeliveryNoteCodeByPrefix("$prefix-$midfix") + val deliveryNoteCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode) + val dpoRecord = buildRecord(deliveryNoteCode) + try { + return doPickOrderRecordRepository.save(dpoRecord) + } catch (e: DataIntegrityViolationException) { + if (attempt == maxAttempts - 1) { + throw e + } + } + } + throw IllegalStateException("Failed to generate unique delivery note code after $maxAttempts attempts") + } + @Transactional(rollbackFor = [java.lang.Exception::class]) open fun checkAndCompletePickOrderByConsoCode(consoCode: String): MessageResponse { try { diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt index b047c0f..146cd97 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt @@ -10,6 +10,7 @@ import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus import com.ffii.fpsms.modules.stock.entity.* import com.ffii.fpsms.modules.stock.entity.projection.StockOutLineInfo import com.ffii.fpsms.modules.stock.web.model.* +import org.springframework.dao.DataIntegrityViolationException import org.springframework.data.repository.query.Param import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -483,36 +484,32 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { dpo.ticketCompleteDateTime = LocalDateTime.now() doPickOrderRepository.save(dpo) - val prefix = "DN" - val midfix = CodeGenerator.DEFAULT_MIDFIX - val latestCode = doPickOrderRecordRepository.findLatestDeliveryNoteCodeByPrefix("${prefix}-${midfix}") - val deliveryNoteCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode) - // 4) 可选:复制 header 到 record 表,复制 lines 到 line_record,再删除原有行/头(与你在 PickOrderService.completeStockOut 的做法保持一致) - val dpoRecord = DoPickOrderRecord( - recordId =dpoId, - storeId = dpo.storeId ?: "", - ticketNo = dpo.ticketNo ?: "", - ticketStatus = DoPickOrderStatus.completed, - truckId = dpo.truckId, - pickOrderId = dpo.pickOrderId, - truckDepartureTime = dpo.truckDepartureTime, - shopId = dpo.shopId, - handledBy = dpo.handledBy, - handlerName = dpo.handlerName, - doOrderId = dpo.doOrderId, - pickOrderCode = dpo.pickOrderCode, - deliveryOrderCode = dpo.deliveryOrderCode, - deliveryNoteCode = deliveryNoteCode, - loadingSequence = dpo.loadingSequence, - ticketReleaseTime = dpo.ticketReleaseTime, - ticketCompleteDateTime = LocalDateTime.now(), - truckLanceCode = dpo.truckLanceCode, - shopCode = dpo.shopCode, - shopName = dpo.shopName, - requiredDeliveryDate = dpo.requiredDeliveryDate - ) - val savedHeader = doPickOrderRecordRepository.save(dpoRecord) + val savedHeader = saveDoPickOrderRecordWithDnRetry { deliveryNoteCode -> + DoPickOrderRecord( + recordId = dpoId, + storeId = dpo.storeId ?: "", + ticketNo = dpo.ticketNo ?: "", + ticketStatus = DoPickOrderStatus.completed, + truckId = dpo.truckId, + pickOrderId = dpo.pickOrderId, + truckDepartureTime = dpo.truckDepartureTime, + shopId = dpo.shopId, + handledBy = dpo.handledBy, + handlerName = dpo.handlerName, + doOrderId = dpo.doOrderId, + pickOrderCode = dpo.pickOrderCode, + deliveryOrderCode = dpo.deliveryOrderCode, + deliveryNoteCode = deliveryNoteCode, + loadingSequence = dpo.loadingSequence, + ticketReleaseTime = dpo.ticketReleaseTime, + ticketCompleteDateTime = LocalDateTime.now(), + truckLanceCode = dpo.truckLanceCode, + shopCode = dpo.shopCode, + shopName = dpo.shopName, + requiredDeliveryDate = dpo.requiredDeliveryDate + ) + } val lineRecords = allLines.map { l -> DoPickOrderLineRecord().apply { @@ -590,6 +587,27 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { entity = lineInfoList, ) } + + private fun saveDoPickOrderRecordWithDnRetry( + maxAttempts: Int = 3, + buildRecord: (String) -> DoPickOrderRecord + ): DoPickOrderRecord { + val prefix = "DN" + repeat(maxAttempts) { attempt -> + val midfix = LocalDate.now().format(CodeGenerator.DEFAULT_FORMATTER) + val latestCode = doPickOrderRecordRepository.findLatestDeliveryNoteCodeByPrefix("$prefix-$midfix") + val deliveryNoteCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode) + val dpoRecord = buildRecord(deliveryNoteCode) + try { + return doPickOrderRecordRepository.save(dpoRecord) + } catch (e: DataIntegrityViolationException) { + if (attempt == maxAttempts - 1) { + throw e + } + } + } + throw IllegalStateException("Failed to generate unique delivery note code after $maxAttempts attempts") + } private fun completeDoForPickOrder(pickOrderId: Long) { // 1) 原表 do_pick_order → completed val dpos = doPickOrderRepository.findByPickOrderId(pickOrderId) diff --git a/src/main/resources/db/changelog/changes/20260413_01_codex/01_add_unique_index_delivery_note_code.sql b/src/main/resources/db/changelog/changes/20260413_01_codex/01_add_unique_index_delivery_note_code.sql new file mode 100644 index 0000000..c60ba0e --- /dev/null +++ b/src/main/resources/db/changelog/changes/20260413_01_codex/01_add_unique_index_delivery_note_code.sql @@ -0,0 +1,5 @@ +-- liquibase formatted sql +-- changeset codex:add_unique_index_delivery_note_code_20260413 + +ALTER TABLE `fpsmsdb`.`do_pick_order_record` +ADD CONSTRAINT `uk_do_pick_order_record_delivery_note_code` UNIQUE (`deliveryNoteCode`);