# Conflicts: # src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.ktmaster
| @@ -0,0 +1,97 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.entity | |||
| import jakarta.persistence.* | |||
| import java.time.LocalDateTime | |||
| import java.time.LocalTime | |||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus | |||
| import org.hibernate.annotations.CreationTimestamp | |||
| import org.hibernate.annotations.UpdateTimestamp | |||
| @Entity | |||
| @Table(name = "do_pick_order_record") | |||
| class DoPickOrderRecord { | |||
| @Id | |||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | |||
| var id: Long? = null | |||
| @Column(name = "store_id", length = 10) | |||
| var storeId: String? = null | |||
| @Column(name = "ticket_no", length = 50) | |||
| var ticketNo: String? = null | |||
| @Column(name = "pick_order_id") | |||
| var pickOrderId: Long? = null | |||
| @Enumerated(EnumType.STRING) | |||
| @Column(name = "ticket_status") | |||
| var ticketStatus: DoPickOrderStatus? = null | |||
| @Column(name = "truck_id") | |||
| var truckId: Long? = null | |||
| @Column(name = "truck_departure_time") | |||
| var truckDepartureTime: LocalTime? = null | |||
| @Column(name = "item_id") | |||
| var itemId: Long? = null | |||
| @Column(name = "shop_id") | |||
| var shopId: Long? = null | |||
| @Column(name = "shop_po_supplier_id") | |||
| var shopPoSupplierId: Long? = null | |||
| @Column(name = "handled_by") | |||
| var handledBy: Long? = null | |||
| @CreationTimestamp | |||
| @Column(name = "created") | |||
| var created: LocalDateTime? = null | |||
| @Column(name = "createdBy", length = 30) | |||
| var createdBy: String? = null | |||
| @Version | |||
| var version: Int = 0 | |||
| @UpdateTimestamp | |||
| @Column(name = "modified") | |||
| var modified: LocalDateTime? = null | |||
| @Column(name = "modifiedBy", length = 30) | |||
| var modifiedBy: String? = null | |||
| @Column(name = "deleted") | |||
| var deleted: Boolean = false | |||
| // Default constructor for Hibernate | |||
| constructor() | |||
| // Constructor for creating new instances | |||
| constructor( | |||
| storeId: String, | |||
| ticketNo: String, | |||
| ticketStatus: DoPickOrderStatus, | |||
| truckId: Long? = null, | |||
| truckDepartureTime: LocalTime? = null, | |||
| pickOrderId: Long? = null, | |||
| itemId: Long? = null, | |||
| shopId: Long? = null, | |||
| shopPoSupplierId: Long? = null, | |||
| handledBy: Long? = null, | |||
| createdBy: String? = null, | |||
| modifiedBy: String? = null | |||
| ) { | |||
| this.storeId = storeId | |||
| this.ticketNo = ticketNo | |||
| this.pickOrderId = pickOrderId | |||
| this.ticketStatus = ticketStatus | |||
| this.truckId = truckId | |||
| this.truckDepartureTime = truckDepartureTime | |||
| this.itemId = itemId | |||
| this.shopId = shopId | |||
| this.shopPoSupplierId = shopPoSupplierId | |||
| this.handledBy = handledBy | |||
| this.createdBy = createdBy | |||
| this.modifiedBy = modifiedBy | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| package com.ffii.fpsms.modules.deliveryOrder.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.models.DeliveryOrderInfo | |||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus | |||
| import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus | |||
| import com.ffii.fpsms.modules.master.entity.projections.SearchId | |||
| import org.springframework.data.jpa.repository.JpaRepository | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| import java.time.LocalDateTime | |||
| @Repository | |||
| interface DoPickOrderRecordRepository : JpaRepository<DoPickOrderRecord, Long> { | |||
| fun findByPickOrderId(pickOrderId: Long): List<DoPickOrderRecord> | |||
| fun findByTicketNoStartingWith(ticketPrefix: String): List<DoPickOrderRecord> | |||
| } | |||
| @@ -2,5 +2,6 @@ package com.ffii.fpsms.modules.deliveryOrder.enums | |||
| enum class DoPickOrderStatus(val value: String) { | |||
| pending("pending"), | |||
| released("released"), | |||
| completed("completed") | |||
| } | |||
| @@ -57,6 +57,9 @@ import org.springframework.core.io.ClassPathResource | |||
| import java.io.File | |||
| import java.io.FileNotFoundException | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository | |||
| @Service | |||
| open class DeliveryOrderService( | |||
| private val deliveryOrderRepository: DeliveryOrderRepository, | |||
| @@ -75,7 +78,8 @@ open class DeliveryOrderService( | |||
| private val stockOutRepository: StockOutRepository, | |||
| private val stockOutLineRepository: StockOutLIneRepository, | |||
| private val pickOrderLineRepository: PickOrderLineRepository, | |||
| private val printerService: PrinterService | |||
| private val printerService: PrinterService, | |||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository | |||
| ) { | |||
| open fun findByM18DataLogId(m18DataLogId: Long): DeliveryOrder? { | |||
| @@ -452,14 +456,9 @@ open class DeliveryOrderService( | |||
| val datePrefix = targetDate.format(DateTimeFormatter.ofPattern("ddMMyy")) | |||
| println("�� DEBUG: Target date: $targetDate, Date prefix: $datePrefix") | |||
| // Get next ticket number for this date | |||
| val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix) | |||
| println("🔍 DEBUG: Next ticket number: $nextTicketNumber") | |||
| // ✅ Find truck by shop ID with earliest departure time | |||
| val truck = deliveryOrder.shop?.id?.let { shopId -> | |||
| println("�� DEBUG: Looking for truck with shop ID: $shopId") | |||
| println("🔍 DEBUG: Looking for truck with shop ID: $shopId") | |||
| val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| println("🔍 DEBUG: Found ${trucks.size} trucks for shop $shopId") | |||
| trucks.forEach { t -> | |||
| @@ -472,29 +471,59 @@ open class DeliveryOrderService( | |||
| println("🔍 DEBUG: Processing ${deliveryOrder.deliveryOrderLines.size} delivery order lines") | |||
| deliveryOrder.deliveryOrderLines.forEach { line -> | |||
| val storeId = if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F" | |||
| println("�� DEBUG: Processing line - Item ID: ${line.item?.id}, Store ID: $storeId") | |||
| val doPickOrder = DoPickOrder( | |||
| storeId = storeId, | |||
| ticketNo = nextTicketNumber, | |||
| ticketStatus = DoPickOrderStatus.pending, | |||
| truckId = truck?.id, | |||
| pickOrderId = createdPickOrder.id, | |||
| truckDepartureTime = truck?.departureTime, | |||
| itemId = line.item?.id, | |||
| shopId = deliveryOrder.shop?.id, | |||
| shopPoSupplierId = deliveryOrder.shop?.id, | |||
| handledBy = null | |||
| ) | |||
| println("�� DEBUG: Creating DoPickOrder - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}") | |||
| val savedDoPickOrder = doPickOrderService.save(doPickOrder) | |||
| println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") | |||
| // ✅ Group lines by store ID to get unique ticket numbers per store | |||
| val linesByStore = deliveryOrder.deliveryOrderLines.groupBy { line -> | |||
| if (deliveryOrder.supplier?.code == "P06B") "4/F" else "2/F" | |||
| } | |||
| linesByStore.forEach { (storeId, lines) -> | |||
| // ✅ Get ticket number for this specific store | |||
| val nextTicketNumber = doPickOrderService.getNextTicketNumber(datePrefix, storeId) | |||
| println("🔍 DEBUG: Next ticket number for store $storeId: $nextTicketNumber") | |||
| lines.forEach { line -> | |||
| println("�� DEBUG: Processing line - Item ID: ${line.item?.id}, Store ID: $storeId") | |||
| val doPickOrderRecord = DoPickOrderRecord( | |||
| storeId = storeId, | |||
| ticketNo = nextTicketNumber, | |||
| ticketStatus = DoPickOrderStatus.pending, | |||
| truckId = truck?.id, | |||
| pickOrderId = createdPickOrder.id, | |||
| truckDepartureTime = truck?.departureTime, | |||
| itemId = line.item?.id, | |||
| shopId = deliveryOrder.shop?.id, | |||
| shopPoSupplierId = deliveryOrder.shop?.id, | |||
| handledBy = null | |||
| ) | |||
| println("�� DEBUG: Creating DoPickOrderRecord - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}") | |||
| val savedDoPickOrderRecord = doPickOrderRecordRepository.save(doPickOrderRecord) | |||
| println("🔍 DEBUG: Saved DoPickOrderRecord - ID: ${savedDoPickOrderRecord.id}") | |||
| } | |||
| // ✅ Also create DoPickOrder records for this store | |||
| lines.forEach { line -> | |||
| println("�� DEBUG: Creating DoPickOrder - Store: $storeId, Ticket: $nextTicketNumber, Truck: ${truck?.id}") | |||
| val doPickOrder = DoPickOrder( | |||
| storeId = storeId, | |||
| ticketNo = nextTicketNumber, | |||
| ticketStatus = DoPickOrderStatus.pending, | |||
| truckId = truck?.id, | |||
| pickOrderId = createdPickOrder.id, | |||
| truckDepartureTime = truck?.departureTime, | |||
| itemId = line.item?.id, | |||
| shopId = deliveryOrder.shop?.id, | |||
| shopPoSupplierId = deliveryOrder.shop?.id, | |||
| handledBy = null | |||
| ) | |||
| val savedDoPickOrder = doPickOrderService.save(doPickOrder) | |||
| println("🔍 DEBUG: Saved DoPickOrder - ID: ${savedDoPickOrder.id}") | |||
| } | |||
| } | |||
| return MessageResponse( | |||
| id = deliveryOrder.id, | |||
| code = deliveryOrder.code, | |||
| @@ -33,21 +33,27 @@ import java.math.BigDecimal | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import java.time.LocalDateTime | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.AssignByStoreRequest | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository | |||
| @Service | |||
| class DoPickOrderService( | |||
| private val doPickOrderRepository: DoPickOrderRepository | |||
| private val doPickOrderRepository: DoPickOrderRepository, | |||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository | |||
| ) { | |||
| fun getNextTicketNumber(datePrefix: String): String { | |||
| println("🔍 DEBUG: Getting next ticket number for date prefix: $datePrefix") | |||
| fun getNextTicketNumber(datePrefix: String, storeId: String): String { | |||
| println("🔍 DEBUG: Getting next ticket number for date prefix: $datePrefix, store: $storeId") | |||
| try { | |||
| val todayTickets = doPickOrderRepository.findByTicketNoStartingWith("${datePrefix}_") | |||
| println("🔍 DEBUG: Found ${todayTickets.size} existing tickets with prefix ${datePrefix}_") | |||
| val sanitizedStoreId = storeId.replace("/", "") | |||
| // ✅ Include store ID in the search pattern | |||
| val searchPattern = "${datePrefix}_${sanitizedStoreId}_" | |||
| val todayTickets = doPickOrderRepository.findByTicketNoStartingWith(searchPattern) | |||
| println("🔍 DEBUG: Found ${todayTickets.size} existing tickets with prefix $searchPattern") | |||
| todayTickets.forEach { ticket -> | |||
| println("�� DEBUG: Existing ticket: ${ticket.ticketNo}, Status: ${ticket.ticketStatus}") | |||
| } | |||
| val nextNumber = (todayTickets.size + 1).toString().padStart(3, '0') | |||
| val ticketNumber = "${datePrefix}_${nextNumber}" | |||
| val ticketNumber = "${datePrefix}_${sanitizedStoreId}_${nextNumber}" | |||
| println("🔍 DEBUG: Generated ticket number: $ticketNumber") | |||
| return ticketNumber | |||
| } catch (e: Exception) { | |||
| @@ -94,7 +100,52 @@ class DoPickOrderService( | |||
| } | |||
| fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long): List<DoPickOrder> { | |||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||
| doPickOrders.forEach { it.handledBy = userId } | |||
| doPickOrders.forEach { it.handledBy = userId | |||
| it.ticketStatus = DoPickOrderStatus.released } | |||
| return doPickOrderRepository.saveAll(doPickOrders) | |||
| } | |||
| fun completeDoPickOrdersForPickOrder(pickOrderId: Long): List<DoPickOrder> { | |||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||
| doPickOrders.forEach { | |||
| it.ticketStatus = DoPickOrderStatus.completed // ✅ Update status to "completed" | |||
| } | |||
| return doPickOrderRepository.saveAll(doPickOrders) | |||
| } | |||
| // ✅ New method to remove do_pick_order records when auto-assigning by store | |||
| fun removeDoPickOrdersForPickOrder(pickOrderId: Long): Int { | |||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||
| if (doPickOrders.isNotEmpty()) { | |||
| // Mark as deleted instead of physically deleting | |||
| doPickOrders.forEach { | |||
| it.ticketStatus = DoPickOrderStatus.completed | |||
| it.deleted = true } | |||
| doPickOrderRepository.saveAll(doPickOrders) | |||
| return doPickOrders.size | |||
| } | |||
| return 0 | |||
| } | |||
| fun saveRecord(record: DoPickOrderRecord): DoPickOrderRecord { | |||
| return doPickOrderRecordRepository.save(record) | |||
| } | |||
| // ✅ Add method to update DoPickOrderRecord status | |||
| fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long): List<DoPickOrderRecord> { | |||
| val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||
| doPickOrderRecords.forEach { | |||
| it.handledBy = userId | |||
| it.ticketStatus = DoPickOrderStatus.released | |||
| } | |||
| return doPickOrderRecordRepository.saveAll(doPickOrderRecords) | |||
| } | |||
| // ✅ Add method to complete DoPickOrderRecord | |||
| fun completeDoPickOrderRecordsForPickOrder(pickOrderId: Long): List<DoPickOrderRecord> { | |||
| val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId) | |||
| doPickOrderRecords.forEach { | |||
| it.ticketStatus = DoPickOrderStatus.completed | |||
| } | |||
| return doPickOrderRecordRepository.saveAll(doPickOrderRecords) | |||
| } | |||
| } | |||
| @@ -22,5 +22,5 @@ interface RouterRepository : AbstractRepository<Router, Long> { | |||
| @Query("SELECT r FROM Router r WHERE r.route = :route AND r.deleted = false") | |||
| fun findByRouteAndDeletedFalse(@Param("route") route: String): List<Router> | |||
| } | |||
| @@ -266,4 +266,20 @@ class PickOrderController( | |||
| fun getAllPickOrderLotsWithDetailsWithoutAutoAssign(@PathVariable userId: Long): List<Map<String, Any>> { | |||
| return pickOrderService.getAllPickOrderLotsWithDetailsWithoutAutoAssign(userId) | |||
| } | |||
| @GetMapping("/all-lots-hierarchical/{userId}") | |||
| fun getAllPickOrderLotsHierarchical(@PathVariable userId: Long): Map<String, Any?> { | |||
| return pickOrderService.getAllPickOrderLotsWithDetailsHierarchical(userId) | |||
| } | |||
| @PostMapping("/auto-assign-release-by-ticket") | |||
| fun autoAssignAndReleasePickOrderByTicket( | |||
| @RequestParam storeId: String, | |||
| @RequestParam ticketNo: String, | |||
| @RequestParam userId: Long | |||
| ): MessageResponse { | |||
| return pickOrderService.autoAssignAndReleasePickOrderByStoreAndTicket(storeId, ticketNo, userId) | |||
| } | |||
| @GetMapping("/orders-by-store/{storeId}") | |||
| fun getPickOrdersByStore(@PathVariable storeId: String): Map<String, Any?> { | |||
| return pickOrderService.getPickOrdersByDateAndStore(storeId) | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset enson:altertable_enson | |||
| ALTER TABLE `fpsmsdb`.`do_pick_order` | |||
| CHANGE COLUMN `ticket_status` `ticket_status` ENUM('pending', 'released', 'completed') NULL DEFAULT 'pending' ; | |||
| ALTER TABLE `fpsmsdb`.`do_pick_order_record` | |||
| CHANGE COLUMN `ticket_status` `ticket_status` ENUM('pending', 'released', 'completed') NULL DEFAULT 'pending' ; | |||