From eca79a07a77cc2cb7b87e981f776dc60355d023b Mon Sep 17 00:00:00 2001 From: kelvinsuen Date: Thu, 23 Oct 2025 14:30:08 +0800 Subject: [PATCH 1/4] update qc --- .../fpsms/modules/master/entity/QcCategory.kt | 34 +++++++++++++++---- .../master/entity/QcCategoryRepository.kt | 22 ++++++++++++ .../modules/master/entity/QcItemCategory.kt | 5 +++ .../master/entity/QcItemCategoryRepository.kt | 3 ++ .../modules/master/service/ItemsService.kt | 12 +++++++ .../master/service/QcCategoryService.kt | 17 +++++++++- .../modules/master/service/QcItemService.kt | 14 ++++++-- .../master/web/QcCategoryController.kt | 11 ++++-- .../modules/master/web/QcItemController.kt | 17 +++++++++- .../qc/entity/projection/QcCategoryInfo.kt | 26 ++++++++++++++ .../01_create_items_qc_category_mapping.sql | 9 +++++ .../02_alter_qc_item_category.sql | 5 +++ .../03_alter_qc_category.sql | 5 +++ .../04_alter_items_qc_category_mapping.sql | 5 +++ 14 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/ffii/fpsms/modules/qc/entity/projection/QcCategoryInfo.kt create mode 100644 src/main/resources/db/changelog/changes/20251009_01_kelvinS/01_create_items_qc_category_mapping.sql create mode 100644 src/main/resources/db/changelog/changes/20251009_01_kelvinS/02_alter_qc_item_category.sql create mode 100644 src/main/resources/db/changelog/changes/20251009_01_kelvinS/03_alter_qc_category.sql create mode 100644 src/main/resources/db/changelog/changes/20251009_01_kelvinS/04_alter_items_qc_category_mapping.sql diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategory.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategory.kt index be45bc1..0946dd2 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategory.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategory.kt @@ -1,14 +1,10 @@ package com.ffii.fpsms.modules.master.entity +import com.fasterxml.jackson.annotation.JsonBackReference import com.fasterxml.jackson.annotation.JsonManagedReference import com.ffii.core.entity.BaseEntity -import jakarta.persistence.CascadeType -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.JoinColumn -import jakarta.persistence.JoinTable -import jakarta.persistence.OneToMany -import jakarta.persistence.Table +import com.ffii.core.entity.IdEntity +import jakarta.persistence.* import jakarta.validation.constraints.NotNull @Entity @@ -22,6 +18,10 @@ open class QcCategory : BaseEntity() { @Column(name = "name") open var name: String? = null + @NotNull + @Column(name = "description") + open var description: String? = null + // @OneToMany(cascade = [CascadeType.ALL]) // @JoinTable( // name = "qc_item_category", @@ -33,4 +33,24 @@ open class QcCategory : BaseEntity() { @JsonManagedReference @OneToMany(mappedBy = "qcCategory", cascade = [CascadeType.ALL], orphanRemoval = true) open var qcItemCategory: MutableList = mutableListOf() +} + +@Entity +@Table(name = "items_qc_category_mapping") +open class ItemsQcCategoryMapping : IdEntity() { + @NotNull + @Column(name = "itemId") + open var itemId: Long? = null + + @NotNull + @Column(name = "qcCategoryId") + open var qcCategoryId: Long? = null + + @NotNull + @Column(name = "type") + open var type: String? = null +// +// @ManyToOne(fetch = FetchType.LAZY) +// @JoinColumn(name = "qcCategoryId") +// val qcCategory: QcCategory? = null } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategoryRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategoryRepository.kt index 6fd7a66..d25cd15 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategoryRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategoryRepository.kt @@ -2,6 +2,8 @@ package com.ffii.fpsms.modules.master.entity import com.ffii.core.support.AbstractRepository import com.ffii.fpsms.modules.master.entity.projections.QcCategoryCombo +import com.ffii.fpsms.modules.qc.entity.projection.QcCategoryInfo +import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository @Repository @@ -9,4 +11,24 @@ interface QcCategoryRepository : AbstractRepository { fun findAllByDeletedIsFalse(): List; fun findQcCategoryComboByDeletedIsFalse(): List; + +// @Query( +// value = """ +// SELECT qcc.* FROM qc_category qcc +// LEFT JOIN items_qc_category_mapping map ON map.qcCategoryId = qcc.id +// WHERE map.type = :type AND map.itemId = :itemId AND qcc.deleted = false +// ORDER BY qcc.id +// LIMIT 1 +// """, +// nativeQuery = true +// ) + @Query( + """ + SELECT qcc FROM QcCategory qcc + JOIN ItemsQcCategoryMapping map ON qcc.id = map.qcCategoryId + WHERE map.itemId = :itemId AND map.type = :type AND qcc.deleted = false + """ + ) + fun findQcCategoryInfoByItemIdAndType(itemId: Long, type: String): QcCategoryInfo?; +// fun findByItemIdAndType(itemId: Long, type: String): QcCategory?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/QcItemCategory.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/QcItemCategory.kt index 8145eb1..d9ea2db 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/QcItemCategory.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/QcItemCategory.kt @@ -22,7 +22,12 @@ open class QcItemCategory : IdEntity() { @JoinColumn(name = "qcItemId") open var qcItem: QcItem? = null + @NotNull + @Column(name = "order") + open var order: Int = 0 + @Size(max = 1000) @Column(name = "description", length = 1000) open var description: String? = null + } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/QcItemCategoryRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/QcItemCategoryRepository.kt index 1528ddf..c4d3376 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/QcItemCategoryRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/QcItemCategoryRepository.kt @@ -1,8 +1,11 @@ package com.ffii.fpsms.modules.master.entity import com.ffii.core.support.AbstractRepository +import com.ffii.fpsms.modules.qc.entity.projection.QcCategoryInfo +import com.ffii.fpsms.modules.qc.entity.projection.QcItemInfo import org.springframework.stereotype.Repository @Repository interface QcItemCategoryRepository : AbstractRepository { + fun findAllByQcCategoryId(qcCategoryId : Long): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt index 26e5e73..f51e840 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt @@ -373,4 +373,16 @@ open class ItemsService( return jdbcDao.queryForList(sql.toString(), args); } + + open fun getItemsIdWithSameCategoryForQc(args: Map): List { + val sql = StringBuilder("SELECT" + + " i.id " + + " FROM items i " + + " INNER JOIN items_qc_category_mapping iqcm ON iqcm.itemId = i.id AND iqcm.type = :qcType " + + " WHERE i.deleted = false " + + " AND LEFT(i.code, 2) = (SELECT LEFT(code, 2) FROM items WHERE id = :itemId)" + + " AND i.id != :itemId " + ) + return jdbcDao.queryForInts(sql.toString(), args); + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/QcCategoryService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/QcCategoryService.kt index 055d2c1..4c76f02 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/QcCategoryService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/QcCategoryService.kt @@ -4,11 +4,13 @@ import com.ffii.core.support.AbstractBaseEntityService import com.ffii.fpsms.modules.master.entity.QcCategory import com.ffii.fpsms.modules.master.entity.QcCategoryRepository import com.ffii.fpsms.modules.master.entity.projections.QcCategoryCombo +import com.ffii.fpsms.modules.qc.entity.projection.QcCategoryInfo import org.springframework.stereotype.Service @Service open class QcCategoryService( - private val qcCategoryRepository: QcCategoryRepository + private val qcCategoryRepository: QcCategoryRepository, + private val itemsService: ItemsService ) { open fun allQcCategories(): List { return qcCategoryRepository.findAllByDeletedIsFalse() @@ -17,4 +19,17 @@ open class QcCategoryService( open fun getQcCategoryCombo(): List { return qcCategoryRepository.findQcCategoryComboByDeletedIsFalse(); } + + open fun getQcCategoryInfoByMapping(itemId : Long, type: String): QcCategoryInfo? { + var result = qcCategoryRepository.findQcCategoryInfoByItemIdAndType(itemId, type) + if (result == null) { // Temporarily fix for missing QC template (Auto find template from similar items) + val args = mapOf( + "itemId" to itemId, + "qcType" to type + ) + val similarItemIds = itemsService.getItemsIdWithSameCategoryForQc(args); + result = qcCategoryRepository.findQcCategoryInfoByItemIdAndType(similarItemIds[0].toLong(), type) + } + return result; + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/QcItemService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/QcItemService.kt index 75f9edc..d5641a0 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/QcItemService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/QcItemService.kt @@ -1,11 +1,14 @@ package com.ffii.fpsms.modules.master.service -import com.ffii.core.support.AbstractBaseEntityService -import com.ffii.core.support.JdbcDao +import com.ffii.core.exception.NotFoundException +import com.ffii.fpsms.modules.master.entity.QcCategoryRepository import com.ffii.fpsms.modules.master.entity.QcItem +import com.ffii.fpsms.modules.master.entity.QcItemCategoryRepository import com.ffii.fpsms.modules.master.entity.QcItemRepository import com.ffii.fpsms.modules.master.web.models.SaveQcItemRequest import com.ffii.fpsms.modules.master.web.models.SaveQcItemResponse +import com.ffii.fpsms.modules.qc.entity.projection.QcCategoryInfo +import com.ffii.fpsms.modules.qc.entity.projection.QcItemInfo import jakarta.validation.Valid import org.springframework.stereotype.Service import org.springframework.web.bind.annotation.RequestBody @@ -13,6 +16,8 @@ import org.springframework.web.bind.annotation.RequestBody @Service open class QcItemService( private val qcItemRepository: QcItemRepository, + private val qcCategoryRepository: QcCategoryRepository, + private val qcItemCategoryRepository: QcItemCategoryRepository, ) { open fun allQcItems(): List { return qcItemRepository.findAllByDeletedIsFalse() @@ -26,6 +31,11 @@ open class QcItemService( return qcItemRepository.findByIdAndDeletedIsFalse(id) } + open fun findQcItemsByTemplate(itemId: Long, type: String): List? { + val qcCategoryInfo = qcCategoryRepository.findQcCategoryInfoByItemIdAndType(itemId, type) ?: throw NotFoundException() + return qcItemCategoryRepository.findAllByQcCategoryId(qcCategoryInfo.id) + } + open fun markDeleted(id: Long): List { val qcItem = qcItemRepository.findById(id).orElseThrow().apply { deleted = true diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/QcCategoryController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/QcCategoryController.kt index 0eac19f..e493c1d 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/QcCategoryController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/QcCategoryController.kt @@ -1,11 +1,11 @@ package com.ffii.fpsms.modules.master.web +import com.ffii.core.exception.NotFoundException import com.ffii.fpsms.modules.master.entity.QcCategory import com.ffii.fpsms.modules.master.entity.projections.QcCategoryCombo import com.ffii.fpsms.modules.master.service.QcCategoryService -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import com.ffii.fpsms.modules.qc.entity.projection.QcCategoryInfo +import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/qcCategories") @@ -21,4 +21,9 @@ class QcCategoryController( fun getQcCategoryCombo(): List { return qcCategoryService.getQcCategoryCombo(); } + + @GetMapping("/items") + fun getQcCategoryByTemplate(@RequestParam itemId: Long, @RequestParam(defaultValue = "IQC") type: String): QcCategoryInfo { + return qcCategoryService.getQcCategoryInfoByMapping(itemId, type) ?: throw NotFoundException() + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/QcItemController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/QcItemController.kt index 02bd270..ab9ed0d 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/QcItemController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/QcItemController.kt @@ -7,7 +7,10 @@ import com.ffii.fpsms.modules.master.entity.QcItemRepository import com.ffii.fpsms.modules.master.service.QcItemService import com.ffii.fpsms.modules.master.web.models.SaveQcItemRequest import com.ffii.fpsms.modules.master.web.models.SaveQcItemResponse +import com.ffii.fpsms.modules.qc.entity.projection.QcCategoryInfo +import com.ffii.fpsms.modules.qc.entity.projection.QcItemInfo import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.web.bind.annotation.* @@ -37,8 +40,20 @@ class QcItemController( return qcItemService.findQcItemById(id) ?: throw NotFoundException() } + @GetMapping("/template") + fun qcItemsByTemplate(@RequestParam itemId: Long, @RequestParam(defaultValue = "IQC") type: String): List { + return qcItemService.findQcItemsByTemplate(itemId, type) ?: throw NotFoundException() + } + @PostMapping("/save") fun saveQcItem(@Valid @RequestBody request: SaveQcItemRequest): SaveQcItemResponse { return qcItemService.saveQcItem(request) } -} \ No newline at end of file +} + +data class GetQcItemRequest( + @field:NotBlank(message = "Item Id cannot be empty") + val itemId: Long, + @field:NotBlank(message = "Type cannot be empty") + val type: String, +) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/qc/entity/projection/QcCategoryInfo.kt b/src/main/java/com/ffii/fpsms/modules/qc/entity/projection/QcCategoryInfo.kt new file mode 100644 index 0000000..8b6513d --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/qc/entity/projection/QcCategoryInfo.kt @@ -0,0 +1,26 @@ +package com.ffii.fpsms.modules.qc.entity.projection + +import com.ffii.fpsms.modules.master.entity.QcItemCategory +import org.springframework.beans.factory.annotation.Value + +interface QcCategoryInfo { + val id: Long + val code: String + val name: String? + val description: String? + @get:Value("#{target.qcItemCategory}") + var qcItems: List +} + +interface QcItemInfo { + val id: Long + val order: Int + @get:Value("#{target.qcItem.id}") + val qcItemId: Long + @get:Value("#{target.qcItem.code}") + val code: String + @get:Value("#{target.qcItem.name}") + val name: String? + @get:Value("#{target.qcItem.description}") + val description: String? +} \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20251009_01_kelvinS/01_create_items_qc_category_mapping.sql b/src/main/resources/db/changelog/changes/20251009_01_kelvinS/01_create_items_qc_category_mapping.sql new file mode 100644 index 0000000..9322699 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251009_01_kelvinS/01_create_items_qc_category_mapping.sql @@ -0,0 +1,9 @@ +-- liquibase formatted sql +-- changeset kelvinS:create items qc category mapping table + +CREATE TABLE `items_qc_category_mapping` ( + `id` INT NOT NULL, + `itemId` INT NOT NULL, + `qcCategoryId` INT NOT NULL, + `type` VARCHAR(45) NOT NULL, + PRIMARY KEY (`id`)); \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20251009_01_kelvinS/02_alter_qc_item_category.sql b/src/main/resources/db/changelog/changes/20251009_01_kelvinS/02_alter_qc_item_category.sql new file mode 100644 index 0000000..a7a1e37 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251009_01_kelvinS/02_alter_qc_item_category.sql @@ -0,0 +1,5 @@ +-- liquibase formatted sql +-- changeset kelvinS:alter qc item category table + +ALTER TABLE `qc_item_category` +ADD COLUMN `order` INT NOT NULL AFTER `qcCategoryId`; \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20251009_01_kelvinS/03_alter_qc_category.sql b/src/main/resources/db/changelog/changes/20251009_01_kelvinS/03_alter_qc_category.sql new file mode 100644 index 0000000..22ab73f --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251009_01_kelvinS/03_alter_qc_category.sql @@ -0,0 +1,5 @@ +-- liquibase formatted sql +-- changeset kelvinS:alter qc category table + +ALTER TABLE `qc_category` +ADD COLUMN `description` VARCHAR(255) NULL AFTER `name`; \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/20251009_01_kelvinS/04_alter_items_qc_category_mapping.sql b/src/main/resources/db/changelog/changes/20251009_01_kelvinS/04_alter_items_qc_category_mapping.sql new file mode 100644 index 0000000..b55b74c --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251009_01_kelvinS/04_alter_items_qc_category_mapping.sql @@ -0,0 +1,5 @@ +-- liquibase formatted sql +-- changeset kelvinS:alter item qc category mapping table + +ALTER TABLE `items_qc_category_mapping` +CHANGE COLUMN `id` `id` INT NOT NULL AUTO_INCREMENT ; From 1bfaa5553aee35cdab628a239ac654d517d17033 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 23 Oct 2025 17:07:03 +0800 Subject: [PATCH 2/4] quick update for jwt --- .../security/jwt/web/JwtAuthenticationController.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/config/security/jwt/web/JwtAuthenticationController.java b/src/main/java/com/ffii/fpsms/config/security/jwt/web/JwtAuthenticationController.java index 3a0d94f..2fdd62f 100644 --- a/src/main/java/com/ffii/fpsms/config/security/jwt/web/JwtAuthenticationController.java +++ b/src/main/java/com/ffii/fpsms/config/security/jwt/web/JwtAuthenticationController.java @@ -108,16 +108,17 @@ public class JwtAuthenticationController { } private ResponseEntity createAuthTokenResponse(JwtRequest authenticationRequest) { - final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); - if (userDetails == null) { +// final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); + final User user = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); + if (user == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(new ExceptionResponse(authenticationRequest.getUsername() + " not yet register in the system.", null)); } - final String accessToken = jwtTokenUtil.generateToken(userDetails); - final String refreshToken = jwtTokenUtil.createRefreshToken(userDetails.getUsername()).getToken(); + final String accessToken = jwtTokenUtil.generateToken(user); + final String refreshToken = jwtTokenUtil.createRefreshToken(user.getUsername()).getToken(); - User user = userRepository.findByName(authenticationRequest.getUsername()).get(0); +// User user = userRepository.findByName(authenticationRequest.getUsername()).get(0); List abilities = new ArrayList<>(); final Set userAuthority = userAuthorityService.getUserAuthority(user); From 827a224f2612d7a92acb66e1d1614b96549c5160 Mon Sep 17 00:00:00 2001 From: kelvinsuen Date: Thu, 23 Oct 2025 19:04:50 +0800 Subject: [PATCH 3/4] update qc default --- .../com/ffii/fpsms/modules/master/entity/QcCategory.kt | 4 ++++ .../modules/master/entity/QcCategoryRepository.kt | 1 + .../ffii/fpsms/modules/master/service/ItemsService.kt | 3 ++- .../fpsms/modules/master/service/QcCategoryService.kt | 10 +++++++++- .../20251023_02_kelvinS/01_alter_qc_category.sql | 6 ++++++ 5 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/db/changelog/changes/20251023_02_kelvinS/01_alter_qc_category.sql diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategory.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategory.kt index 0946dd2..38d9a17 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategory.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategory.kt @@ -22,6 +22,10 @@ open class QcCategory : BaseEntity() { @Column(name = "description") open var description: String? = null + @NotNull + @Column(name = "isDefault") + open var isDefault: Boolean = false + // @OneToMany(cascade = [CascadeType.ALL]) // @JoinTable( // name = "qc_item_category", diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategoryRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategoryRepository.kt index d25cd15..6283526 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategoryRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/QcCategoryRepository.kt @@ -30,5 +30,6 @@ interface QcCategoryRepository : AbstractRepository { """ ) fun findQcCategoryInfoByItemIdAndType(itemId: Long, type: String): QcCategoryInfo?; + fun findQcCategoryInfoByIsDefault(isDefault: Boolean): QcCategoryInfo?; // fun findByItemIdAndType(itemId: Long, type: String): QcCategory?; } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt index f51e840..fad1f66 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt @@ -379,7 +379,8 @@ open class ItemsService( + " i.id " + " FROM items i " + " INNER JOIN items_qc_category_mapping iqcm ON iqcm.itemId = i.id AND iqcm.type = :qcType " - + " WHERE i.deleted = false " + + " LEFT JOIN qc_category qcc ON qcc.id = iqcm.qcCategoryId " + + " WHERE i.deleted = false AND qcc.deleted = false" + " AND LEFT(i.code, 2) = (SELECT LEFT(code, 2) FROM items WHERE id = :itemId)" + " AND i.id != :itemId " ) diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/QcCategoryService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/QcCategoryService.kt index 4c76f02..0d1fb09 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/QcCategoryService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/QcCategoryService.kt @@ -28,7 +28,15 @@ open class QcCategoryService( "qcType" to type ) val similarItemIds = itemsService.getItemsIdWithSameCategoryForQc(args); - result = qcCategoryRepository.findQcCategoryInfoByItemIdAndType(similarItemIds[0].toLong(), type) + + // Comment the lines below to disable auto matching QC from similar items + result = if (similarItemIds.isNotEmpty()) + qcCategoryRepository.findQcCategoryInfoByItemIdAndType(similarItemIds[0].toLong(), type) + else null + // + if (result == null) { // Use Default Template + result = qcCategoryRepository.findQcCategoryInfoByIsDefault(true) + } } return result; } diff --git a/src/main/resources/db/changelog/changes/20251023_02_kelvinS/01_alter_qc_category.sql b/src/main/resources/db/changelog/changes/20251023_02_kelvinS/01_alter_qc_category.sql new file mode 100644 index 0000000..6b1504b --- /dev/null +++ b/src/main/resources/db/changelog/changes/20251023_02_kelvinS/01_alter_qc_category.sql @@ -0,0 +1,6 @@ +-- liquibase formatted sql +-- changeset kelvinS:alter qc category table + +ALTER TABLE `qc_category` +ADD COLUMN `isDefault` TINYINT(1) NOT NULL DEFAULT '0' AFTER `deleted`, +CHANGE COLUMN `name` `name` VARCHAR(255) NOT NULL ; \ No newline at end of file From c8945091128418ad6bb815c3ae635e121f024cfd Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 24 Oct 2025 18:14:28 +0800 Subject: [PATCH 4/4] update --- .../pickOrder/service/PickOrderService.kt | 559 ++++++++++-------- 1 file changed, 317 insertions(+), 242 deletions(-) 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 ff0f297..b71b17b 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 @@ -1250,8 +1250,37 @@ open class PickOrderService( println("⚠️ WARNING: do_pick_order $doPickOrderId not found, skipping") return@forEach } - + println("🔍 Processing do_pick_order ID: ${dpo.id}, ticket: ${dpo.ticketNo}") + + // ✅ 新增:检查这个 do_pick_order 下是否还有其他未完成的 pick orders + val allDoPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(dpo.id!!) + val allPickOrderIdsInDpo = allDoPickOrderLines.mapNotNull { it.pickOrderId }.distinct() + + println("🔍 DEBUG: This do_pick_order has ${allPickOrderIdsInDpo.size} pick orders") + + // ✅ 检查每个 pick order 的状态 + val pickOrderStatuses = allPickOrderIdsInDpo.map { poId -> + val po = pickOrderRepository.findById(poId).orElse(null) + poId to po?.status + } + + pickOrderStatuses.forEach { (poId, status) -> + println("🔍 DEBUG: Pick order $poId status: $status") + } + + // ✅ 只有当所有 pick orders 都是 COMPLETED 状态时,才移动到 record 表 + val allPickOrdersCompleted = pickOrderStatuses.all { (_, status) -> + status == PickOrderStatus.COMPLETED + } + + if (!allPickOrdersCompleted) { + println("⏳ Not all pick orders in this do_pick_order are completed, keeping do_pick_order alive") + println("🔍 DEBUG: Completed pick orders: ${pickOrderStatuses.count { it.second == PickOrderStatus.COMPLETED }}/${pickOrderStatuses.size}") + return@forEach // ✅ 跳过这个 do_pick_order,不删除它 + } + + println("✅ All pick orders in this do_pick_order are completed, moving to record table") // 2) 先复制 do_pick_order -> do_pick_order_record val dpoRecord = DoPickOrderRecord( @@ -3516,261 +3545,307 @@ ORDER BY val enrichedResults = filteredResults return enrichedResults } - open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map { - println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===") - println("userId filter: $userId") - - val user = userService.find(userId).orElse(null) - if (user == null) { - println("❌ User not found: $userId") - return emptyMap() - } - - // ✅ Step 1: 获取 do_pick_order 基本信息 - val doPickOrderSql = """ - SELECT DISTINCT - dpo.id as do_pick_order_id, - dpo.ticket_no, - dpo.store_id, - dpo.TruckLanceCode, - dpo.truck_departure_time, - dpo.ShopCode, - dpo.ShopName - FROM fpsmsdb.do_pick_order dpo - INNER JOIN fpsmsdb.do_pick_order_line dpol ON dpol.do_pick_order_id = dpo.id AND dpol.deleted = 0 - INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id - WHERE po.assignTo = :userId - AND po.type = 'do' - AND po.status IN ('assigned', 'released', 'picking') - AND po.deleted = false - AND dpo.deleted = false - LIMIT 1 - """.trimIndent() - - val doPickOrderInfo = jdbcDao.queryForMap(doPickOrderSql, mapOf("userId" to userId)).orElse(null) - if (doPickOrderInfo == null) { - println("❌ No do_pick_order found for user $userId") - return mapOf( - "fgInfo" to null, - "pickOrders" to emptyList() - ) - } - - val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong() - println("🔍 Found do_pick_order ID: $doPickOrderId") - - // ✅ Step 2: 获取该 do_pick_order 下的所有 pick orders - val pickOrdersSql = """ - SELECT DISTINCT - dpol.pick_order_id, - dpol.pick_order_code, - dpol.do_order_id, - dpol.delivery_order_code, - po.consoCode, - po.status, - DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate - FROM fpsmsdb.do_pick_order_line dpol - INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id - WHERE dpol.do_pick_order_id = :doPickOrderId - AND dpol.deleted = 0 - AND po.deleted = false - ORDER BY dpol.pick_order_id - """.trimIndent() + // ... existing code ... + +open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map { + println("=== Debug: getAllPickOrderLotsWithDetailsHierarchical (NEW STRUCTURE) ===") + println("userId filter: $userId") + + val user = userService.find(userId).orElse(null) + if (user == null) { + println("❌ User not found: $userId") + return emptyMap() + } + + // ✅ Step 1: 获取 do_pick_order 基本信息 + val doPickOrderSql = """ + SELECT DISTINCT + dpo.id as do_pick_order_id, + dpo.ticket_no, + dpo.store_id, + dpo.TruckLanceCode, + dpo.truck_departure_time, + dpo.ShopCode, + dpo.ShopName + FROM fpsmsdb.do_pick_order dpo + INNER JOIN fpsmsdb.do_pick_order_line dpol ON dpol.do_pick_order_id = dpo.id AND dpol.deleted = 0 + INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id + WHERE po.assignTo = :userId + AND po.type = 'do' + AND EXISTS ( + SELECT 1 + FROM fpsmsdb.do_pick_order_line dpol2 + INNER JOIN fpsmsdb.pick_order po2 ON po2.id = dpol2.pick_order_id + WHERE dpol2.do_pick_order_id = dpo.id + AND dpol2.deleted = 0 + AND po2.status IN ('assigned', 'released', 'picking') + AND po2.deleted = false + ) + AND po.deleted = false + AND dpo.deleted = false + LIMIT 1 + """.trimIndent() + + val doPickOrderInfo = jdbcDao.queryForMap(doPickOrderSql, mapOf("userId" to userId)).orElse(null) + if (doPickOrderInfo == null) { + println("❌ No do_pick_order found for user $userId") + return mapOf( + "fgInfo" to null, + "pickOrders" to emptyList() + ) + } + + val doPickOrderId = (doPickOrderInfo["do_pick_order_id"] as? Number)?.toLong() + println("🔍 Found do_pick_order ID: $doPickOrderId") + + // ✅ Step 2: 获取该 do_pick_order 下的所有 pick orders + val pickOrdersSql = """ + SELECT DISTINCT + dpol.pick_order_id, + dpol.pick_order_code, + dpol.do_order_id, + dpol.delivery_order_code, + po.consoCode, + po.status, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as targetDate + FROM fpsmsdb.do_pick_order_line dpol + INNER JOIN fpsmsdb.pick_order po ON po.id = dpol.pick_order_id + WHERE dpol.do_pick_order_id = :doPickOrderId + AND dpol.deleted = 0 + AND po.deleted = false + ORDER BY dpol.pick_order_id + """.trimIndent() + + val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("doPickOrderId" to doPickOrderId)) + println("🔍 Found ${pickOrdersInfo.size} pick orders") + + // ✅ Step 3: 为每个 pick order 获取 lines 和 lots + val allPickOrderLines = mutableListOf>() + val lineCountsPerPickOrder = mutableListOf() + val pickOrderIds = mutableListOf() + val pickOrderCodes = mutableListOf() + val doOrderIds = mutableListOf() + val deliveryOrderCodes = mutableListOf() + + pickOrdersInfo.forEach { poInfo -> + val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() + val pickOrderCode = poInfo["pick_order_code"] as? String + val doOrderId = (poInfo["do_order_id"] as? Number)?.toLong() + val deliveryOrderCode = poInfo["delivery_order_code"] as? String - val pickOrdersInfo = jdbcDao.queryForList(pickOrdersSql, mapOf("doPickOrderId" to doPickOrderId)) - println("🔍 Found ${pickOrdersInfo.size} pick orders") + // 收集 IDs 和 codes + pickOrderId?.let { pickOrderIds.add(it) } + pickOrderCode?.let { pickOrderCodes.add(it) } + doOrderId?.let { doOrderIds.add(it) } + deliveryOrderCode?.let { deliveryOrderCodes.add(it) } - // ✅ Step 3: 为每个 pick order 获取 lines 和 lots(包括 null stock 的) - val pickOrders = pickOrdersInfo.map { poInfo -> - val pickOrderId = (poInfo["pick_order_id"] as? Number)?.toLong() - - // ✅ 查询该 pick order 的所有 lines 和 lots - val linesSql = """ - SELECT - po.id as pickOrderId, - po.code as pickOrderCode, - po.consoCode as pickOrderConsoCode, - DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, - po.type as pickOrderType, - po.status as pickOrderStatus, - po.assignTo as pickOrderAssignTo, - - pol.id as pickOrderLineId, - pol.qty as pickOrderLineRequiredQty, - pol.status as pickOrderLineStatus, - - i.id as itemId, - i.code as itemCode, - i.name as itemName, - uc.code as uomCode, - uc.udfudesc as uomDesc, - uc.udfShortDesc as uomShortDesc, - - ill.id as lotId, - il.lotNo, - DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, - w.name as location, - COALESCE(uc.udfudesc, 'N/A') as stockUnit, - w.`order` as routerIndex, - w.code as routerRoute, - - CASE - WHEN sol.status = 'rejected' THEN NULL - ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) - END as availableQty, - - COALESCE(spl.qty, 0) as requiredQty, - COALESCE(sol.qty, 0) as actualPickQty, - spl.id as suggestedPickLotId, - ill.status as lotStatus, - sol.id as stockOutLineId, - sol.status as stockOutLineStatus, - COALESCE(sol.qty, 0) as stockOutLineQty, - COALESCE(ill.inQty, 0) as inQty, - COALESCE(ill.outQty, 0) as outQty, - COALESCE(ill.holdQty, 0) as holdQty, - - CASE - WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' - WHEN sol.status = 'rejected' THEN 'rejected' - WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' - WHEN ill.status = 'unavailable' THEN 'status_unavailable' - ELSE 'available' - END as lotAvailability, - - CASE - WHEN sol.status = 'completed' THEN 'completed' - WHEN sol.status = 'rejected' THEN 'rejected' - WHEN sol.status = 'created' THEN 'pending' - ELSE 'pending' - END as processingStatus - - FROM fpsmsdb.pick_order po - JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false - JOIN fpsmsdb.items i ON i.id = pol.itemId - LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId + // ✅ 查询该 pick order 的所有 lines 和 lots + val linesSql = """ + SELECT + po.id as pickOrderId, + po.code as pickOrderCode, + po.consoCode as pickOrderConsoCode, + DATE_FORMAT(po.targetDate, '%Y-%m-%d') as pickOrderTargetDate, + po.type as pickOrderType, + po.status as pickOrderStatus, + po.assignTo as pickOrderAssignTo, - -- ✅ 关键修改:使用 LEFT JOIN 来包含没有 lot 的 pick order lines - LEFT JOIN ( - SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId - FROM fpsmsdb.suggested_pick_lot spl - UNION - SELECT sol.pickOrderLineId, sol.inventoryLotLineId - FROM fpsmsdb.stock_out_line sol - WHERE sol.deleted = false - ) ll ON ll.pickOrderLineId = pol.id + pol.id as pickOrderLineId, + pol.qty as pickOrderLineRequiredQty, + pol.status as pickOrderLineStatus, - LEFT JOIN fpsmsdb.suggested_pick_lot spl - ON spl.pickOrderLineId = pol.id AND spl.suggestedLotLineId = ll.lotLineId - LEFT JOIN fpsmsdb.stock_out_line sol - ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ll.lotLineId AND sol.deleted = false - LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false - LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false - LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId + i.id as itemId, + i.code as itemCode, + i.name as itemName, + uc.code as uomCode, + uc.udfudesc as uomDesc, + uc.udfShortDesc as uomShortDesc, - WHERE po.id = :pickOrderId - AND po.deleted = false - ORDER BY - COALESCE(w.`order`, 999999) ASC, - pol.id ASC, - il.lotNo ASC - """.trimIndent() + ill.id as lotId, + il.lotNo, + DATE_FORMAT(il.expiryDate, '%Y-%m-%d') as expiryDate, + w.name as location, + COALESCE(uc.udfudesc, 'N/A') as stockUnit, + w.`order` as routerIndex, + w.code as routerRoute, + + CASE + WHEN sol.status = 'rejected' THEN NULL + ELSE (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) + END as availableQty, + + COALESCE(spl.qty, 0) as requiredQty, + COALESCE(sol.qty, 0) as actualPickQty, + spl.id as suggestedPickLotId, + ill.status as lotStatus, + sol.id as stockOutLineId, + sol.status as stockOutLineStatus, + COALESCE(sol.qty, 0) as stockOutLineQty, + COALESCE(ill.inQty, 0) as inQty, + COALESCE(ill.outQty, 0) as outQty, + COALESCE(ill.holdQty, 0) as holdQty, + + CASE + WHEN (il.expiryDate IS NOT NULL AND il.expiryDate < CURDATE()) THEN 'expired' + WHEN sol.status = 'rejected' THEN 'rejected' + WHEN (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0)) <= 0 THEN 'insufficient_stock' + WHEN ill.status = 'unavailable' THEN 'status_unavailable' + ELSE 'available' + END as lotAvailability, + + CASE + WHEN sol.status = 'completed' THEN 'completed' + WHEN sol.status = 'rejected' THEN 'rejected' + WHEN sol.status = 'created' THEN 'pending' + ELSE 'pending' + END as processingStatus + + FROM fpsmsdb.pick_order po + JOIN fpsmsdb.pick_order_line pol ON pol.poId = po.id AND pol.deleted = false + JOIN fpsmsdb.items i ON i.id = pol.itemId + LEFT JOIN fpsmsdb.uom_conversion uc ON uc.id = pol.uomId - val linesResults = jdbcDao.queryForList(linesSql, mapOf("pickOrderId" to pickOrderId)) - println("🔍 Pick order $pickOrderId has ${linesResults.size} line-lot records") + LEFT JOIN ( + SELECT spl.pickOrderLineId, spl.suggestedLotLineId AS lotLineId + FROM fpsmsdb.suggested_pick_lot spl + UNION + SELECT sol.pickOrderLineId, sol.inventoryLotLineId + FROM fpsmsdb.stock_out_line sol + WHERE sol.deleted = false + ) ll ON ll.pickOrderLineId = pol.id - // ✅ 按 pickOrderLineId 分组 - val lineGroups = linesResults.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() } + LEFT JOIN fpsmsdb.suggested_pick_lot spl + ON spl.pickOrderLineId = pol.id AND spl.suggestedLotLineId = ll.lotLineId + LEFT JOIN fpsmsdb.stock_out_line sol + ON sol.pickOrderLineId = pol.id AND sol.inventoryLotLineId = ll.lotLineId AND sol.deleted = false + LEFT JOIN fpsmsdb.inventory_lot_line ill ON ill.id = ll.lotLineId AND ill.deleted = false + LEFT JOIN fpsmsdb.inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = false + LEFT JOIN fpsmsdb.warehouse w ON w.id = ill.warehouseId - val pickOrderLines = lineGroups.map { (lineId, lineRows) -> - val firstLineRow = lineRows.firstOrNull() - - if (firstLineRow == null) { - null - } else { - // ✅ 构建 lots 列表(如果没有 lot,返回空数组) - val lots = if (lineRows.any { it["lotId"] != null }) { - lineRows.filter { it["lotId"] != null }.map { lotRow -> - mapOf( - "id" to lotRow["lotId"], - "lotNo" to lotRow["lotNo"], - "expiryDate" to lotRow["expiryDate"], - "location" to lotRow["location"], - "stockUnit" to lotRow["stockUnit"], - "availableQty" to lotRow["availableQty"], - "requiredQty" to lotRow["requiredQty"], - "actualPickQty" to lotRow["actualPickQty"], - "inQty" to lotRow["inQty"], - "outQty" to lotRow["outQty"], - "holdQty" to lotRow["holdQty"], - "lotStatus" to lotRow["lotStatus"], - "lotAvailability" to lotRow["lotAvailability"], - "processingStatus" to lotRow["processingStatus"], - "suggestedPickLotId" to lotRow["suggestedPickLotId"], - "stockOutLineId" to lotRow["stockOutLineId"], - "stockOutLineStatus" to lotRow["stockOutLineStatus"], - "stockOutLineQty" to lotRow["stockOutLineQty"], - "router" to mapOf( - "id" to null, - "index" to lotRow["routerIndex"], - "route" to lotRow["routerRoute"], - "area" to lotRow["routerRoute"], - "itemCode" to lotRow["itemId"], - "itemName" to lotRow["itemName"], - "uomId" to lotRow["uomCode"], - "noofCarton" to lotRow["requiredQty"] - ) + WHERE po.id = :pickOrderId + AND po.deleted = false + ORDER BY + COALESCE(w.`order`, 999999) ASC, + pol.id ASC, + il.lotNo ASC + """.trimIndent() + + val linesResults = jdbcDao.queryForList(linesSql, mapOf("pickOrderId" to pickOrderId)) + println("🔍 Pick order $pickOrderId has ${linesResults.size} line-lot records") + + // ✅ 按 pickOrderLineId 分组 + val lineGroups = linesResults.groupBy { (it["pickOrderLineId"] as? Number)?.toLong() } + + val pickOrderLines = lineGroups.map { (lineId, lineRows) -> + val firstLineRow = lineRows.firstOrNull() + + if (firstLineRow == null) { + null + } else { + // ✅ 构建 lots 列表 + val lots = if (lineRows.any { it["lotId"] != null }) { + lineRows.filter { it["lotId"] != null }.map { lotRow -> + mapOf( + "id" to lotRow["lotId"], + "lotNo" to lotRow["lotNo"], + "expiryDate" to lotRow["expiryDate"], + "location" to lotRow["location"], + "stockUnit" to lotRow["stockUnit"], + "availableQty" to lotRow["availableQty"], + "requiredQty" to lotRow["requiredQty"], + "actualPickQty" to lotRow["actualPickQty"], + "inQty" to lotRow["inQty"], + "outQty" to lotRow["outQty"], + "holdQty" to lotRow["holdQty"], + "lotStatus" to lotRow["lotStatus"], + "lotAvailability" to lotRow["lotAvailability"], + "processingStatus" to lotRow["processingStatus"], + "suggestedPickLotId" to lotRow["suggestedPickLotId"], + "stockOutLineId" to lotRow["stockOutLineId"], + "stockOutLineStatus" to lotRow["stockOutLineStatus"], + "stockOutLineQty" to lotRow["stockOutLineQty"], + "router" to mapOf( + "id" to null, + "index" to lotRow["routerIndex"], + "route" to lotRow["routerRoute"], + "area" to lotRow["routerRoute"], + "itemCode" to lotRow["itemId"], + "itemName" to lotRow["itemName"], + "uomId" to lotRow["uomCode"], + "noofCarton" to lotRow["requiredQty"] ) - } - } else { - emptyList() // ✅ 返回空数组而不是 null + ) } - - mapOf( - "id" to lineId, - "requiredQty" to firstLineRow["pickOrderLineRequiredQty"], - "status" to firstLineRow["pickOrderLineStatus"], - "item" to mapOf( - "id" to firstLineRow["itemId"], - "code" to firstLineRow["itemCode"], - "name" to firstLineRow["itemName"], - "uomCode" to firstLineRow["uomCode"], - "uomDesc" to firstLineRow["uomDesc"], - "uomShortDesc" to firstLineRow["uomShortDesc"], - ), - "lots" to lots // ✅ 即使是空数组也返回 - ) + } else { + emptyList() } - }.filterNotNull() - - mapOf( - "pickOrderId" to pickOrderId, - "pickOrderCode" to poInfo["pick_order_code"], - "doOrderId" to poInfo["do_order_id"], - "deliveryOrderCode" to poInfo["delivery_order_code"], - "consoCode" to poInfo["consoCode"], - "status" to poInfo["status"], - "targetDate" to poInfo["targetDate"], - "pickOrderLines" to pickOrderLines - ) - } - - // ✅ 构建 FG 信息 - val fgInfo = mapOf( - "doPickOrderId" to doPickOrderId, - "ticketNo" to doPickOrderInfo["ticket_no"], - "storeId" to doPickOrderInfo["store_id"], - "shopCode" to doPickOrderInfo["ShopCode"], - "shopName" to doPickOrderInfo["ShopName"], - "truckLanceCode" to doPickOrderInfo["TruckLanceCode"], - "departureTime" to doPickOrderInfo["truck_departure_time"] - ) + + mapOf( + "id" to lineId, + "requiredQty" to firstLineRow["pickOrderLineRequiredQty"], + "status" to firstLineRow["pickOrderLineStatus"], + "item" to mapOf( + "id" to firstLineRow["itemId"], + "code" to firstLineRow["itemCode"], + "name" to firstLineRow["itemName"], + "uomCode" to firstLineRow["uomCode"], + "uomDesc" to firstLineRow["uomDesc"], + "uomShortDesc" to firstLineRow["uomShortDesc"], + ), + "lots" to lots + ) + } + }.filterNotNull() - return mapOf( - "fgInfo" to fgInfo, - "pickOrders" to pickOrders + // 记录该 pick order 的行数 + lineCountsPerPickOrder.add(pickOrderLines.size) + allPickOrderLines.addAll(pickOrderLines) + } + // 合并到总列表 + allPickOrderLines.sortWith(compareBy( + { line -> + val lots = line["lots"] as? List> + val firstLot = lots?.firstOrNull() + val router = firstLot?.get("router") as? Map + (router?.get("index") as? Number)?.toInt() ?: 999999 + } + )) + + + // ✅ 构建 FG 信息 + val fgInfo = mapOf( + "doPickOrderId" to doPickOrderId, + "ticketNo" to doPickOrderInfo["ticket_no"], + "storeId" to doPickOrderInfo["store_id"], + "shopCode" to doPickOrderInfo["ShopCode"], + "shopName" to doPickOrderInfo["ShopName"], + "truckLanceCode" to doPickOrderInfo["TruckLanceCode"], + "departureTime" to doPickOrderInfo["truck_departure_time"] + ) + + // ✅ 构建合并后的 pick order 对象 + val mergedPickOrder = if (pickOrdersInfo.isNotEmpty()) { + val firstPickOrderInfo = pickOrdersInfo.first() + mapOf( + "pickOrderIds" to pickOrderIds, + "pickOrderCodes" to pickOrderCodes, + "doOrderIds" to doOrderIds, + "deliveryOrderCodes" to deliveryOrderCodes, + "lineCountsPerPickOrder" to lineCountsPerPickOrder, + "consoCode" to firstPickOrderInfo["consoCode"], + "status" to firstPickOrderInfo["status"], + "targetDate" to firstPickOrderInfo["targetDate"], + "pickOrderLines" to allPickOrderLines ) + } else { + null } + + return mapOf( + "fgInfo" to fgInfo, + "pickOrders" to listOfNotNull(mergedPickOrder) + ) +} // Fix the type issues in the getPickOrdersByDateAndStore method open fun getPickOrdersByDateAndStore(storeId: String): Map { println("=== Debug: getPickOrdersByDateAndStore ===")