diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt index 1a737c8..9fbdc68 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt @@ -1182,25 +1182,34 @@ open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDo val targetDate = deliveryOrder.estimatedArrivalDate?.toLocalDate() ?: LocalDate.now() val itemIds = deliveryOrder.deliveryOrderLines.mapNotNull { it.item?.id }.distinct() - val inventoryQuery = """ + val itemsStoreIdQuery = """ SELECT - w.store_id as floor, - COUNT(DISTINCT il.itemId) as item_count - FROM inventory_lot il - INNER JOIN inventory i ON i.itemId = il.itemId AND i.deleted = 0 AND i.onHandQty > 0 - INNER JOIN inventory_lot_line ill ON ill.inventoryLotId = il.id AND ill.deleted = 0 - INNER JOIN warehouse w ON w.id = ill.warehouseId AND w.deleted = 0 AND w.store_id IN ('2F', '4F') - WHERE il.itemId IN (${itemIds.joinToString(",")}) AND il.deleted = 0 - GROUP BY w.store_id + i.store_id, + COUNT(DISTINCT i.id) as item_count + FROM items i + WHERE i.id IN (${itemIds.joinToString(",")}) + AND i.deleted = 0 + GROUP BY i.store_id """.trimIndent() - val inventoryResults = jdbcDao.queryForList(inventoryQuery) - val floorItemCount = mutableMapOf() - inventoryResults.forEach { row -> - floorItemCount[row["floor"] as? String ?: "Other"] = (row["item_count"] as? Number)?.toInt() ?: 0 + val itemsStoreIdResults = jdbcDao.queryForList(itemsStoreIdQuery) + val storeIdItemCount = mutableMapOf() + itemsStoreIdResults.forEach { row -> + val rawStoreId = row["store_id"] as? String + if (rawStoreId != null) { + + val normalizedStoreId = when (rawStoreId) { + "3F" -> "4F" + else -> rawStoreId + } + storeIdItemCount[normalizedStoreId] = + (storeIdItemCount[normalizedStoreId] ?: 0) + + ((row["item_count"] as? Number)?.toInt() ?: 0) + } } - val preferredFloor = if ((floorItemCount["4F"] ?: 0) == itemIds.size && (floorItemCount["2F"] ?: 0) == 0) { + val preferredFloor = if ((storeIdItemCount["4F"] ?: 0) == itemIds.size && + (storeIdItemCount["2F"] ?: 0) == 0) { "4F" } else { "2F" diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderQueryService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderQueryService.kt index 816b1fe..9f607a7 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderQueryService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderQueryService.kt @@ -94,10 +94,11 @@ class DoPickOrderQueryService( .groupBy { it.key.first } .mapValues { (_, entries) -> entries.map { it.value } + .filter { it.unassigned > 0 } // filter out lanes with no unassigned orders .sortedByDescending { it.unassigned } .take(3) } - .filterValues { lanes -> lanes.any { it.unassigned > 0 } } + .filterValues { lanes -> lanes.isNotEmpty() } .toSortedMap(compareBy { it }) .entries.take(4) .map { (time, lanes) -> diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt index 727363f..5694ecd 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt @@ -47,32 +47,31 @@ class DoReleaseCoordinatorService( val updateSql = """ UPDATE fpsmsdb.do_pick_order dpo INNER JOIN ( - WITH DoFloorCounts AS ( - SELECT - dol.deliveryOrderId, - w.store_id, - COUNT(DISTINCT dol.itemId) AS item_count - FROM fpsmsdb.delivery_order_line dol - INNER JOIN fpsmsdb.inventory i ON i.itemId = dol.itemId - AND i.deleted = 0 - AND i.onHandQty > 0 - INNER JOIN fpsmsdb.inventory_lot il ON il.itemId = i.itemId - AND il.deleted = 0 - INNER JOIN fpsmsdb.inventory_lot_line ill ON ill.inventoryLotId = il.id - AND ill.deleted = 0 - INNER JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId - AND w.deleted = 0 - AND w.store_id IN ('2F', '4F') - WHERE dol.deleted = 0 - GROUP BY dol.deliveryOrderId, w.store_id - ), - DoFloorSummary AS ( + WITH DoStoreIdCounts AS ( + SELECT + dol.deliveryOrderId, + CASE + WHEN i.store_id = '3F' THEN '4F' + ELSE i.store_id + END AS store_id, -- 这里做 3F → 4F + COUNT(DISTINCT dol.itemId) AS item_count + FROM fpsmsdb.delivery_order_line dol + INNER JOIN fpsmsdb.items i ON i.id = dol.itemId + AND i.deleted = 0 + WHERE dol.deleted = 0 + GROUP BY dol.deliveryOrderId, + CASE + WHEN i.store_id = '3F' THEN '4F' + ELSE i.store_id + END + ), + DoStoreIdSummary AS ( SELECT do.id AS deliveryOrderId, - COALESCE(SUM(CASE WHEN dfc.store_id = '2F' THEN dfc.item_count ELSE 0 END), 0) AS count_2f, - COALESCE(SUM(CASE WHEN dfc.store_id = '4F' THEN dfc.item_count ELSE 0 END), 0) AS count_4f + COALESCE(SUM(CASE WHEN dsc.store_id = '2F' THEN dsc.item_count ELSE 0 END), 0) AS count_2f, + COALESCE(SUM(CASE WHEN dsc.store_id = '4F' THEN dsc.item_count ELSE 0 END), 0) AS count_4f FROM fpsmsdb.delivery_order do - LEFT JOIN DoFloorCounts dfc ON dfc.deliveryOrderId = do.id + LEFT JOIN DoStoreIdCounts dsc ON dsc.deliveryOrderId = do.id WHERE do.deleted = 0 GROUP BY do.id ), @@ -89,7 +88,7 @@ class DoReleaseCoordinatorService( WHEN count_4f > count_2f THEN 4 ELSE 2 END AS preferred_store_id - FROM DoFloorSummary + FROM DoStoreIdSummary ), TruckSelection AS ( SELECT @@ -161,7 +160,7 @@ class DoReleaseCoordinatorService( CONCAT('TI-', DATE_FORMAT(dpo2.RequiredDeliveryDate, '%Y%m%d'), '-', - COALESCE(ts.preferred_floor, '2F'), + REPLACE(COALESCE(dpo2.store_id, ts.preferred_floor, '2F'), '/', ''), '-', LPAD( ROW_NUMBER() OVER ( diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderProcessRepository.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderProcessRepository.kt index 857aa3d..7b98b95 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderProcessRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderProcessRepository.kt @@ -5,4 +5,6 @@ import org.springframework.stereotype.Repository @Repository interface JobOrderProcessRepository : AbstractRepository { + + fun findAllByJo_Id(joId: Long): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt index 5972e17..6e1aa67 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt @@ -34,6 +34,11 @@ import com.ffii.fpsms.modules.jobOrder.web.model.AllJoPickOrderResponse import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository import com.ffii.fpsms.modules.jobOrder.entity.JobTypeRepository import com.ffii.fpsms.modules.master.entity.ItemsRepository +import com.ffii.fpsms.modules.productProcess.entity.ProductProcessRepository +import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus +import com.ffii.fpsms.modules.productProcess.entity.ProductProcessLineRepository +import com.ffii.fpsms.modules.stock.entity.StockOutRepository +import com.ffii.fpsms.modules.jobOrder.entity.JobOrderProcessRepository @Service open class JoPickOrderService( private val joPickOrderRepository: JoPickOrderRepository, @@ -50,7 +55,11 @@ open class JoPickOrderService( private val stockOutLineRepository: StockOutLIneRepository, private val jobOrderRepository: JobOrderRepository, private val jobTypeRepository: JobTypeRepository, - private val itemsRepository: ItemsRepository + private val itemsRepository: ItemsRepository, + private val productProcessRepository: ProductProcessRepository, + private val productProcessLineRepository: ProductProcessLineRepository, + private val stockOutRepository: StockOutRepository, + private val jobOrderProcessRepository: JobOrderProcessRepository ) { @@ -1951,5 +1960,113 @@ open fun updateRecordHandledByForItem(pickOrderId: Long, itemId: Long, userId: L return joPickOrderRecordRepository.save(joPickOrderRecord) } +@Transactional(rollbackFor = [Exception::class]) +open fun deleteJoPickOrderJobOrderProductProcessPickOrder(jobOrderId: Long): MessageResponse { + println("=== deleteJoPickOrderJobOrderProductProcessPickOrder ===") + println("jobOrderId: $jobOrderId") + + // 1. 只允许删除 PLANNING 的工单 + val jobOrder = jobOrderRepository.findById(jobOrderId) + .orElseThrow { NoSuchElementException("JobOrder $jobOrderId not found") } + + if (jobOrder.status != JobOrderStatus.PLANNING) { + return null as MessageResponse; + } + + // 2. Product Process & Product Process Line + val productProcesses = productProcessRepository.findByJobOrder_Id(jobOrderId) + val productProcessIds = productProcesses.mapNotNull { it.id } + + if (productProcessIds.isNotEmpty()) { + val productProcessLines = + productProcessLineRepository.findByProductProcess_IdIn(productProcessIds) + if (productProcessLines.isNotEmpty()) { + productProcessLineRepository.deleteAll(productProcessLines) + } + productProcessRepository.deleteAll(productProcesses) + } + + // 3. PickOrder / PickOrderLine / JoPickOrder / JoPickOrderRecord + val pickOrders = pickOrderRepository.findAllByJobOrder_Id(jobOrderId) + if (pickOrders.isNotEmpty()) { + val pickOrderIds = pickOrders.mapNotNull { it.id } + + // 3.1 所有 pick order line + val pickOrderLines = pickOrderIds.flatMap { poId -> + pickOrderLineRepository.findAllByPickOrderId(poId) + } + val pickOrderLineIds = pickOrderLines.mapNotNull { it.id } + + // 3.2 jo_pick_order & jo_pick_order_record + pickOrderIds.forEach { poId -> + val joPickOrders = joPickOrderRepository.findByPickOrderId(poId) + if (joPickOrders.isNotEmpty()) { + joPickOrderRepository.deleteAll(joPickOrders) + } + val joPickOrderRecords = joPickOrderRecordRepository.findByPickOrderId(poId) + if (joPickOrderRecords.isNotEmpty()) { + joPickOrderRecordRepository.deleteAll(joPickOrderRecords) + } + } + + // 4. SuggestedPickLot & 释放 inventoryLotLine.holdQty + if (pickOrderLineIds.isNotEmpty()) { + val suggestedPickLots = + suggestPickLotRepository.findAllByPickOrderLineIdIn(pickOrderLineIds) + if (suggestedPickLots.isNotEmpty()) { + val lotLineIds = suggestedPickLots.mapNotNull { it.suggestedLotLine?.id }.distinct() + if (lotLineIds.isNotEmpty()) { + val lotLines = inventoryLotLineRepository.findAllByIdIn(lotLineIds) + // 把当初 hold 住的 qty 减回去(releaseJobOrder 里是直接 + qty,这里反向 - qty) + lotLines.forEach { ill -> + val totalQtyToRelease = suggestedPickLots + .filter { it.suggestedLotLine?.id == ill.id } + .mapNotNull { it.qty } + .fold(BigDecimal.ZERO) { acc, q -> acc + q } + if (totalQtyToRelease > BigDecimal.ZERO) { + val current = ill.holdQty ?: BigDecimal.ZERO + ill.holdQty = current - totalQtyToRelease + } + } + inventoryLotLineRepository.saveAll(lotLines) + } + suggestPickLotRepository.deleteAll(suggestedPickLots) + } + } + + // 5. StockOut & StockOutLine(按 consoPickOrderCode 关联) + pickOrders + .mapNotNull { it.consoCode } + .mapNotNull { stockOutRepository.findByConsoPickOrderCode(it).orElse(null) } + .forEach { stockOut -> + val lines = stockOutLineRepository.findAllByStockOutId(stockOut.id!!) + if (lines.isNotEmpty()) { + stockOutLineRepository.deleteAll(lines) + } + stockOutRepository.delete(stockOut) + } + + // 6. 最后删 PickOrderLine & PickOrder + if (pickOrderLines.isNotEmpty()) { + pickOrderLineRepository.deleteAll(pickOrderLines) + } + pickOrderRepository.deleteAll(pickOrders) + } + val jobOrderProcesses = jobOrderProcessRepository.findAllByJo_Id(jobOrderId) + if (jobOrderProcesses.isNotEmpty()) { + jobOrderProcessRepository.deleteAll(jobOrderProcesses) + } + // 7. 最后删除 JobOrder 本身 + jobOrderRepository.delete(jobOrder) + + return MessageResponse( + id = jobOrder.id, + code = jobOrder.code, + name = jobOrder.bom?.name, + type = null, + message = "Job Order deleted", + errorPosition = null + ) +} } diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt index ce4d9aa..3103cb0 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/JobOrderController.kt @@ -289,4 +289,10 @@ fun updateJoPickOrderHandledBy(@Valid @RequestBody request: UpdateJoPickOrderHan fun getAllJobTypes(): List { return jobOrderService.getAllJobTypes() } + + +@PostMapping("/demo/deleteJobOrder/{jobOrderId}") +fun deleteJoPickOrderJobOrderProductProcessPickOrder(@PathVariable jobOrderId: Long): MessageResponse { + return joPickOrderService.deleteJoPickOrderJobOrderProductProcessPickOrder(jobOrderId) } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/ShopRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/ShopRepository.kt index 98f9a63..8647f2d 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/ShopRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/ShopRepository.kt @@ -25,4 +25,6 @@ interface ShopRepository : AbstractRepository { fun findM18IdsByCodeNotRegexpAndTypeAndDeletedIsFalse(codeNotRegexp: String, type: String): List? fun findShopComboByTypeAndDeletedIsFalse(type: ShopType): List + + fun findByCode(code: String): Shop? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/projections/BomCombo.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/projections/BomCombo.kt index 80fb748..682b7e2 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/projections/BomCombo.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/projections/BomCombo.kt @@ -1,6 +1,7 @@ package com.ffii.fpsms.modules.master.entity.projections import org.springframework.beans.factory.annotation.Value +import java.math.BigDecimal interface BomCombo { val id: Long; @@ -8,4 +9,5 @@ interface BomCombo { val value: Long; @get:Value("#{target.code} - #{target.name} - #{target.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}") val label: String; + val outputQty: BigDecimal; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/TruckRepository.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/TruckRepository.kt index b701482..b212b47 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/TruckRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/entity/TruckRepository.kt @@ -7,21 +7,26 @@ import org.springframework.stereotype.Repository @Repository interface TruckRepository : AbstractRepository { - + fun findByShopIdAndDeletedFalse(shopId: Long): List - + @Query("SELECT t FROM Truck t WHERE t.shop.id = :shopId AND t.deleted = false ORDER BY t.id ASC") fun findFirstByShopIdAndDeletedFalse(@Param("shopId") shopId: Long): List - + // 使用新的 TruckLanceCode 字段名 @Query("SELECT t FROM Truck t WHERE t.truckLanceCode = :truckLanceCode AND t.deleted = false") fun findByTruckLanceCodeAndDeletedFalse(@Param("truckLanceCode") truckLanceCode: String): Truck? - + // 按 ShopCode 查询 @Query("SELECT t FROM Truck t WHERE t.shopCode = :shopCode AND t.deleted = false") fun findByShopCodeAndDeletedFalse(@Param("shopCode") shopCode: String): List - + // 按 Store_id 查询 fun findByStoreIdAndDeletedFalse(storeId: Int): List + + // 按 TruckLanceCode 查询 + fun findByTruckLanceCode(truckLanceCode: String): Truck? + fun findByShopNameAndStoreIdAndTruckLanceCode(shopName: String, storeId: Int, truckLanceCode: String): Truck? + fun findByShopCodeAndStoreId(shopCode: String, storeId: Int): Truck? } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt new file mode 100644 index 0000000..e1ef0fe --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/TruckService.kt @@ -0,0 +1,198 @@ +package com.ffii.fpsms.modules.pickOrder.service + +import com.ffii.core.support.AbstractBaseEntityService +import com.ffii.core.support.JdbcDao +import com.ffii.core.utils.ExcelUtils +import com.ffii.core.utils.JwtTokenUtil +import com.ffii.fpsms.modules.master.entity.ItemsRepository +import com.ffii.fpsms.modules.master.entity.Warehouse +import com.ffii.fpsms.modules.master.entity.WarehouseRepository +import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo +import com.ffii.fpsms.modules.master.web.models.SaveWarehouseRequest +import com.ffii.fpsms.modules.master.web.models.NewWarehouseRequest +import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository +import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository +import com.ffii.fpsms.modules.stock.entity.StockInLine +import com.ffii.fpsms.modules.stock.entity.StockInLineRepository +import com.ffii.fpsms.modules.stock.entity.StockInRepository +import com.ffii.fpsms.modules.stock.service.InventoryLotService +import com.ffii.fpsms.modules.stock.service.StockInService +import org.apache.poi.ss.usermodel.Sheet +import org.apache.poi.ss.usermodel.Workbook +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import java.math.BigDecimal +import kotlin.jvm.optionals.getOrNull +import com.ffii.fpsms.modules.pickOrder.entity.Truck +import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository +import com.ffii.fpsms.modules.pickOrder.web.models.SaveTruckRequest +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import com.ffii.fpsms.modules.master.entity.ShopRepository +@Service +open class TruckService( + private val jdbcDao: JdbcDao, + private val truckRepository: TruckRepository, + private val shopRepository: ShopRepository, +) : AbstractBaseEntityService(jdbcDao, truckRepository) { + open fun saveTruck(request: SaveTruckRequest): Truck { + val truck = request.id?.let { + truckRepository.findById(it).orElse(null) + } ?: Truck() + val shop = shopRepository.findById(request.shopId).orElse(null) + if (shop == null) { + throw IllegalArgumentException("Shop not found with id: ${request.shopId}") + } + + truck.apply { + this.storeId = request.store_id + this.truckLanceCode = request.truckLanceCode + this.departureTime = request.departureTime + this.shop = shop + this.shopName = request.shopName + this.shopCode = request.shopCode + this.loadingSequence = request.loadingSequence + } + + return truckRepository.save(truck); + } + + private fun parseDepartureTime(timeStr: String?): LocalTime? { + if (timeStr.isNullOrBlank()) return null + + return try { + val cleaned = timeStr.trim().uppercase().replace(" ", "") + + // 处理 3:00AM / 5:30PM 这类 12 小时制 + if (cleaned.contains("AM") || cleaned.contains("PM")) { + val isPM = cleaned.contains("PM") + val timePart = cleaned.replace("AM", "").replace("PM", "") + val parts = timePart.split(":") + if (parts.size == 2) { + var hour = parts[0].toInt() + val minute = parts[1].toIntOrNull() ?: 0 + + if (isPM && hour != 12) hour += 12 + if (!isPM && hour == 12) hour = 0 + + LocalTime.of(hour, minute) + } else null + } else { + // 处理 17:30 / 3:00 这类 24 小时制 + try { + LocalTime.parse(timeStr.trim(), DateTimeFormatter.ofPattern("H:mm")) + } catch (_: Exception) { + LocalTime.parse(timeStr.trim(), DateTimeFormatter.ofPattern("HH:mm")) + } + } + } catch (e: Exception) { + logger.warn("Failed to parse departure time: $timeStr", e) + null + } + } + private fun normalizeShopCode(shopCode: String): String { + val firstDigitIndex = shopCode.indexOfFirst { it.isDigit() } + if (firstDigitIndex == -1) { + return shopCode + } + + val letterPart = shopCode.substring(0, firstDigitIndex) + val numberPart = shopCode.substring(firstDigitIndex) + + val normalizedNumber = if (numberPart.startsWith("0") && numberPart.length > 1) { + numberPart.substring(1) + } else { + numberPart + } + return letterPart + normalizedNumber + } + open fun importExcel(workbook: Workbook?): String { + logger.info("--------- Start - Import Warehouse Excel -------"); + + if (workbook == null) { + logger.error("No Excel Import"); + return "Import Excel failure"; + } + val sheet: Sheet = workbook.getSheetAt(0); + + // Columns + val COLUMN_STORE_ID_INDEX = 0; + val COLUMN_REQUIRED_DELIVERY_DATE_INDEX = 1; + val COLUMN_DEPARTURE_TIME_INDEX = 2; + val COLUMN_TRUCK_LANCE_CODE_INDEX = 3; + val COLUMN_SHOP_CODE_INDEX = 4; + val COLUMN_SHOP_NAME_INDEX = 5; + val COLUMN_LOADING_SEQUENCE_INDEX = 6; + val COLUMN_REMARK_INDEX = 7; + + val START_ROW_INDEX = 3; + logger.info("Total rows in sheet: ${sheet.lastRowNum + 1}, Processing from row ${START_ROW_INDEX + 1} to ${sheet.lastRowNum + 1}"); + // Start Import + for (i in START_ROW_INDEX.. 2 + "4F" -> 4 + "3F" -> 3 + else -> { + logger.warn("Invalid store_id '${store_id}', defaulting to 2") + 2 + } + } + val departureTime = parseDepartureTime(departureTimeStr) + if (departureTime == null) { + logger.warn("Row ${i + 1}: Invalid departure time '$departureTimeStr', skipping") + continue + } + val normalizedShopCode = normalizeShopCode(shopCode) + val shop = shopRepository.findAllByDeletedIsFalse().firstOrNull { it.code == normalizedShopCode } + //println("shop: ${shop}") + val existingTruck = truckRepository.findByShopCodeAndStoreId(shopCode, storeIdInt) + if (existingTruck != null) { + + val truckRequest = SaveTruckRequest( + id = existingTruck.id, + store_id = storeIdInt, + truckLanceCode = truckLanceCode ?: existingTruck.truckLanceCode ?: "", + departureTime = departureTime, + shopId = shop?.id!!, + shopName = shopName?: "", + shopCode = shopCode, + loadingSequence = loadingSequence + ) + saveTruck(truckRequest) + } else { + // 创建新记录 + val truckRequest = SaveTruckRequest( + id = null, + store_id = storeIdInt, + truckLanceCode = truckLanceCode ?: "", + departureTime = departureTime, + shopId = shop?.id!!, + shopName = shopName?: "", + shopCode = shopCode, + loadingSequence = loadingSequence + ) + saveTruck(truckRequest) + } + } catch (e: Exception) { + logger.error("Import Error (Warehouse Error): ${e.message}") + } + } + logger.info("--------- End - Import Warehouse Excel -------") + + return "Import Excel success"; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/TruckController.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/TruckController.kt new file mode 100644 index 0000000..2433cc7 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/TruckController.kt @@ -0,0 +1,85 @@ +package com.ffii.fpsms.modules.pickOrder.web + +import com.ffii.fpsms.modules.master.web.models.MessageResponse +import org.springframework.web.bind.ServletRequestBindingException +import jakarta.servlet.http.HttpServletRequest +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartHttpServletRequest + +import com.ffii.fpsms.modules.pickOrder.web.models.SaveTruckRequest +import com.ffii.fpsms.modules.pickOrder.service.TruckService +import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository + +@RestController +@RequestMapping("/truck") +class TruckController( + private val truckService: TruckService, + private val truckRepository: TruckRepository, +) { + + @PostMapping("/save") + fun saveTruck(@RequestBody request: SaveTruckRequest): MessageResponse { + try { + val truck = truckService.saveTruck(request) + return MessageResponse( + id = truck.id, + name = truck.shopName, + code = truck.truckLanceCode, + type = "truck", + message = if (truck.id != null) "Truck updated successfully" else "Truck created successfully", + errorPosition = null, + entity = truck + ) + } catch (e: Exception) { + return MessageResponse( + id = null, + name = null, + code = null, + type = "truck", + message = "Error: ${e.message}", + errorPosition = null, + entity = null + ) + } + } + @PostMapping("/importExcel") + @Throws(ServletRequestBindingException::class) + fun importExcel(request: HttpServletRequest): ResponseEntity<*> { + var workbook: Workbook? = null + + try { + val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList") + workbook = XSSFWorkbook(multipartFile?.inputStream) + } catch (e: Exception) { + println("Error reading Excel file: ${e.message}") + return ResponseEntity.badRequest().body( + MessageResponse( + id = null, + name = null, + code = null, + type = "truck", + message = "Error reading Excel file: ${e.message}", + errorPosition = null, + entity = null + ) + ) + } + + val result = truckService.importExcel(workbook) + return ResponseEntity.ok( + MessageResponse( + id = null, + name = null, + code = null, + type = "truck", + message = result, + errorPosition = null, + entity = null + ) + ) + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SaveTruckRequest.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SaveTruckRequest.kt new file mode 100644 index 0000000..a2f8aa2 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SaveTruckRequest.kt @@ -0,0 +1,12 @@ +package com.ffii.fpsms.modules.pickOrder.web.models +import java.time.LocalTime +data class SaveTruckRequest( + val id: Long? = null, + val store_id: Int, + val truckLanceCode: String, + val departureTime: LocalTime, + val shopId: Long, + val shopName: String, + val shopCode: String, + val loadingSequence: Int, +) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt index 4e9af3d..2a99994 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt @@ -142,7 +142,11 @@ open class SuggestedPickLotService( if (remainingQtyToAllocate <= zero) return@forEachIndexed println("calculateRemainingQtyForInfo(lotLine) ${calculateRemainingQtyForInfo(lotLine)}") - + val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } + val warehouseStoreId=inventoryLotLine?.warehouse?.store_id + if ( warehouseStoreId == "3F") { + return@forEachIndexed + } // 修复:计算可用数量,转换为销售单位 val availableQtyInBaseUnits = calculateRemainingQtyForInfo(lotLine) val holdQtyInBaseUnits = holdQtyMap[lotLine.id] ?: zero @@ -157,7 +161,7 @@ open class SuggestedPickLotService( return@forEachIndexed } println("$index : ${lotLine.id}") - val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } + // val inventoryLotLine = lotLine.id?.let { inventoryLotLineService.findById(it).getOrNull() } val originalHoldQty = inventoryLotLine?.holdQty // 修复:在销售单位中计算分配数量 @@ -902,7 +906,7 @@ private fun generateOptimalSuggestionsForAllPickOrders( val lotEntities = inventoryLotLineRepository.findAllByIdIn(availableLots.mapNotNull { it.id }) lotEntities.forEach { lot -> lot.holdQty = BigDecimal.ZERO } - // FIX: Calculate remaining quantity for each pick order line + // FIX: Calculate remaining quantity for each pick or der line // FIX: Calculate remaining quantity for each pick order line val remainingQtyPerLine = pickOrderLines.associate { pol -> val stockOutLines = stockOutLIneRepository.findAllByPickOrderLineIdAndDeletedFalse(pol.id!!) @@ -929,7 +933,10 @@ private fun generateOptimalSuggestionsForAllPickOrders( lotEntities.forEach { lot -> if (remainingPickOrderLines.isEmpty()) return@forEach - + val warehouseStoreId=lot.warehouse?.store_id + if ( warehouseStoreId == "3F") { + return@forEach + } val totalQty = lot.inQty ?: zero val outQty = lot.outQty ?: zero val holdQty = lot.holdQty ?: zero // This should be 0 now @@ -1204,7 +1211,10 @@ private fun generateCorrectSuggestionsWithExistingHolds(pickOrder: PickOrder): L val lot = lotInfo.id?.let { inventoryLotLineRepository.findById(it).orElse(null) } ?: return@forEach - + val warehouseStoreId=lot.warehouse?.store_id + if ( warehouseStoreId == "3F") { + return@forEach + } val totalQty = lot.inQty ?: zero val outQty = lot.outQty ?: zero val holdQty = lot.holdQty ?: zero