| @@ -82,11 +82,13 @@ open class ItemsService( | |||
| po.targetDate, | |||
| po.status, | |||
| po.consoCode, | |||
| po.assignTo | |||
| po.assignTo, | |||
| COALESCE(pog.name, 'No Group') as groupName | |||
| FROM pick_order po | |||
| JOIN pick_order_line pol ON pol.poId = po.id | |||
| JOIN items i ON i.id = pol.itemId | |||
| LEFT JOIN uom_conversion uc ON uc.id = pol.uomId | |||
| LEFT JOIN pick_order_group pog ON pog.pick_order_id = po.id AND pog.deleted = false | |||
| LEFT JOIN ( | |||
| SELECT | |||
| il.itemId, | |||
| @@ -0,0 +1,29 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.entity | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.user.entity.User | |||
| import jakarta.persistence.* | |||
| import jakarta.validation.constraints.NotNull | |||
| import jakarta.validation.constraints.Size | |||
| import java.time.LocalDate | |||
| @Entity | |||
| @Table(name = "pick_order_group") | |||
| open class PickOrderGroup: BaseEntity<Long>() { | |||
| @Column(name = "pick_order_id") | |||
| open var pickOrderId: Long? = null | |||
| @Size(max = 50) | |||
| @Column(name = "name") | |||
| open var name: String? = null | |||
| @Column(name = "target_date") | |||
| open var targetDate: LocalDate? = null | |||
| @ManyToOne | |||
| @JoinColumn(name = "releasedBy", referencedColumnName = "id") | |||
| open var releasedBy: User? = null | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.stereotype.Repository | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.data.repository.query.Param | |||
| import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderGroupInfo | |||
| @Repository | |||
| interface PickOrderGroupRepository : AbstractRepository<PickOrderGroup, Long> { | |||
| @Query(""" | |||
| SELECT pog FROM PickOrderGroup pog | |||
| WHERE pog.deleted = false | |||
| ORDER BY pog.name DESC | |||
| """) | |||
| fun findTopByOrderByNameDesc(): PickOrderGroup? | |||
| @Query(""" | |||
| SELECT pog FROM PickOrderGroup pog | |||
| WHERE pog.pickOrderId = :pickOrderId | |||
| AND pog.deleted = false | |||
| """) | |||
| fun findByPickOrderIdAndDeletedIsFalse(@Param("pickOrderId") pickOrderId: Long): List<PickOrderGroup> | |||
| @Query(""" | |||
| SELECT pog FROM PickOrderGroup pog | |||
| WHERE pog.pickOrderId IN :pickOrderIds | |||
| AND pog.deleted = false | |||
| """) | |||
| fun findByPickOrderIdInAndDeletedIsFalse(@Param("pickOrderIds") pickOrderIds: List<Long>): List<PickOrderGroup> | |||
| @Query(""" | |||
| SELECT pog FROM PickOrderGroup pog | |||
| WHERE pog.name = :name | |||
| AND pog.deleted = false | |||
| """) | |||
| fun findByNameAndDeletedIsFalse(@Param("name") name: String): PickOrderGroup? | |||
| @Query(""" | |||
| SELECT pog FROM PickOrderGroup pog | |||
| WHERE pog.deleted = false | |||
| """) | |||
| fun findAllByDeletedIsFalse(): List<PickOrderGroupInfo> | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.entity.projection | |||
| import org.springframework.beans.factory.annotation.Value | |||
| import java.time.LocalDate | |||
| interface PickOrderGroupInfo { | |||
| val id: Long? | |||
| val name: String? | |||
| val targetDate: LocalDate? | |||
| val pickOrderId: Long? | |||
| } | |||
| @@ -16,6 +16,8 @@ import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository | |||
| import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderInfo | |||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus | |||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderGroup | |||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderGroupRepository | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.* | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.StockOut | |||
| @@ -44,6 +46,7 @@ import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| import java.time.format.DateTimeFormatter | |||
| import kotlin.jvm.optionals.getOrNull | |||
| import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderGroupInfo | |||
| @Service | |||
| open class PickOrderService( | |||
| @@ -54,6 +57,7 @@ open class PickOrderService( | |||
| private val userService: UserService, | |||
| private val stockOutLIneRepository: StockOutLIneRepository, | |||
| private val inventoryLotLineRepository: InventoryLotLineRepository, | |||
| private val pickOrderGroupRepository: PickOrderGroupRepository, | |||
| private val inventoryLotLineService: InventoryLotLineService, | |||
| private val inventoryService: InventoryService, | |||
| private val stockOutRepository: StockOutRepository, | |||
| @@ -152,7 +156,8 @@ open class PickOrderService( | |||
| // Get full pick orders with relationships | |||
| val fullPickOrders = pickOrderRepository.findAllByIdIn(pickOrderIds) | |||
| val groupsByPickOrderId = pickOrderGroupRepository.findByPickOrderIdInAndDeletedIsFalse(pickOrderIds) | |||
| .groupBy { it.pickOrderId } | |||
| // Get all item IDs | |||
| val itemIds = fullPickOrders | |||
| .flatMap { it.pickOrderLines } | |||
| @@ -191,7 +196,9 @@ open class PickOrderService( | |||
| suggestedList = emptyList() // Empty list since you don't need suggestions | |||
| ) | |||
| } | |||
| val groupName = po.id?.let { pickOrderId -> | |||
| groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name | |||
| } ?: "No Group" | |||
| GetPickOrderInfo( | |||
| id = po.id, | |||
| code = po.code, | |||
| @@ -199,6 +206,7 @@ open class PickOrderService( | |||
| type = po.type?.value, | |||
| status = po.status?.value, | |||
| assignTo = po.assignTo?.id, | |||
| groupName = groupName, | |||
| pickOrderLines = pickOrderLineInfos | |||
| ) | |||
| } | |||
| @@ -644,6 +652,8 @@ open class PickOrderService( | |||
| val one = BigDecimal.ONE | |||
| val pos = pickOrderRepository.findAllByIdIn(ids) | |||
| println(pos) | |||
| val groupsByPickOrderId = pickOrderGroupRepository.findByPickOrderIdInAndDeletedIsFalse(ids) | |||
| .groupBy { it.pickOrderId } | |||
| // Get Inventory Data | |||
| val requiredItems = pos | |||
| .flatMap { it.pickOrderLines } | |||
| @@ -698,7 +708,9 @@ open class PickOrderService( | |||
| ) | |||
| // } | |||
| } | |||
| val groupName = po.id?.let { pickOrderId -> | |||
| groupsByPickOrderId[pickOrderId]?.firstOrNull()?.name | |||
| } ?: "No Group" | |||
| // Return | |||
| GetPickOrderInfo( | |||
| id = po.id, | |||
| @@ -707,6 +719,7 @@ open class PickOrderService( | |||
| type = po.type?.value, | |||
| status = po.status?.value, | |||
| assignTo = po.assignTo?.id, | |||
| groupName = groupName, | |||
| pickOrderLines = releasePickOrderLineInfos | |||
| ) | |||
| } | |||
| @@ -873,7 +886,7 @@ open class PickOrderService( | |||
| il.lotNo, | |||
| il.expiryDate, | |||
| w.name as location, | |||
| COALESCE(uc.code, 'N/A') as stockUnit, | |||
| COALESCE(uc.udfudesc, 'N/A') as stockUnit, | |||
| (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) as availableQty, | |||
| spl.qty as requiredQty, | |||
| COALESCE(sol.qty, 0) as actualPickQty, | |||
| @@ -1092,12 +1105,127 @@ open class PickOrderService( | |||
| } else { | |||
| return MessageResponse( | |||
| id = stockOut.id, | |||
| name = stockOut.consoPickOrderCode ?: stockOut.deliveryOrderCode, // 修复:使用 stockOut 而不是 savedStockOut | |||
| code = stockOut.consoPickOrderCode ?: stockOut.deliveryOrderCode, // 修复:使用 stockOut 而不是 savedStockOut | |||
| name = stockOut.consoPickOrderCode ?: stockOut.deliveryOrderCode, | |||
| code = stockOut.consoPickOrderCode ?: stockOut.deliveryOrderCode, | |||
| type = stockOut.type, | |||
| message = "not completed", | |||
| errorPosition = null, | |||
| ) | |||
| } | |||
| } | |||
| open fun createGroup(name: String, targetDate: LocalDate, pickOrderId: Long?): PickOrderGroup { | |||
| val group = PickOrderGroup().apply { | |||
| this.name = name | |||
| this.targetDate = targetDate | |||
| this.pickOrderId = pickOrderId | |||
| } | |||
| return pickOrderGroupRepository.save(group) | |||
| } | |||
| fun getGroupsByPickOrderId(pickOrderId: Long): List<PickOrderGroup> { | |||
| return pickOrderGroupRepository.findByPickOrderIdAndDeletedIsFalse(pickOrderId) | |||
| } | |||
| fun getAllGroupsFromDatabase(): List<Map<String, Any>> { | |||
| val sql = """ | |||
| SELECT id, name, target_date, pick_order_id | |||
| FROM pick_order_group | |||
| WHERE deleted = false | |||
| ORDER BY name ASC | |||
| """.trimIndent() | |||
| return jdbcDao.queryForList(sql, emptyMap<String, Any>()) | |||
| } | |||
| open fun allPickOrdersGroup(): List<PickOrderGroupInfo> { | |||
| return pickOrderGroupRepository.findAllByDeletedIsFalse() | |||
| } | |||
| open fun getLatestGroupNameAndCreate(): MessageResponse { | |||
| // Use the working repository method like the old /latest endpoint | |||
| val allGroups = allPickOrdersGroup() | |||
| val nextGroupName = if (allGroups.isNotEmpty()) { | |||
| // Sort by numeric part of the name (A001 -> 1, A006 -> 6) | |||
| val latestName = allGroups | |||
| .filter { it.name?.startsWith("A") == true } | |||
| .maxByOrNull { group -> | |||
| group.name?.substring(1)?.toIntOrNull() ?: 0 | |||
| }?.name ?: "A000" | |||
| // Generate next group name | |||
| val currentNumber = latestName.substring(1).toIntOrNull() ?: 0 | |||
| val nextNumber = currentNumber + 1 | |||
| "A${nextNumber.toString().padStart(3, '0')}" | |||
| } else { | |||
| "A001" // If no groups exist, return A001 | |||
| } | |||
| // Use the new flexible createNewGroups method | |||
| val request = SavePickOrderGroupRequest( | |||
| names = listOf(nextGroupName), // Create new group with the generated name | |||
| targetDate = null, | |||
| pickOrderId = null | |||
| ) | |||
| return createNewGroups(request) | |||
| } | |||
| fun getLatestGroupName(): String { | |||
| // Use allPickOrdersGroup() instead of the failing repository method | |||
| val allGroups = allPickOrdersGroup() | |||
| return if (allGroups.isNotEmpty()) { | |||
| // Sort by numeric part of the name (A001 -> 1, A006 -> 6) | |||
| val latestName = allGroups | |||
| .filter { it.name?.startsWith("A") == true } | |||
| .maxByOrNull { group -> | |||
| group.name?.substring(1)?.toIntOrNull() ?: 0 | |||
| }?.name ?: "A000" | |||
| // Generate next group name | |||
| val currentNumber = latestName.substring(1).toIntOrNull() ?: 0 | |||
| val nextNumber = currentNumber + 1 | |||
| "A${nextNumber.toString().padStart(3, '0')}" | |||
| } else { | |||
| "A001" // If no groups exist, return A001 | |||
| } | |||
| } | |||
| open fun createNewGroups(request: SavePickOrderGroupRequest): MessageResponse { | |||
| val updatedGroups = mutableListOf<PickOrderGroup>() | |||
| val createdGroups = mutableListOf<PickOrderGroup>() | |||
| // Case 1: Update existing groups by IDs | |||
| request.groupIds?.forEach { groupId -> | |||
| val group = pickOrderGroupRepository.findById(groupId).orElse(null) | |||
| if (group != null) { | |||
| group.targetDate = request.targetDate | |||
| group.pickOrderId = request.pickOrderId | |||
| val savedGroup = pickOrderGroupRepository.save(group) | |||
| updatedGroups.add(savedGroup) | |||
| } | |||
| } | |||
| // Case 2: Create new groups by names | |||
| request.names?.forEach { name -> | |||
| val group = PickOrderGroup().apply { | |||
| this.name = name | |||
| this.targetDate = request.targetDate | |||
| this.pickOrderId = request.pickOrderId | |||
| } | |||
| val savedGroup = pickOrderGroupRepository.save(group) | |||
| createdGroups.add(savedGroup) | |||
| } | |||
| val totalGroups = updatedGroups.size + createdGroups.size | |||
| val allGroups = updatedGroups + createdGroups | |||
| return MessageResponse( | |||
| id = allGroups.firstOrNull()?.id, | |||
| name = allGroups.map { it.name ?: "" }.joinToString(", "), | |||
| code = "groups_processed", | |||
| type = "pick_order_groups", | |||
| message = "Updated ${updatedGroups.size} groups and created ${createdGroups.size} groups successfully", | |||
| errorPosition = "", | |||
| ) | |||
| } | |||
| } | |||
| @@ -12,6 +12,7 @@ import com.ffii.fpsms.modules.pickOrder.web.models.ConsoPickOrderRequest | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.ConsoPickOrderResponse | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.ReleaseConsoPickOrderRequest | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.SearchPickOrderRequest | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.SavePickOrderGroupRequest | |||
| import jakarta.servlet.http.HttpServletRequest | |||
| import jakarta.validation.Valid | |||
| import org.springframework.data.domain.Page | |||
| @@ -29,6 +30,8 @@ import org.springframework.web.bind.annotation.RestController | |||
| import java.time.DateTimeException | |||
| import java.time.LocalDateTime | |||
| import java.time.format.DateTimeFormatter | |||
| import java.time.LocalDate | |||
| import com.ffii.fpsms.modules.pickOrder.entity.projection.PickOrderGroupInfo | |||
| @RestController | |||
| @RequestMapping("/pickOrder") | |||
| @@ -45,11 +48,12 @@ class PickOrderController( | |||
| fun allPickOrdersByPage(@ModelAttribute request: SearchPickOrderRequest): RecordsRes<PickOrderInfo> { | |||
| return pickOrderService.allPickOrdersByPage(request); | |||
| } | |||
| @GetMapping("/getRecordByPageWithStock") | |||
| fun getPickOrdersWithStockBalanceByPage(@ModelAttribute request: SearchPickOrderRequest): RecordsRes<GetPickOrderInfo> { | |||
| return pickOrderService.getPickOrdersWithStockBalanceByPage(request); | |||
| } | |||
| @GetMapping("/getRecordByPage-conso") | |||
| fun allConsoPickOrdersByPage(request: HttpServletRequest): RecordsRes<Map<String, Any>> { | |||
| val criteriaArgs = CriteriaArgsBuilder.withRequest(request) | |||
| @@ -60,9 +64,10 @@ class PickOrderController( | |||
| val pageSize = request.getParameter("pageSize")?.toIntOrNull() ?: 10 // Default to 10 if not provided | |||
| val pageNum = request.getParameter("pageNum")?.toIntOrNull() ?: 1 // Default to 1 if not provided | |||
| val fullList = pickOrderService.getConsoPickOrderList(criteriaArgs) | |||
| val paginatedList = PagingUtils.getPaginatedList(fullList,pageSize, pageNum) | |||
| val paginatedList = PagingUtils.getPaginatedList(fullList, pageSize, pageNum) | |||
| return RecordsRes(paginatedList, fullList.size) | |||
| } | |||
| @PostMapping("/assign") | |||
| fun assignPickOrders(@RequestBody request: Map<String, Any>): MessageResponse { | |||
| val pickOrderIds = (request["pickOrderIds"] as List<*>).map { it.toString().toLong() } | |||
| @@ -77,6 +82,7 @@ class PickOrderController( | |||
| val assignTo = request["assignTo"].toString().toLong() | |||
| return pickOrderService.releasePickOrders(pickOrderIds, assignTo) | |||
| } | |||
| @GetMapping("/get-pickorder-line-byPage") | |||
| fun getPickOrderLine(request: HttpServletRequest): RecordsRes<Map<String, Any>> { | |||
| val criteriaArgs = CriteriaArgsBuilder.withRequest(request) | |||
| @@ -85,7 +91,7 @@ class PickOrderController( | |||
| val pageSize = request.getParameter("pageSize")?.toIntOrNull() ?: 10 // Default to 10 if not provided | |||
| val pageNum = request.getParameter("pageNum")?.toIntOrNull() ?: 1 // Default to 1 if not provided | |||
| val fullList = pickOrderService.getPickOrderLine(criteriaArgs) | |||
| val paginatedList = PagingUtils.getPaginatedList(fullList,pageSize, pageNum) | |||
| val paginatedList = PagingUtils.getPaginatedList(fullList, pageSize, pageNum) | |||
| return RecordsRes(paginatedList, fullList.size) | |||
| } | |||
| @@ -121,16 +127,19 @@ class PickOrderController( | |||
| fun getAllPickOrdersInfo(): GetPickOrderInfoResponse { | |||
| return pickOrderService.getAllPickOrdersInfo(); | |||
| } | |||
| @PostMapping("/releaseConso") | |||
| fun releaseConsoPickOrderAction(@Valid @RequestBody request: ReleaseConsoPickOrderRequest): ReleasePickOrderInfoResponse { | |||
| return pickOrderService.releaseConsoPickOrderAction(request) | |||
| } | |||
| @PostMapping("/release-assigned") | |||
| fun releaseAssignedPickOrders(@RequestBody request: Map<String, Any>): MessageResponse { | |||
| val pickOrderIds = (request["pickOrderIds"] as List<*>).map { it.toString().toLong() } | |||
| val assignTo = request["assignTo"].toString().toLong() | |||
| return pickOrderService.releaseAssignedPickOrders(pickOrderIds, assignTo) | |||
| } | |||
| // Start Pick Order | |||
| @GetMapping("/pickConso/{consoCode}") | |||
| fun pickConsoPickOrderInfo(@PathVariable consoCode: String): ReleasePickOrderInfoResponse { | |||
| @@ -153,4 +162,60 @@ class PickOrderController( | |||
| } | |||
| @PostMapping("/groups") | |||
| fun createGroup(@RequestBody request: Map<String, Any>): ResponseEntity<Map<String, Any>> { | |||
| println("request: $request") | |||
| val name = request["name"] as String | |||
| val targetDateStr = request["targetDate"] as String | |||
| val targetDate = LocalDate.parse(targetDateStr) | |||
| val pickOrderId = when (val value = request["pickOrderId"]) { | |||
| is Int -> value.toLong() | |||
| is Long -> value | |||
| is String -> value.toLongOrNull() | |||
| else -> null | |||
| } | |||
| println("name: $name") | |||
| println("targetDate: $targetDate") | |||
| println("pickOrderId: $pickOrderId") | |||
| val group = pickOrderService.createGroup(name, targetDate, pickOrderId) | |||
| return ResponseEntity.ok( | |||
| mapOf( | |||
| "id" to (group.id ?: 0L), | |||
| "name" to (group.name ?: ""), | |||
| "targetDate" to (group.targetDate?.toString() ?: ""), | |||
| "pickOrderId" to (group.pickOrderId ?: 0L) | |||
| ) | |||
| ) | |||
| //println("Response: $response") | |||
| } | |||
| @GetMapping("/groups/{pickOrderId}") | |||
| fun getGroupsByPickOrder(@PathVariable pickOrderId: Long): ResponseEntity<List<Map<String, Any>>> { | |||
| val groups = pickOrderService.getGroupsByPickOrderId(pickOrderId) | |||
| val response = groups.map { group -> | |||
| mapOf( | |||
| "id" to (group.id ?: 0L), | |||
| "name" to (group.name ?: ""), | |||
| "targetDate" to (group.targetDate?.toString() ?: ""), | |||
| "pickOrderId" to (group.pickOrderId ?: 0L) | |||
| ) | |||
| } | |||
| return ResponseEntity.ok(response) | |||
| } | |||
| @GetMapping("/groups/latest") | |||
| fun getLatestGroupName(): MessageResponse { | |||
| return pickOrderService.getLatestGroupNameAndCreate() | |||
| } | |||
| @GetMapping("/groups/list") | |||
| fun getAllGroups(): List<PickOrderGroupInfo> { | |||
| return pickOrderService.allPickOrdersGroup() | |||
| } | |||
| @PostMapping("/groups/create") | |||
| fun createNewGroups(@Valid @RequestBody request: SavePickOrderGroupRequest): MessageResponse { | |||
| return pickOrderService.createNewGroups(request) | |||
| } | |||
| } | |||
| @@ -35,6 +35,7 @@ data class GetPickOrderInfo( | |||
| val type: String?, | |||
| val status: String?, | |||
| val assignTo: Long?, | |||
| val groupName: String?, | |||
| val pickOrderLines: List<GetPickOrderLineInfo> | |||
| ) | |||
| @@ -0,0 +1,12 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.web.models | |||
| import java.time.LocalDate | |||
| data class SavePickOrderGroupRequest ( | |||
| val groupIds: List<Long>? = null, | |||
| val names: List<String>? = null, | |||
| val targetDate: LocalDate?=null, | |||
| val pickOrderId: Long? = null | |||
| ) | |||
| @@ -0,0 +1,20 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset cyril:create pick order | |||
| CREATE TABLE `pick_order_group` | |||
| ( | |||
| `id` INT NOT NULL AUTO_INCREMENT, | |||
| `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||
| `createdBy` VARCHAR(30) NULL DEFAULT NULL, | |||
| `version` INT NOT NULL DEFAULT '0', | |||
| `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||
| `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, | |||
| `deleted` TINYINT(1) NOT NULL DEFAULT '0', | |||
| `pick_order_id` INT , | |||
| `name` VARCHAR(50) NOT NULL UNIQUE, | |||
| `target_date` DATE NOT NULL, | |||
| `releasedBy` INT NULL, | |||
| CONSTRAINT pk_pick_order_group PRIMARY KEY (id), | |||
| CONSTRAINT `FK_PICK_ORDER_GROUP_ON_PICK_ORDER_ID` FOREIGN KEY (`pick_order_id`) REFERENCES `pick_order` (`id`), | |||
| CONSTRAINT `FK_PICK_ORDER_GROUP_ON_RELEASEDBY` FOREIGN KEY (`releasedBy`) REFERENCES `user` (`id`) | |||
| ); | |||