| @@ -0,0 +1,33 @@ | |||||
| --- | |||||
| description: Prevent double-click / duplicate API calls from frontend UI | |||||
| alwaysApply: true | |||||
| --- | |||||
| # Prevent duplicated API calls (frontend) | |||||
| When wiring UI actions (buttons, row actions, dialogs) to backend APIs, **always prevent double submission**. Relying on `setState` + `disabled` alone is not sufficient because rapid double-click can fire twice before React re-renders. | |||||
| - **Must**: add an **in-flight lock** (e.g. `useRef(false)`) and early-return if already running. | |||||
| - **Must**: keep the UI disabled/loading (`disabled={isLoading}`) for user feedback. | |||||
| - **Must**: clear the lock in `finally` so it always releases. | |||||
| - **Should**: if the same endpoint can be triggered from multiple places, consider a shared “single-flight” helper (dedupe by `method+url+body` key). | |||||
| Example pattern: | |||||
| ```tsx | |||||
| const inFlightRef = useRef(false); | |||||
| const [isSaving, setIsSaving] = useState(false); | |||||
| const onSave = async () => { | |||||
| if (inFlightRef.current) return; | |||||
| inFlightRef.current = true; | |||||
| setIsSaving(true); | |||||
| try { | |||||
| await doRequest(); | |||||
| } finally { | |||||
| setIsSaving(false); | |||||
| inFlightRef.current = false; | |||||
| } | |||||
| }; | |||||
| ``` | |||||
| @@ -12,6 +12,7 @@ import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderLineStatus | |||||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus | import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus | ||||
| import com.ffii.fpsms.modules.deliveryOrder.service.DeliveryOrderLineService | import com.ffii.fpsms.modules.deliveryOrder.service.DeliveryOrderLineService | ||||
| import com.ffii.fpsms.modules.deliveryOrder.service.DeliveryOrderService | import com.ffii.fpsms.modules.deliveryOrder.service.DeliveryOrderService | ||||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | |||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderLineRequest | import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderLineRequest | ||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderRequest | import com.ffii.fpsms.modules.deliveryOrder.web.models.SaveDeliveryOrderRequest | ||||
| import com.ffii.fpsms.modules.master.entity.ItemUom | import com.ffii.fpsms.modules.master.entity.ItemUom | ||||
| @@ -35,6 +36,7 @@ open class M18DeliveryOrderService( | |||||
| val apiCallerService: ApiCallerService, | val apiCallerService: ApiCallerService, | ||||
| val m18DataLogService: M18DataLogService, | val m18DataLogService: M18DataLogService, | ||||
| val deliveryOrderService: DeliveryOrderService, | val deliveryOrderService: DeliveryOrderService, | ||||
| val deliveryOrderRepository: DeliveryOrderRepository, | |||||
| val deliveryOrderLineService: DeliveryOrderLineService, | val deliveryOrderLineService: DeliveryOrderLineService, | ||||
| val itemsService: ItemsService, | val itemsService: ItemsService, | ||||
| val shopService: ShopService, | val shopService: ShopService, | ||||
| @@ -106,7 +108,6 @@ open class M18DeliveryOrderService( | |||||
| if (request.dDateEqual != null) { | if (request.dDateEqual != null) { | ||||
| shopPoConds += "=and=(${dDateEqualConds})" | shopPoConds += "=and=(${dDateEqualConds})" | ||||
| } | } | ||||
| logger.info("shopPoConds: ${shopPoConds}") | logger.info("shopPoConds: ${shopPoConds}") | ||||
| val shopPoParams = M18PurchaseOrderListRequest( | val shopPoParams = M18PurchaseOrderListRequest( | ||||
| @@ -153,18 +154,35 @@ open class M18DeliveryOrderService( | |||||
| open fun saveDeliveryOrders(request: M18CommonRequest): SyncResult { | open fun saveDeliveryOrders(request: M18CommonRequest): SyncResult { | ||||
| val deliveryOrdersWithType = getDeliveryOrdersWithType(request) | val deliveryOrdersWithType = getDeliveryOrdersWithType(request) | ||||
| return saveDeliveryOrdersWithPreparedList(deliveryOrdersWithType) | |||||
| return saveDeliveryOrdersWithPreparedList(deliveryOrdersWithType, syncIsEtra = false) | |||||
| } | } | ||||
| /** | /** | ||||
| * Sync a single M18 shop PO / delivery order by document [code], same search pattern as | * Sync a single M18 shop PO / delivery order by document [code], same search pattern as | ||||
| * [com.ffii.fpsms.m18.service.M18PurchaseOrderService.savePurchaseOrderByCode]. | * [com.ffii.fpsms.m18.service.M18PurchaseOrderService.savePurchaseOrderByCode]. | ||||
| * | |||||
| * @param isEtraSync when true, persist local `delivery_order.isEtra=true` (manual DO(加單) sync). | |||||
| * No M18-side "加單" filtering is used. | |||||
| * @param newOnly when true, skip if a non-deleted local DO already exists with the same `code`. | |||||
| */ | */ | ||||
| open fun saveDeliveryOrderByCode(code: String): SyncResult { | |||||
| open fun saveDeliveryOrderByCode( | |||||
| code: String, | |||||
| isEtraSync: Boolean = false, | |||||
| newOnly: Boolean = false, | |||||
| ): SyncResult { | |||||
| if (newOnly && deliveryOrderRepository.existsByCodeAndDeletedIsFalse(code)) { | |||||
| return SyncResult( | |||||
| totalProcessed = 1, | |||||
| totalSuccess = 0, | |||||
| totalFail = 0, | |||||
| query = "skipped (newOnly=true): delivery_order.code already exists: $code", | |||||
| ) | |||||
| } | |||||
| val conds = "(code=equal=$code)" | |||||
| val searchRequest = M18PurchaseOrderListRequest( | val searchRequest = M18PurchaseOrderListRequest( | ||||
| stSearch = "po", | stSearch = "po", | ||||
| params = null, | params = null, | ||||
| conds = "(code=equal=$code)" | |||||
| conds = conds | |||||
| ) | ) | ||||
| val doListResponse = try { | val doListResponse = try { | ||||
| apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>( | apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>( | ||||
| @@ -183,20 +201,21 @@ open class M18DeliveryOrderService( | |||||
| totalProcessed = 1, | totalProcessed = 1, | ||||
| totalSuccess = 0, | totalSuccess = 0, | ||||
| totalFail = 1, | totalFail = 1, | ||||
| query = "code=equal=$code" | |||||
| query = conds | |||||
| ) | ) | ||||
| } | } | ||||
| val prepared = M18PurchaseOrderListResponseWithType( | val prepared = M18PurchaseOrderListResponseWithType( | ||||
| valuesWithType = mutableListOf(Pair(PurchaseOrderType.SHOP, doListResponse)), | valuesWithType = mutableListOf(Pair(PurchaseOrderType.SHOP, doListResponse)), | ||||
| query = "code=equal=$code" | |||||
| query = conds | |||||
| ) | ) | ||||
| return saveDeliveryOrdersWithPreparedList(prepared) | |||||
| return saveDeliveryOrdersWithPreparedList(prepared, syncIsEtra = isEtraSync) | |||||
| } | } | ||||
| private fun saveDeliveryOrdersWithPreparedList( | private fun saveDeliveryOrdersWithPreparedList( | ||||
| deliveryOrdersWithType: M18PurchaseOrderListResponseWithType? | |||||
| deliveryOrdersWithType: M18PurchaseOrderListResponseWithType?, | |||||
| syncIsEtra: Boolean = false, | |||||
| ): SyncResult { | ): SyncResult { | ||||
| logger.info("--------------------------------------------Start - Saving M18 Delivery Order--------------------------------------------") | logger.info("--------------------------------------------Start - Saving M18 Delivery Order--------------------------------------------") | ||||
| @@ -283,7 +302,8 @@ open class M18DeliveryOrderService( | |||||
| m18DataLogId = saveM18DeliveryOrderLog.id, | m18DataLogId = saveM18DeliveryOrderLog.id, | ||||
| handlerId = null, | handlerId = null, | ||||
| m18BeId = mainpo.beId, | m18BeId = mainpo.beId, | ||||
| deleted = mainpo.udfIsVoid == true | |||||
| deleted = mainpo.udfIsVoid == true, | |||||
| isEtra = syncIsEtra, | |||||
| ) | ) | ||||
| val saveDeliveryOrderResponse = | val saveDeliveryOrderResponse = | ||||
| @@ -72,7 +72,14 @@ class M18TestController ( | |||||
| @GetMapping("/test/do-by-code") | @GetMapping("/test/do-by-code") | ||||
| fun testSyncDoByCode(@RequestParam code: String): SyncResult { | fun testSyncDoByCode(@RequestParam code: String): SyncResult { | ||||
| return m18DeliveryOrderService.saveDeliveryOrderByCode(code) | |||||
| return m18DeliveryOrderService.saveDeliveryOrderByCode(code, isEtraSync = false) | |||||
| } | |||||
| /** DO(加單):手動按 code 同步,並寫入本地 [DeliveryOrder.isEtra]=true(不做 M18 端加單條件過濾) */ | |||||
| @GetMapping("/test/do-by-code-extra") | |||||
| fun testSyncDoByCodeExtra(@RequestParam code: String): SyncResult { | |||||
| // 加單 tab: only sync when it's a NEW order (not existing in local system) | |||||
| return m18DeliveryOrderService.saveDeliveryOrderByCode(code, isEtraSync = true, newOnly = true) | |||||
| } | } | ||||
| @GetMapping("/test/product-by-code") | @GetMapping("/test/product-by-code") | ||||
| @@ -491,6 +491,30 @@ open class SchedulerService( | |||||
| result = result, | result = result, | ||||
| start = currentTime | start = currentTime | ||||
| ) | ) | ||||
| // Extra DO sync window: after DO2, also sync ETA = today or tomorrow (normal sync; does NOT set isEtra). | |||||
| try { | |||||
| val extraStart = LocalDateTime.now() | |||||
| val requestExtra = M18CommonRequest( | |||||
| dDateFrom = today.format(dateTimeStringFormat), | |||||
| dDateTo = tmr.format(dateTimeStringFormat), | |||||
| ) | |||||
| val extraResult = m18DeliveryOrderService.saveDeliveryOrders(requestExtra) | |||||
| saveSyncLog( | |||||
| type = "DO2_EXTRA", | |||||
| status = "SUCCESS", | |||||
| result = extraResult, | |||||
| start = extraStart, | |||||
| ) | |||||
| } catch (e: Exception) { | |||||
| logger.error("DO2_EXTRA sync failed: ${e.message}", e) | |||||
| saveSyncLog( | |||||
| type = "DO2_EXTRA", | |||||
| status = "FAIL", | |||||
| error = e.message, | |||||
| start = LocalDateTime.now(), | |||||
| ) | |||||
| } | |||||
| } | } | ||||
| open fun getPostCompletedDnAndProcessGrn( | open fun getPostCompletedDnAndProcessGrn( | ||||
| @@ -62,4 +62,8 @@ open class DeliveryOrder: BaseEntity<Long>() { | |||||
| @Column(name = "m18BeId") | @Column(name = "m18BeId") | ||||
| open var m18BeId: Long? = null | open var m18BeId: Long? = null | ||||
| /** 加單:由 M18「加單」專用同步標記;一般 DO 為 false */ | |||||
| @Column(name = "isEtra", nullable = false) | |||||
| open var isEtra: Boolean = false | |||||
| } | } | ||||
| @@ -15,6 +15,8 @@ import com.ffii.fpsms.modules.deliveryOrder.web.models.* | |||||
| import com.ffii.fpsms.modules.deliveryOrder.entity.models.* | import com.ffii.fpsms.modules.deliveryOrder.entity.models.* | ||||
| @Repository | @Repository | ||||
| interface DeliveryOrderRepository : AbstractRepository<DeliveryOrder, Long> { | interface DeliveryOrderRepository : AbstractRepository<DeliveryOrder, Long> { | ||||
| fun existsByCodeAndDeletedIsFalse(code: String): Boolean | |||||
| @Query(""" | @Query(""" | ||||
| select d from DeliveryOrder d | select d from DeliveryOrder d | ||||
| where d.deleted = false | where d.deleted = false | ||||
| @@ -109,6 +111,7 @@ fun searchDoLite( | |||||
| and (:status is null or d.status = :status) | and (:status is null or d.status = :status) | ||||
| and (:etaStart is null or d.estimatedArrivalDate >= :etaStart) | and (:etaStart is null or d.estimatedArrivalDate >= :etaStart) | ||||
| and (:etaEnd is null or d.estimatedArrivalDate < :etaEnd) | and (:etaEnd is null or d.estimatedArrivalDate < :etaEnd) | ||||
| and (:isEtra is null or d.isEtra = :isEtra) | |||||
| order by d.id desc | order by d.id desc | ||||
| """) | """) | ||||
| fun searchDoLitePage( | fun searchDoLitePage( | ||||
| @@ -117,6 +120,7 @@ fun searchDoLitePage( | |||||
| @Param("status") status: DeliveryOrderStatus?, | @Param("status") status: DeliveryOrderStatus?, | ||||
| @Param("etaStart") etaStart: LocalDateTime?, | @Param("etaStart") etaStart: LocalDateTime?, | ||||
| @Param("etaEnd") etaEnd: LocalDateTime?, | @Param("etaEnd") etaEnd: LocalDateTime?, | ||||
| @Param("isEtra") isEtra: Boolean?, | |||||
| pageable: Pageable | pageable: Pageable | ||||
| ): Page<DeliveryOrderInfoLite> | ): Page<DeliveryOrderInfoLite> | ||||
| @@ -132,6 +136,7 @@ fun searchDoLitePage( | |||||
| and (:status is null or d.status = :status) | and (:status is null or d.status = :status) | ||||
| and (:etaStart is null or d.estimatedArrivalDate >= :etaStart) | and (:etaStart is null or d.estimatedArrivalDate >= :etaStart) | ||||
| and (:etaEnd is null or d.estimatedArrivalDate < :etaEnd) | and (:etaEnd is null or d.estimatedArrivalDate < :etaEnd) | ||||
| and (:isEtra is null or d.isEtra = :isEtra) | |||||
| and d.supplier is not null | and d.supplier is not null | ||||
| and d.supplier.code in :allowedSupplierCodes | and d.supplier.code in :allowedSupplierCodes | ||||
| order by d.id desc | order by d.id desc | ||||
| @@ -143,6 +148,7 @@ fun searchDoLitePageWithSupplierCodes( | |||||
| @Param("status") status: DeliveryOrderStatus?, | @Param("status") status: DeliveryOrderStatus?, | ||||
| @Param("etaStart") etaStart: LocalDateTime?, | @Param("etaStart") etaStart: LocalDateTime?, | ||||
| @Param("etaEnd") etaEnd: LocalDateTime?, | @Param("etaEnd") etaEnd: LocalDateTime?, | ||||
| @Param("isEtra") isEtra: Boolean?, | |||||
| @Param("allowedSupplierCodes") allowedSupplierCodes: List<String>, | @Param("allowedSupplierCodes") allowedSupplierCodes: List<String>, | ||||
| pageable: Pageable, | pageable: Pageable, | ||||
| ): Page<DeliveryOrderInfoLite> | ): Page<DeliveryOrderInfoLite> | ||||
| @@ -47,6 +47,9 @@ interface DeliveryOrderInfoLite { | |||||
| val supplierCode: String? | val supplierCode: String? | ||||
| @get:Value("#{target.shop?.addr3}") | @get:Value("#{target.shop?.addr3}") | ||||
| val shopAddress: String? | val shopAddress: String? | ||||
| @get:Value("#{target.isEtra}") | |||||
| val isEtra: Boolean | |||||
| } | } | ||||
| data class DeliveryOrderInfoLiteDto( | data class DeliveryOrderInfoLiteDto( | ||||
| val id: Long, | val id: Long, | ||||
| @@ -57,5 +60,6 @@ data class DeliveryOrderInfoLiteDto( | |||||
| val shopName: String?, | val shopName: String?, | ||||
| val supplierName: String?, | val supplierName: String?, | ||||
| val shopAddress: String?, | val shopAddress: String?, | ||||
| val truckLanceCode: String? | |||||
| val truckLanceCode: String?, | |||||
| val isEtra: Boolean = false, | |||||
| ) | ) | ||||
| @@ -147,6 +147,7 @@ open class DeliveryOrderService( | |||||
| pageSize: Int?, | pageSize: Int?, | ||||
| truckLanceCode: String?, | truckLanceCode: String?, | ||||
| floor: String? = null, | floor: String? = null, | ||||
| isEtra: Boolean? = null, | |||||
| ): RecordsRes<DeliveryOrderInfoLiteDto> { | ): RecordsRes<DeliveryOrderInfoLiteDto> { | ||||
| val page = (pageNum ?: 1) - 1 | val page = (pageNum ?: 1) - 1 | ||||
| @@ -168,6 +169,7 @@ open class DeliveryOrderService( | |||||
| status = statusEnum, | status = statusEnum, | ||||
| etaStart = etaStart, | etaStart = etaStart, | ||||
| etaEnd = etaEnd, | etaEnd = etaEnd, | ||||
| isEtra = isEtra, | |||||
| allowedSupplierCodes = allowedForFloor, | allowedSupplierCodes = allowedForFloor, | ||||
| pageable = PageRequest.of(0, 100_000), | pageable = PageRequest.of(0, 100_000), | ||||
| ) | ) | ||||
| @@ -245,7 +247,8 @@ open class DeliveryOrderService( | |||||
| shopName = info.shopName, | shopName = info.shopName, | ||||
| supplierName = info.supplierName, | supplierName = info.supplierName, | ||||
| shopAddress = info.shopAddress, | shopAddress = info.shopAddress, | ||||
| truckLanceCode = calculatedTruckLanceCode | |||||
| truckLanceCode = calculatedTruckLanceCode, | |||||
| isEtra = deliveryOrdersMap[info.id]?.isEtra ?: info.isEtra, | |||||
| ) | ) | ||||
| }.filter { dto -> | }.filter { dto -> | ||||
| val dtoTruckLanceCode = dto.truckLanceCode?.lowercase() ?: "" | val dtoTruckLanceCode = dto.truckLanceCode?.lowercase() ?: "" | ||||
| @@ -276,6 +279,7 @@ open class DeliveryOrderService( | |||||
| status = statusEnum, | status = statusEnum, | ||||
| etaStart = etaStart, | etaStart = etaStart, | ||||
| etaEnd = etaEnd, | etaEnd = etaEnd, | ||||
| isEtra = isEtra, | |||||
| allowedSupplierCodes = allowedSupplierCodes, | allowedSupplierCodes = allowedSupplierCodes, | ||||
| pageable = PageRequest.of(page.coerceAtLeast(0), size), | pageable = PageRequest.of(page.coerceAtLeast(0), size), | ||||
| ) | ) | ||||
| @@ -311,6 +315,7 @@ open class DeliveryOrderService( | |||||
| supplierName = info.supplierName, | supplierName = info.supplierName, | ||||
| shopAddress = info.shopAddress, | shopAddress = info.shopAddress, | ||||
| truckLanceCode = calculatedTruckLanceCode, | truckLanceCode = calculatedTruckLanceCode, | ||||
| isEtra = deliveryOrder?.isEtra ?: info.isEtra, | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -333,6 +338,7 @@ open class DeliveryOrderService( | |||||
| pageSize: Int?, | pageSize: Int?, | ||||
| truckLanceCode: String?, | truckLanceCode: String?, | ||||
| floor: String? = null, | floor: String? = null, | ||||
| isEtra: Boolean? = null, | |||||
| ): RecordsRes<DeliveryOrderInfoLiteDto> { | ): RecordsRes<DeliveryOrderInfoLiteDto> { | ||||
| val mode = TruckLaneSearchSpec.parse(truckLanceCode) | val mode = TruckLaneSearchSpec.parse(truckLanceCode) | ||||
| if (mode is TruckLaneSearchSpec.Mode.NoFilter) { | if (mode is TruckLaneSearchSpec.Mode.NoFilter) { | ||||
| @@ -345,6 +351,7 @@ open class DeliveryOrderService( | |||||
| pageSize, | pageSize, | ||||
| null, | null, | ||||
| floor, | floor, | ||||
| isEtra, | |||||
| ) | ) | ||||
| } | } | ||||
| val pageIdx = (pageNum ?: 1).coerceAtLeast(1) - 1 | val pageIdx = (pageNum ?: 1).coerceAtLeast(1) - 1 | ||||
| @@ -360,6 +367,7 @@ open class DeliveryOrderService( | |||||
| statusEnum = statusEnum, | statusEnum = statusEnum, | ||||
| etaStart = etaStart, | etaStart = etaStart, | ||||
| etaEnd = etaEnd, | etaEnd = etaEnd, | ||||
| isEtra = isEtra, | |||||
| allowedSupplierCodes = allowedSupplierCodesForFloor(floor), | allowedSupplierCodes = allowedSupplierCodesForFloor(floor), | ||||
| lanePredicate = lanePredicate, | lanePredicate = lanePredicate, | ||||
| ) | ) | ||||
| @@ -383,6 +391,7 @@ open class DeliveryOrderService( | |||||
| pageNum: Int?, | pageNum: Int?, | ||||
| pageSize: Int?, | pageSize: Int?, | ||||
| floor: String? = null, | floor: String? = null, | ||||
| isEtra: Boolean? = null, | |||||
| ): RecordsRes<DeliveryOrderInfoLiteDto> { | ): RecordsRes<DeliveryOrderInfoLiteDto> { | ||||
| val page = (pageNum ?: 1) - 1 | val page = (pageNum ?: 1) - 1 | ||||
| val size = pageSize ?: 10 | val size = pageSize ?: 10 | ||||
| @@ -397,6 +406,7 @@ open class DeliveryOrderService( | |||||
| status = statusEnum, | status = statusEnum, | ||||
| etaStart = etaStart, | etaStart = etaStart, | ||||
| etaEnd = etaEnd, | etaEnd = etaEnd, | ||||
| isEtra = isEtra, | |||||
| allowedSupplierCodes = allowedSupplierCodes, | allowedSupplierCodes = allowedSupplierCodes, | ||||
| pageable = PageRequest.of(0, 100_000), | pageable = PageRequest.of(0, 100_000), | ||||
| ) | ) | ||||
| @@ -435,6 +445,7 @@ open class DeliveryOrderService( | |||||
| supplierName = info.supplierName, | supplierName = info.supplierName, | ||||
| shopAddress = info.shopAddress, | shopAddress = info.shopAddress, | ||||
| truckLanceCode = calculatedTruckLanceCode, | truckLanceCode = calculatedTruckLanceCode, | ||||
| isEtra = deliveryOrdersMap[info.id]?.isEtra ?: info.isEtra, | |||||
| ) | ) | ||||
| }.filter { dto -> TruckLaneSearchSpec.isUnassignedResolvedLane(dto.truckLanceCode) } | }.filter { dto -> TruckLaneSearchSpec.isUnassignedResolvedLane(dto.truckLanceCode) } | ||||
| @@ -476,6 +487,7 @@ open class DeliveryOrderService( | |||||
| estimatedArrivalDate = deliveryOrder.estimatedArrivalDate, | estimatedArrivalDate = deliveryOrder.estimatedArrivalDate, | ||||
| completeDate = deliveryOrder.completeDate, | completeDate = deliveryOrder.completeDate, | ||||
| status = deliveryOrder.status?.value, | status = deliveryOrder.status?.value, | ||||
| isEtra = deliveryOrder.isEtra, | |||||
| deliveryOrderLines = deliveryOrder.deliveryOrderLines.map { line -> | deliveryOrderLines = deliveryOrder.deliveryOrderLines.map { line -> | ||||
| DoDetailLineResponse( | DoDetailLineResponse( | ||||
| id = line.id!!, | id = line.id!!, | ||||
| @@ -796,6 +808,7 @@ open class DeliveryOrderService( | |||||
| this.handler = handler | this.handler = handler | ||||
| m18BeId = request.m18BeId | m18BeId = request.m18BeId | ||||
| this.deleted = request.deleted | this.deleted = request.deleted | ||||
| isEtra = request.isEtra ?: false | |||||
| } | } | ||||
| val savedDeliveryOrder = deliveryOrderRepository.saveAndFlush(deliveryOrder).let { | val savedDeliveryOrder = deliveryOrderRepository.saveAndFlush(deliveryOrder).let { | ||||
| @@ -2091,6 +2104,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||||
| statusEnum: DeliveryOrderStatus?, | statusEnum: DeliveryOrderStatus?, | ||||
| etaStart: LocalDateTime?, | etaStart: LocalDateTime?, | ||||
| etaEnd: LocalDateTime?, | etaEnd: LocalDateTime?, | ||||
| isEtra: Boolean?, | |||||
| allowedSupplierCodes: List<String>, | allowedSupplierCodes: List<String>, | ||||
| lanePredicate: (String?) -> Boolean, | lanePredicate: (String?) -> Boolean, | ||||
| ): List<DeliveryOrderInfoLiteDto> { | ): List<DeliveryOrderInfoLiteDto> { | ||||
| @@ -2104,6 +2118,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||||
| status = statusEnum, | status = statusEnum, | ||||
| etaStart = etaStart, | etaStart = etaStart, | ||||
| etaEnd = etaEnd, | etaEnd = etaEnd, | ||||
| isEtra = isEtra, | |||||
| allowedSupplierCodes = allowedSupplierCodes, | allowedSupplierCodes = allowedSupplierCodes, | ||||
| pageable = PageRequest.of(dbPage, 500), | pageable = PageRequest.of(dbPage, 500), | ||||
| ) | ) | ||||
| @@ -2179,6 +2194,7 @@ val inventoryLotLine = illId?.let { inventoryLotLineMap[it] } | |||||
| supplierName = info.supplierName, | supplierName = info.supplierName, | ||||
| shopAddress = info.shopAddress, | shopAddress = info.shopAddress, | ||||
| truckLanceCode = calculatedTruckLanceCode, | truckLanceCode = calculatedTruckLanceCode, | ||||
| isEtra = deliveryOrder?.isEtra ?: info.isEtra, | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @@ -72,6 +72,7 @@ class DeliveryOrderController( | |||||
| pageSize = request.pageSize, | pageSize = request.pageSize, | ||||
| truckLanceCode = request.truckLanceCode, | truckLanceCode = request.truckLanceCode, | ||||
| floor = request.floor, | floor = request.floor, | ||||
| isEtra = request.isEtra, | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -88,6 +89,7 @@ class DeliveryOrderController( | |||||
| pageNum = request.pageNum, | pageNum = request.pageNum, | ||||
| pageSize = request.pageSize, | pageSize = request.pageSize, | ||||
| floor = request.floor, | floor = request.floor, | ||||
| isEtra = request.isEtra, | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -106,6 +108,7 @@ class DeliveryOrderController( | |||||
| pageSize = request.pageSize, | pageSize = request.pageSize, | ||||
| truckLanceCode = request.truckLanceCode, | truckLanceCode = request.truckLanceCode, | ||||
| floor = request.floor, | floor = request.floor, | ||||
| isEtra = request.isEtra, | |||||
| ) | ) | ||||
| } | } | ||||
| @@ -18,6 +18,8 @@ data class DoDetailResponse( | |||||
| @JsonFormat(pattern = "yyyy-MM-dd") | @JsonFormat(pattern = "yyyy-MM-dd") | ||||
| val completeDate: LocalDateTime?, | val completeDate: LocalDateTime?, | ||||
| val status: String?, | val status: String?, | ||||
| /** 加單 DO(M18 加單專用同步) */ | |||||
| val isEtra: Boolean = false, | |||||
| val deliveryOrderLines: List<DoDetailLineResponse> | val deliveryOrderLines: List<DoDetailLineResponse> | ||||
| ) | ) | ||||
| @@ -33,4 +33,6 @@ data class SearchDeliveryOrderInfoRequest( | |||||
| val truckLanceCode: String?, | val truckLanceCode: String?, | ||||
| /** `ALL`/`All`/null:P06B+P07+P06D;`2F`:P07+P06D;`4F`:P06B。車線-X 亦依供應商歸屬出現在對應樓層。 */ | /** `ALL`/`All`/null:P06B+P07+P06D;`2F`:P07+P06D;`4F`:P06B。車線-X 亦依供應商歸屬出現在對應樓層。 */ | ||||
| val floor: String? = null, | val floor: String? = null, | ||||
| /** null:不篩 isEtra;true/false:只顯示加單或非加單 DO */ | |||||
| val isEtra: Boolean? = null, | |||||
| ) | ) | ||||
| @@ -20,6 +20,7 @@ data class SaveDeliveryOrderRequest( | |||||
| val handlerId: Long?, | val handlerId: Long?, | ||||
| val m18BeId: Long?, | val m18BeId: Long?, | ||||
| val deleted: Boolean? = false, | val deleted: Boolean? = false, | ||||
| val isEtra: Boolean? = false, | |||||
| ) | ) | ||||
| data class SaveDeliveryOrderStatusRequest( | data class SaveDeliveryOrderStatusRequest( | ||||
| @@ -0,0 +1,5 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset fpsms:20260508_delivery_order_is_etra | |||||
| ALTER TABLE `delivery_order` | |||||
| ADD COLUMN `isEtra` TINYINT(1) NOT NULL DEFAULT 0 AFTER `m18BeId`; | |||||