| @@ -474,7 +474,7 @@ open class JoPickOrderService( | |||||
| expiryDate = il.expiryDate?.let { | expiryDate = il.expiryDate?.let { | ||||
| "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" | "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" | ||||
| }, | }, | ||||
| location = warehouse?.name, | |||||
| location = warehouse?.code, | |||||
| availableQty = availableQty?.toDouble(), | availableQty = availableQty?.toDouble(), | ||||
| requiredQty = spl.qty?.toDouble() ?: 0.0, | requiredQty = spl.qty?.toDouble() ?: 0.0, | ||||
| actualPickQty = sol?.qty ?: 0.0, | actualPickQty = sol?.qty ?: 0.0, | ||||
| @@ -701,7 +701,7 @@ open class JoPickOrderService( | |||||
| "expiryDate" to il.expiryDate?.let { | "expiryDate" to il.expiryDate?.let { | ||||
| "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}" | "${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}" | ||||
| }, | }, | ||||
| "location" to warehouse?.name, | |||||
| "location" to warehouse?.code, | |||||
| "availableQty" to availableQty?.toDouble(), | "availableQty" to availableQty?.toDouble(), | ||||
| "requiredQty" to (spl.qty?.toDouble() ?: 0.0), | "requiredQty" to (spl.qty?.toDouble() ?: 0.0), | ||||
| "actualPickQty" to (sol?.qty ?: 0.0), | "actualPickQty" to (sol?.qty ?: 0.0), | ||||
| @@ -1989,7 +1989,7 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLo | |||||
| expiryDate = il.expiryDate?.let { | expiryDate = il.expiryDate?.let { | ||||
| "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" | "${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}" | ||||
| }, | }, | ||||
| location = warehouse?.name, | |||||
| location = warehouse?.code, | |||||
| availableQty = availableQty?.toDouble(), | availableQty = availableQty?.toDouble(), | ||||
| requiredQty = spl.qty?.toDouble() ?: 0.0, | requiredQty = spl.qty?.toDouble() ?: 0.0, | ||||
| actualPickQty = sol?.qty ?: 0.0, | actualPickQty = sol?.qty ?: 0.0, | ||||
| @@ -138,8 +138,7 @@ val doPickOrderId: Long? = null, | |||||
| enum class HandleStatus { | enum class HandleStatus { | ||||
| pending, | pending, | ||||
| sort_and_repair, | |||||
| dispose | |||||
| completed | |||||
| } | } | ||||
| enum class IssueCategory { | enum class IssueCategory { | ||||
| lot_issue, // 正常 Issue Button 提交 | lot_issue, // 正常 Issue Button 提交 | ||||
| @@ -2,11 +2,13 @@ | |||||
| package com.ffii.fpsms.modules.pickOrder.entity | package com.ffii.fpsms.modules.pickOrder.entity | ||||
| import org.springframework.data.jpa.repository.JpaRepository | import org.springframework.data.jpa.repository.JpaRepository | ||||
| import org.springframework.data.jpa.repository.Modifying | |||||
| import org.springframework.data.jpa.repository.Query | import org.springframework.data.jpa.repository.Query | ||||
| import org.springframework.data.repository.query.Param | import org.springframework.data.repository.query.Param | ||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import com.ffii.fpsms.modules.pickOrder.enums.PickExecutionIssueEnum | import com.ffii.fpsms.modules.pickOrder.enums.PickExecutionIssueEnum | ||||
| import java.time.LocalDate | |||||
| import org.springframework.transaction.annotation.Transactional | |||||
| @Repository | @Repository | ||||
| interface PickExecutionIssueRepository : JpaRepository<PickExecutionIssue, Long> { | interface PickExecutionIssueRepository : JpaRepository<PickExecutionIssue, Long> { | ||||
| fun findByPickOrderIdAndDeletedFalse(pickOrderId: Long): List<PickExecutionIssue> | fun findByPickOrderIdAndDeletedFalse(pickOrderId: Long): List<PickExecutionIssue> | ||||
| @@ -76,6 +78,48 @@ fun findMaterialIssues(): List<PickExecutionIssue> | |||||
| fun getBadItemList(): List<PickExecutionIssue> | fun getBadItemList(): List<PickExecutionIssue> | ||||
| fun findByPickOrderId(pickOrderId: Long): List<PickExecutionIssue> | fun findByPickOrderId(pickOrderId: Long): List<PickExecutionIssue> | ||||
| @Query(""" | |||||
| SELECT p FROM PickExecutionIssue p | |||||
| WHERE p.issueCategory = :issueCategory | |||||
| AND p.missQty > 0 | |||||
| AND p.deleted = false | |||||
| ORDER BY p.created DESC | |||||
| """) | |||||
| fun findMissItemList(@Param("issueCategory") issueCategory: IssueCategory): List<PickExecutionIssue> | |||||
| @Query(""" | |||||
| SELECT p FROM PickExecutionIssue p | |||||
| WHERE p.issueCategory = :issueCategory | |||||
| AND p.badItemQty > 0 | |||||
| AND p.deleted = false | |||||
| ORDER BY p.created DESC | |||||
| """) | |||||
| fun findBadItemListByCategory(@Param("issueCategory") issueCategory: IssueCategory): List<PickExecutionIssue> | |||||
| @Query(""" | |||||
| SELECT p FROM PickExecutionIssue p | |||||
| WHERE p.issueCategory = :issueCategory | |||||
| AND p.badItemQty > 0 | |||||
| AND p.deleted = false | |||||
| ORDER BY p.created DESC | |||||
| """) | |||||
| fun findBadItemOnlyList(@Param("issueCategory") issueCategory: IssueCategory): List<PickExecutionIssue> | |||||
| @Modifying | |||||
| @Transactional | |||||
| @Query(""" | |||||
| UPDATE PickExecutionIssue p | |||||
| SET p.handleStatus = :handleStatus, | |||||
| p.handleDate = :handleDate, | |||||
| p.handledBy = :handledBy | |||||
| WHERE p.id = :issueId | |||||
| """) | |||||
| fun updateHandleStatus( | |||||
| @Param("issueId") issueId: Long, | |||||
| @Param("handleStatus") handleStatus: HandleStatus, | |||||
| @Param("handleDate") handleDate: LocalDate, | |||||
| @Param("handledBy") handledBy: Long? | |||||
| ): Int | |||||
| } | } | ||||
| @@ -32,6 +32,13 @@ import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus | |||||
| import com.ffii.fpsms.modules.stock.web.model.StockOutStatus | import com.ffii.fpsms.modules.stock.web.model.StockOutStatus | ||||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus | import com.ffii.fpsms.modules.pickOrder.enums.PickOrderLineStatus | ||||
| import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | ||||
| import com.ffii.fpsms.modules.pickOrder.web.models.* | |||||
| import com.ffii.fpsms.modules.common.SecurityUtils | |||||
| import com.ffii.fpsms.modules.stock.entity.StockOut | |||||
| import com.ffii.fpsms.modules.pickOrder.entity.PickOrderLine | |||||
| import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||||
| import com.ffii.fpsms.modules.common.CodeGenerator | |||||
| import com.ffii.fpsms.modules.pickOrder.web.models.SubmitIssueRequest | |||||
| @Service | @Service | ||||
| open class PickExecutionIssueService( | open class PickExecutionIssueService( | ||||
| private val pickExecutionIssueRepository: PickExecutionIssueRepository, | private val pickExecutionIssueRepository: PickExecutionIssueRepository, | ||||
| @@ -45,7 +52,8 @@ open class PickExecutionIssueService( | |||||
| private val pickOrderLineRepository: PickOrderLineRepository, | private val pickOrderLineRepository: PickOrderLineRepository, | ||||
| private val doPickOrderService: DoPickOrderService, | private val doPickOrderService: DoPickOrderService, | ||||
| private val joPickOrderRepository: JoPickOrderRepository, | private val joPickOrderRepository: JoPickOrderRepository, | ||||
| private val joPickOrderRecordRepository: JoPickOrderRecordRepository | |||||
| private val joPickOrderRecordRepository: JoPickOrderRecordRepository, | |||||
| private val itemsRepository: ItemsRepository | |||||
| ) { | ) { | ||||
| @@ -792,4 +800,567 @@ open fun createBatchReleaseIssue( | |||||
| return 0 | return 0 | ||||
| } | } | ||||
| } | } | ||||
| open fun getMissItemList(issueCategory: String = "lot_issue"): List<PickExecutionIssue> { | |||||
| val category = try { | |||||
| IssueCategory.valueOf(issueCategory) | |||||
| } catch (e: Exception) { | |||||
| IssueCategory.lot_issue | |||||
| } | |||||
| return pickExecutionIssueRepository.findMissItemList(category).filter { it.handleStatus != HandleStatus.completed } | |||||
| } | |||||
| open fun getBadItemList(issueCategory: String = "lot_issue"): List<PickExecutionIssue> { | |||||
| val category = try { | |||||
| IssueCategory.valueOf(issueCategory) | |||||
| } catch (e: Exception) { | |||||
| IssueCategory.lot_issue | |||||
| } | |||||
| return pickExecutionIssueRepository.findBadItemListByCategory(category).filter { it.handleStatus != HandleStatus.completed } | |||||
| } | |||||
| open fun getBadItemOnlyList(): List<PickExecutionIssue> { | |||||
| return pickExecutionIssueRepository.findBadItemOnlyList(IssueCategory.lot_issue) | |||||
| } | |||||
| open fun getExpiryItemList(): List<ExpiryItemResponse> { | |||||
| val today = LocalDate.now() | |||||
| val lotLines = inventoryLotLineRepository.findExpiredItems(today) | |||||
| return lotLines.map { lotLine -> | |||||
| val lot = lotLine.inventoryLot | |||||
| val item = lot?.item // Get item from inventoryLot | |||||
| ExpiryItemResponse( | |||||
| id = lotLine.id ?: 0L, | |||||
| itemId = item?.id ?: 0L, | |||||
| itemCode = item?.code ?: "", | |||||
| itemDescription = item?.name, | |||||
| lotId = lot?.id ?: 0L, | |||||
| lotNo = lot?.lotNo, | |||||
| storeLocation = lotLine.warehouse?.code, // Construct from warehouse | |||||
| expiryDate = lot?.expiryDate, | |||||
| remainingQty = (lotLine.inQty ?: BigDecimal.ZERO).subtract(lotLine.outQty ?: BigDecimal.ZERO) | |||||
| ) | |||||
| } | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun submitMissItem(request: SubmitIssueRequest): MessageResponse { | |||||
| try { | |||||
| val issue = pickExecutionIssueRepository.findById(request.issueId).orElse(null) | |||||
| ?: return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "NOT_FOUND", | |||||
| type = "stock_issue", | |||||
| message = "Issue not found", | |||||
| errorPosition = null | |||||
| ) | |||||
| if (issue.missQty <= BigDecimal.ZERO || issue.deleted) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "INVALID", | |||||
| type = "stock_issue", | |||||
| message = "Invalid issue or no miss quantity", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L | |||||
| val stockOut = createIssueStockOutHeader("MISS_ITEM", issue.issueRemark, handler) | |||||
| val pickOrderLine = issue.pickOrderLineId?.let { | |||||
| pickOrderLineRepository.findById(it).orElse(null) | |||||
| } | |||||
| val lotLine = issue.lotId?.let { | |||||
| inventoryLotLineRepository.findById(it).orElse(null) | |||||
| } | |||||
| val item = itemsRepository.findById(issue.itemId).orElse(null) | |||||
| ?: return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ITEM_NOT_FOUND", | |||||
| type = "stock_issue", | |||||
| message = "Item not found", | |||||
| errorPosition = null | |||||
| ) | |||||
| val stockOutLine = StockOutLine().apply { | |||||
| this.stockOut = stockOut | |||||
| this.inventoryLotLine = lotLine | |||||
| this.item = item | |||||
| this.qty = issue.missQty.toDouble() | |||||
| this.status = StockOutLineStatus.PENDING.status | |||||
| this.pickOrderLine = pickOrderLine | |||||
| } | |||||
| stockOutLineRepository.save(stockOutLine) | |||||
| if (issue.lotId != null) { | |||||
| updateLotLineAfterIssue(issue.lotId, issue.missQty) | |||||
| } | |||||
| markIssueHandled(issue, handler) | |||||
| return MessageResponse( | |||||
| id = stockOut.id, | |||||
| name = "Success", | |||||
| code = "SUCCESS", | |||||
| type = "stock_issue", | |||||
| message = "Successfully submitted miss item", | |||||
| errorPosition = null | |||||
| ) | |||||
| } catch (e: Exception) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ERROR", | |||||
| type = "stock_issue", | |||||
| message = "Failed to submit miss item: ${e.message}", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun submitBadItem(request: SubmitIssueRequest): MessageResponse { | |||||
| try { | |||||
| val issue = pickExecutionIssueRepository.findById(request.issueId).orElse(null) | |||||
| ?: return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "NOT_FOUND", | |||||
| type = "stock_issue", | |||||
| message = "Issue not found", | |||||
| errorPosition = null | |||||
| ) | |||||
| if (issue.badItemQty <= BigDecimal.ZERO || issue.deleted) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "INVALID", | |||||
| type = "stock_issue", | |||||
| message = "Invalid issue or no bad item quantity", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L | |||||
| val stockOut = createIssueStockOutHeader("BAD_ITEM", issue.issueRemark, handler) | |||||
| val pickOrderLine = issue.pickOrderLineId?.let { | |||||
| pickOrderLineRepository.findById(it).orElse(null) | |||||
| } | |||||
| val lotLine = issue.lotId?.let { | |||||
| inventoryLotLineRepository.findById(it).orElse(null) | |||||
| } | |||||
| val item = itemsRepository.findById(issue.itemId).orElse(null) | |||||
| ?: return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ITEM_NOT_FOUND", | |||||
| type = "stock_issue", | |||||
| message = "Item not found", | |||||
| errorPosition = null | |||||
| ) | |||||
| val stockOutLine = StockOutLine().apply { | |||||
| this.stockOut = stockOut | |||||
| this.inventoryLotLine = lotLine | |||||
| this.item = item | |||||
| this.qty = issue.badItemQty.toDouble() | |||||
| this.status = StockOutLineStatus.PENDING.status | |||||
| this.pickOrderLine = pickOrderLine | |||||
| } | |||||
| stockOutLineRepository.save(stockOutLine) | |||||
| if (issue.lotId != null) { | |||||
| updateLotLineAfterIssue(issue.lotId, issue.badItemQty) | |||||
| } | |||||
| markIssueHandled(issue, handler) | |||||
| return MessageResponse( | |||||
| id = stockOut.id, | |||||
| name = "Success", | |||||
| code = "SUCCESS", | |||||
| type = "stock_issue", | |||||
| message = "Successfully submitted bad item", | |||||
| errorPosition = null | |||||
| ) | |||||
| } catch (e: Exception) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ERROR", | |||||
| type = "stock_issue", | |||||
| message = "Failed to submit bad item: ${e.message}", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun submitExpiryItem(request: SubmitExpiryRequest): MessageResponse { | |||||
| try { | |||||
| val lotLine = inventoryLotLineRepository.findById(request.lotLineId).orElse(null) | |||||
| ?: return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "NOT_FOUND", | |||||
| type = "stock_issue", | |||||
| message = "Lot line not found", | |||||
| errorPosition = null | |||||
| ) | |||||
| val lot = lotLine.inventoryLot | |||||
| val today = LocalDate.now() | |||||
| if (lot?.expiryDate == null || !lot.expiryDate!!.isBefore(today)) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "NOT_EXPIRED", | |||||
| type = "stock_issue", | |||||
| message = "Item is not expired", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| val inQty = lotLine.inQty ?: BigDecimal.ZERO | |||||
| val outQty = lotLine.outQty ?: BigDecimal.ZERO | |||||
| val remainingQty = inQty.subtract(outQty) | |||||
| if (remainingQty <= BigDecimal.ZERO) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "NO_REMAINING", | |||||
| type = "stock_issue", | |||||
| message = "No remaining quantity", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L | |||||
| val stockOut = createIssueStockOutHeader("EXPIRY_ITEM", "Expired item removal", handler) | |||||
| val item = lot.item | |||||
| ?: return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ITEM_NOT_FOUND", | |||||
| type = "stock_issue", | |||||
| message = "Item not found", | |||||
| errorPosition = null | |||||
| ) | |||||
| val stockOutLine = StockOutLine().apply { | |||||
| this.stockOut = stockOut | |||||
| this.inventoryLotLine = lotLine | |||||
| this.item = item | |||||
| this.qty = remainingQty.toDouble() | |||||
| this.status = StockOutLineStatus.PENDING.status | |||||
| } | |||||
| stockOutLineRepository.save(stockOutLine) | |||||
| updateLotLineAfterIssue(lotLine.id ?: 0L, remainingQty) | |||||
| return MessageResponse( | |||||
| id = stockOut.id, | |||||
| name = "Success", | |||||
| code = "SUCCESS", | |||||
| type = "stock_issue", | |||||
| message = "Successfully submitted expiry item", | |||||
| errorPosition = null | |||||
| ) | |||||
| } catch (e: Exception) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ERROR", | |||||
| type = "stock_issue", | |||||
| message = "Failed to submit expiry item: ${e.message}", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| // Fix batchSubmitMissItem method (around line 835): | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun batchSubmitMissItem(request: BatchSubmitIssueRequest): MessageResponse { | |||||
| try { | |||||
| val issues = pickExecutionIssueRepository.findAllById(request.issueIds) | |||||
| .filter { it.missQty > BigDecimal.ZERO && !it.deleted } | |||||
| if (issues.isEmpty()) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "EMPTY", | |||||
| type = "stock_issue", | |||||
| message = "No valid issues to submit", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L | |||||
| // Create single StockOut header for all submissions | |||||
| val stockOut = createIssueStockOutHeader("MISS_ITEM", "Batch miss item submission", handler) | |||||
| issues.forEach { issue -> | |||||
| // Get pickOrderLine if available | |||||
| val pickOrderLine = issue.pickOrderLineId?.let { | |||||
| pickOrderLineRepository.findById(it).orElse(null) | |||||
| } | |||||
| // Get inventoryLotLine | |||||
| val lotLine = issue.lotId?.let { | |||||
| inventoryLotLineRepository.findById(it).orElse(null) | |||||
| } | |||||
| // Get item from issue (it has itemId) | |||||
| val item = itemsRepository.findById(issue.itemId).orElse(null) | |||||
| // Create StockOutLine | |||||
| val stockOutLine = StockOutLine().apply { | |||||
| this.stockOut = stockOut | |||||
| this.inventoryLotLine = lotLine | |||||
| this.item = item | |||||
| this.qty = issue.missQty.toDouble() | |||||
| this.status = StockOutLineStatus.PENDING.status | |||||
| this.pickOrderLine = pickOrderLine | |||||
| } | |||||
| stockOutLineRepository.save(stockOutLine) | |||||
| // Update InventoryLotLine | |||||
| if (issue.lotId != null) { | |||||
| updateLotLineAfterIssue(issue.lotId, issue.missQty) | |||||
| } | |||||
| // Mark issue as handled | |||||
| markIssueHandled(issue, handler) | |||||
| } | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Success", | |||||
| code = "SUCCESS", | |||||
| type = "stock_issue", | |||||
| message = "Successfully submitted ${issues.size} miss item(s)", | |||||
| errorPosition = null | |||||
| ) | |||||
| } catch (e: Exception) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ERROR", | |||||
| type = "stock_issue", | |||||
| message = "Failed to submit miss items: ${e.message}", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| // Fix batchSubmitBadItem method (around line 890) - same pattern: | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun batchSubmitBadItem(request: BatchSubmitIssueRequest): MessageResponse { | |||||
| try { | |||||
| val issues = pickExecutionIssueRepository.findAllById(request.issueIds) | |||||
| .filter { it.badItemQty > BigDecimal.ZERO && !it.deleted } | |||||
| if (issues.isEmpty()) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "EMPTY", | |||||
| type = "stock_issue", | |||||
| message = "No valid issues to submit", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L | |||||
| // Create single StockOut header for all submissions | |||||
| val stockOut = createIssueStockOutHeader("BAD_ITEM", "Batch bad item submission", handler) | |||||
| issues.forEach { issue -> | |||||
| // Get pickOrderLine if available | |||||
| val pickOrderLine = issue.pickOrderLineId?.let { | |||||
| pickOrderLineRepository.findById(it).orElse(null) | |||||
| } | |||||
| // Get inventoryLotLine | |||||
| val lotLine = issue.lotId?.let { | |||||
| inventoryLotLineRepository.findById(it).orElse(null) | |||||
| } | |||||
| // Get item from issue | |||||
| val item = itemsRepository.findById(issue.itemId).orElse(null) | |||||
| // Create StockOutLine | |||||
| val stockOutLine = StockOutLine().apply { | |||||
| this.stockOut = stockOut | |||||
| this.inventoryLotLine = lotLine | |||||
| this.item = item | |||||
| this.qty = issue.badItemQty.toDouble() | |||||
| this.status = StockOutLineStatus.PENDING.status | |||||
| this.pickOrderLine = pickOrderLine | |||||
| } | |||||
| stockOutLineRepository.save(stockOutLine) | |||||
| // Update InventoryLotLine | |||||
| if (issue.lotId != null) { | |||||
| updateLotLineAfterIssue(issue.lotId, issue.badItemQty) | |||||
| } | |||||
| // Mark issue as handled | |||||
| markIssueHandled(issue, handler) | |||||
| } | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Success", | |||||
| code = "SUCCESS", | |||||
| type = "stock_issue", | |||||
| message = "Successfully submitted ${issues.size} bad item(s)", | |||||
| errorPosition = null | |||||
| ) | |||||
| } catch (e: Exception) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ERROR", | |||||
| type = "stock_issue", | |||||
| message = "Failed to submit bad items: ${e.message}", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| // Fix batchSubmitExpiryItem method (around line 945): | |||||
| @Transactional(rollbackFor = [Exception::class]) | |||||
| open fun batchSubmitExpiryItem(request: BatchSubmitExpiryRequest): MessageResponse { | |||||
| try { | |||||
| val lotLines = inventoryLotLineRepository.findAllById(request.lotLineIds) | |||||
| .filter { | |||||
| val lot = it.inventoryLot | |||||
| val today = LocalDate.now() | |||||
| lot?.expiryDate != null && lot.expiryDate!!.isBefore(today) && | |||||
| (it.inQty ?: BigDecimal.ZERO) != (it.outQty ?: BigDecimal.ZERO) | |||||
| } | |||||
| if (lotLines.isEmpty()) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "EMPTY", | |||||
| type = "stock_issue", | |||||
| message = "No valid expiry items to submit", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| val handler = request.handler ?: SecurityUtils.getUser().orElse(null)?.id ?: 0L | |||||
| // Create single StockOut header for all submissions | |||||
| val stockOut = createIssueStockOutHeader("EXPIRY_ITEM", "Batch expiry item removal", handler) | |||||
| lotLines.forEach { lotLine -> | |||||
| val remainingQty = (lotLine.inQty ?: BigDecimal.ZERO).subtract(lotLine.outQty ?: BigDecimal.ZERO) | |||||
| // Get item from inventoryLot | |||||
| val item = lotLine.inventoryLot?.item | |||||
| // Create StockOutLine | |||||
| val stockOutLine = StockOutLine().apply { | |||||
| this.stockOut = stockOut | |||||
| this.inventoryLotLine = lotLine | |||||
| this.item = item | |||||
| this.qty = remainingQty.toDouble() | |||||
| this.status = StockOutLineStatus.PENDING.status | |||||
| } | |||||
| stockOutLineRepository.save(stockOutLine) | |||||
| // Update InventoryLotLine | |||||
| updateLotLineAfterIssue(lotLine.id ?: 0L, remainingQty) | |||||
| } | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Success", | |||||
| code = "SUCCESS", | |||||
| type = "stock_issue", | |||||
| message = "Successfully submitted ${lotLines.size} expiry item(s)", | |||||
| errorPosition = null | |||||
| ) | |||||
| } catch (e: Exception) { | |||||
| return MessageResponse( | |||||
| id = null, | |||||
| name = "Error", | |||||
| code = "ERROR", | |||||
| type = "stock_issue", | |||||
| message = "Failed to submit expiry items: ${e.message}", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| private fun markIssueHandled(issue: PickExecutionIssue, handler: Long?) { | |||||
| pickExecutionIssueRepository.updateHandleStatus( | |||||
| issueId = issue.id ?: 0L, | |||||
| handleStatus = HandleStatus.completed, | |||||
| handleDate = LocalDate.now(), | |||||
| handledBy = handler | |||||
| ) | |||||
| } | |||||
| private fun createIssueStockOutHeader(type: String, remarks: String?, handler: Long?): StockOut { | |||||
| val prefix = "ISSUE" | |||||
| val midfix = CodeGenerator.DEFAULT_MIDFIX | |||||
| val latestCode = stockOutRepository.findAll().mapNotNull { it.consoPickOrderCode } | |||||
| .filter { it.startsWith("$prefix-$midfix") } | |||||
| .maxOrNull() | |||||
| val consoCode = CodeGenerator.generateNo(prefix = prefix, midfix = midfix, latestCode = latestCode) | |||||
| val stockOut = StockOut().apply { | |||||
| this.type = type | |||||
| this.consoPickOrderCode = consoCode | |||||
| this.status = StockOutStatus.PENDING.status | |||||
| this.handler = handler | |||||
| this.remarks = remarks | |||||
| } | |||||
| return stockOutRepository.save(stockOut) | |||||
| } | |||||
| private fun updateLotLineAfterIssue(lotLineId: Long, qty: BigDecimal) { | |||||
| val lotLine = inventoryLotLineRepository.findById(lotLineId).orElse(null) | |||||
| if (lotLine != null) { | |||||
| val currentOutQty = lotLine.outQty ?: BigDecimal.ZERO | |||||
| val newOutQty = currentOutQty.add(qty) | |||||
| lotLine.outQty = newOutQty | |||||
| // If outQty != inQty, set status to AVAILABLE | |||||
| val inQty = lotLine.inQty ?: BigDecimal.ZERO | |||||
| if (newOutQty != inQty) { | |||||
| lotLine.status = InventoryLotLineStatus.AVAILABLE | |||||
| } | |||||
| lotLine.modified = LocalDateTime.now() | |||||
| lotLine.modifiedBy = "system" | |||||
| inventoryLotLineRepository.save(lotLine) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -934,7 +934,7 @@ open class PickOrderService( | |||||
| lotId = ill.id, | lotId = ill.id, | ||||
| lotNo = il?.lotNo, | lotNo = il?.lotNo, | ||||
| expiryDate = il?.expiryDate, | expiryDate = il?.expiryDate, | ||||
| location = w?.name, | |||||
| location = w?.code, | |||||
| stockUnit = ill.stockUom?.uom?.udfudesc ?: uomDesc, | stockUnit = ill.stockUom?.uom?.udfudesc ?: uomDesc, | ||||
| availableQty = availableQty, | availableQty = availableQty, | ||||
| requiredQty = spl?.qty ?: zero, | requiredQty = spl?.qty ?: zero, | ||||
| @@ -3771,7 +3771,7 @@ ORDER BY | |||||
| "id" to lotId, | "id" to lotId, | ||||
| "lotNo" to il?.lotNo, | "lotNo" to il?.lotNo, | ||||
| "expiryDate" to il?.expiryDate?.toString(), | "expiryDate" to il?.expiryDate?.toString(), | ||||
| "location" to w?.name, | |||||
| "location" to w?.code, | |||||
| "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), | "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), | ||||
| "availableQty" to availableQty, | "availableQty" to availableQty, | ||||
| "requiredQty" to (spl.qty ?: zero), | "requiredQty" to (spl.qty ?: zero), | ||||
| @@ -3857,7 +3857,7 @@ ORDER BY | |||||
| "id" to inventoryLotLineId, | "id" to inventoryLotLineId, | ||||
| "lotNo" to il?.lotNo, | "lotNo" to il?.lotNo, | ||||
| "expiryDate" to il?.expiryDate?.toString(), | "expiryDate" to il?.expiryDate?.toString(), | ||||
| "location" to w?.name, | |||||
| "location" to w?.code, | |||||
| "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), | "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), | ||||
| "availableQty" to availableQty, | "availableQty" to availableQty, | ||||
| "requiredQty" to (suggestion?.qty ?: zero), | "requiredQty" to (suggestion?.qty ?: zero), | ||||
| @@ -4957,7 +4957,7 @@ open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map<Stri | |||||
| "id" to ill.id, | "id" to ill.id, | ||||
| "lotNo" to il?.lotNo, | "lotNo" to il?.lotNo, | ||||
| "expiryDate" to il?.expiryDate, | "expiryDate" to il?.expiryDate, | ||||
| "location" to w?.name, | |||||
| "location" to w?.code, | |||||
| "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), | "stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"), | ||||
| "availableQty" to ((ill.inQty ?: BigDecimal.ZERO) | "availableQty" to ((ill.inQty ?: BigDecimal.ZERO) | ||||
| .minus(ill.outQty ?: BigDecimal.ZERO) | .minus(ill.outQty ?: BigDecimal.ZERO) | ||||
| @@ -5030,7 +5030,7 @@ open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map<Stri | |||||
| "qty" to numToBigDecimal(sol.qty as Number?), | "qty" to numToBigDecimal(sol.qty as Number?), | ||||
| "lotId" to ill?.id, | "lotId" to ill?.id, | ||||
| "lotNo" to (il?.lotNo ?: ""), | "lotNo" to (il?.lotNo ?: ""), | ||||
| "location" to (w?.name ?: ""), | |||||
| "location" to (w?.code ?: ""), | |||||
| "availableQty" to available, | "availableQty" to available, | ||||
| "noLot" to (ill == null) | "noLot" to (ill == null) | ||||
| ) | ) | ||||
| @@ -5219,7 +5219,7 @@ open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map<Stri | |||||
| id = illId, | id = illId, | ||||
| lotNo = il?.lotNo, | lotNo = il?.lotNo, | ||||
| expiryDate = il?.expiryDate, | expiryDate = il?.expiryDate, | ||||
| location = w?.name, | |||||
| location = w?.code, | |||||
| stockUnit = illEntity.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A", | stockUnit = illEntity.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A", | ||||
| availableQty = availableQty, | availableQty = availableQty, | ||||
| requiredQty = spl.qty, | requiredQty = spl.qty, | ||||
| @@ -5274,7 +5274,7 @@ open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map<Stri | |||||
| qty = sol.qty?.let { numToBigDecimal(it as? Number) }, | qty = sol.qty?.let { numToBigDecimal(it as? Number) }, | ||||
| lotId = ill?.id, | lotId = ill?.id, | ||||
| lotNo = il?.lotNo ?: "", | lotNo = il?.lotNo ?: "", | ||||
| location = w?.name ?: "", | |||||
| location = w?.code ?: "", | |||||
| availableQty = available, | availableQty = available, | ||||
| noLot = (ill == null) | noLot = (ill == null) | ||||
| ) | ) | ||||
| @@ -7,6 +7,7 @@ import com.ffii.fpsms.modules.pickOrder.enums.PickExecutionIssueEnum | |||||
| import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService // 修复导入路径 | import com.ffii.fpsms.modules.pickOrder.service.PickExecutionIssueService // 修复导入路径 | ||||
| import com.ffii.fpsms.modules.stock.web.model.PickExecutionIssueRequest | import com.ffii.fpsms.modules.stock.web.model.PickExecutionIssueRequest | ||||
| import org.springframework.web.bind.annotation.* | import org.springframework.web.bind.annotation.* | ||||
| import com.ffii.fpsms.modules.pickOrder.web.models.* | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/pickExecution") | @RequestMapping("/pickExecution") | ||||
| @@ -43,4 +44,53 @@ class PickExecutionIssueController( | |||||
| return pickExecutionIssueService.getBadItemList(status) | return pickExecutionIssueService.getBadItemList(status) | ||||
| } | } | ||||
| } | |||||
| @GetMapping("/issues/missItem") | |||||
| fun getMissItemIssues( | |||||
| @RequestParam(required = false, defaultValue = "lot_issue") issueCategory: String | |||||
| ): List<PickExecutionIssue> { | |||||
| return pickExecutionIssueService.getMissItemList(issueCategory) | |||||
| } | |||||
| @GetMapping("/issues/badItem") | |||||
| fun getBadItemIssues( | |||||
| @RequestParam(required = false, defaultValue = "lot_issue") issueCategory: String | |||||
| ): List<PickExecutionIssue> { | |||||
| return pickExecutionIssueService.getBadItemList(issueCategory) | |||||
| } | |||||
| @GetMapping("/issues/expiryItem") | |||||
| fun getExpiryItemIssues(): List<ExpiryItemResponse> { | |||||
| return pickExecutionIssueService.getExpiryItemList() | |||||
| } | |||||
| @PostMapping("/submitMissItem") | |||||
| fun submitMissItem(@RequestBody request: SubmitIssueRequest): MessageResponse { | |||||
| return pickExecutionIssueService.submitMissItem(request) | |||||
| } | |||||
| @PostMapping("/batchSubmitMissItem") | |||||
| fun batchSubmitMissItem(@RequestBody request: BatchSubmitIssueRequest): MessageResponse { | |||||
| return pickExecutionIssueService.batchSubmitMissItem(request) | |||||
| } | |||||
| @PostMapping("/submitBadItem") | |||||
| fun submitBadItem(@RequestBody request: SubmitIssueRequest): MessageResponse { | |||||
| return pickExecutionIssueService.submitBadItem(request) | |||||
| } | |||||
| @PostMapping("/batchSubmitBadItem") | |||||
| fun batchSubmitBadItem(@RequestBody request: BatchSubmitIssueRequest): MessageResponse { | |||||
| return pickExecutionIssueService.batchSubmitBadItem(request) | |||||
| } | |||||
| @PostMapping("/submitExpiryItem") | |||||
| fun submitExpiryItem(@RequestBody request: SubmitExpiryRequest): MessageResponse { | |||||
| return pickExecutionIssueService.submitExpiryItem(request) | |||||
| } | |||||
| @PostMapping("/batchSubmitExpiryItem") | |||||
| fun batchSubmitExpiryItem(@RequestBody request: BatchSubmitExpiryRequest): MessageResponse { | |||||
| return pickExecutionIssueService.batchSubmitExpiryItem(request) | |||||
| } | |||||
| } | |||||
| @@ -3,6 +3,9 @@ package com.ffii.fpsms.modules.stock.web.model | |||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import java.time.LocalDateTime | |||||
| data class PickExecutionIssueRequest( | data class PickExecutionIssueRequest( | ||||
| val pickOrderId: Long, | val pickOrderId: Long, | ||||
| @@ -25,4 +28,35 @@ data class PickExecutionIssueRequest( | |||||
| val issueRemark: String? = null, | val issueRemark: String? = null, | ||||
| val pickerName: String? = null, | val pickerName: String? = null, | ||||
| val handledBy: Long? = null | val handledBy: Long? = null | ||||
| ) | |||||
| data class SubmitIssueRequest( | |||||
| val issueId: Long, | |||||
| val handler: Long? = null, | |||||
| ) | |||||
| data class BatchSubmitIssueRequest( | |||||
| val issueIds: List<Long>, | |||||
| val handler: Long? = null, | |||||
| ) | |||||
| data class SubmitExpiryRequest( | |||||
| val lotLineId: Long, | |||||
| val handler: Long? = null, | |||||
| ) | |||||
| data class BatchSubmitExpiryRequest( | |||||
| val lotLineIds: List<Long>, | |||||
| val handler: Long? = null, | |||||
| ) | |||||
| data class ExpiryItemResponse( | |||||
| val id: Long, // InventoryLotLine ID | |||||
| val itemId: Long, | |||||
| val itemCode: String, | |||||
| val itemDescription: String?, | |||||
| val lotId: Long, // InventoryLot ID | |||||
| val lotNo: String?, | |||||
| val storeLocation: String?, | |||||
| val expiryDate: LocalDate?, | |||||
| val remainingQty: BigDecimal, | |||||
| ) | ) | ||||
| @@ -0,0 +1,34 @@ | |||||
| package com.ffii.fpsms.modules.pickOrder.web.models | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDate | |||||
| data class SubmitIssueRequest( | |||||
| val issueId: Long, | |||||
| val handler: Long? = null, | |||||
| ) | |||||
| data class BatchSubmitIssueRequest( | |||||
| val issueIds: List<Long>, | |||||
| val handler: Long? = null, | |||||
| ) | |||||
| data class SubmitExpiryRequest( | |||||
| val lotLineId: Long, | |||||
| val handler: Long? = null, | |||||
| ) | |||||
| data class BatchSubmitExpiryRequest( | |||||
| val lotLineIds: List<Long>, | |||||
| val handler: Long? = null, | |||||
| ) | |||||
| data class ExpiryItemResponse( | |||||
| val id: Long, // InventoryLotLine ID | |||||
| val itemId: Long, | |||||
| val itemCode: String, | |||||
| val itemDescription: String?, | |||||
| val lotId: Long, // InventoryLot ID | |||||
| val lotNo: String?, | |||||
| val storeLocation: String?, | |||||
| val expiryDate: LocalDate?, | |||||
| val remainingQty: BigDecimal, | |||||
| ) | |||||
| @@ -1497,6 +1497,7 @@ val sufficientStockQty = bomMaterials | |||||
| productionDate = LocalDate.now(), | productionDate = LocalDate.now(), | ||||
| jobOrderId = jobOrder.id, | jobOrderId = jobOrder.id, | ||||
| acceptQty =jobOrder?.reqQty?:BigDecimal.ZERO , | acceptQty =jobOrder?.reqQty?:BigDecimal.ZERO , | ||||
| //acceptQty = null, | |||||
| expiryDate=null, | expiryDate=null, | ||||
| status="pending", | status="pending", | ||||
| ) | ) | ||||
| @@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository | |||||
| import java.io.Serializable | import java.io.Serializable | ||||
| import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus | ||||
| import org.springframework.data.repository.query.Param | import org.springframework.data.repository.query.Param | ||||
| import java.time.LocalDate | |||||
| @Repository | @Repository | ||||
| interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | ||||
| @@ -78,4 +78,13 @@ fun countDistinctItemsByWarehouseIds(@Param("warehouseIds") warehouseIds: List<L | |||||
| AND ill.deleted = false | AND ill.deleted = false | ||||
| """) | """) | ||||
| fun countAllByWarehouseIds(@Param("warehouseIds") warehouseIds: List<Long>): Long | fun countAllByWarehouseIds(@Param("warehouseIds") warehouseIds: List<Long>): Long | ||||
| @Query(""" | |||||
| SELECT ill FROM InventoryLotLine ill | |||||
| JOIN ill.inventoryLot il | |||||
| WHERE il.expiryDate < :today | |||||
| AND ill.inQty != ill.outQty | |||||
| AND ill.deleted = false | |||||
| ORDER BY il.expiryDate ASC | |||||
| """) | |||||
| fun findExpiredItems(@Param("today") today: LocalDate): List<InventoryLotLine> | |||||
| } | } | ||||
| @@ -22,6 +22,7 @@ interface StockInLineInfo { | |||||
| val purchaseOrderId: Long? | val purchaseOrderId: Long? | ||||
| @get:Value("#{target.jobOrder?.id}") | @get:Value("#{target.jobOrder?.id}") | ||||
| val jobOrderId: Long? | val jobOrderId: Long? | ||||
| val demandQty: BigDecimal? | val demandQty: BigDecimal? | ||||
| val acceptedQty: BigDecimal | val acceptedQty: BigDecimal | ||||
| @get:Value("#{target.purchaseOrderLine?.qty}") | @get:Value("#{target.purchaseOrderLine?.qty}") | ||||
| @@ -118,7 +118,7 @@ open class StockInLineService( | |||||
| // stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | // stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | ||||
| // var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | // var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | ||||
| } | } | ||||
| createStockLedgerForStockIn(stockInLine) | |||||
| val item = itemRepository.findById(request.itemId).orElseThrow() | val item = itemRepository.findById(request.itemId).orElseThrow() | ||||
| // If request contains valid POL | // If request contains valid POL | ||||
| if (pol != null) { | if (pol != null) { | ||||
| @@ -406,7 +406,7 @@ open class StockInLineService( | |||||
| } | } | ||||
| // TODO: check all status to prevent reverting progress due to multiple users access to the same po? | // TODO: check all status to prevent reverting progress due to multiple users access to the same po? | ||||
| // return list of stock in line, update data grid with the list | // return list of stock in line, update data grid with the list | ||||
| createStockLedgerForStockIn(stockInLine) | |||||
| stockInLine.apply { | stockInLine.apply { | ||||
| this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate | this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate | ||||
| this.productLotNo = request.productLotNo ?: this.productLotNo | this.productLotNo = request.productLotNo ?: this.productLotNo | ||||
| @@ -458,6 +458,7 @@ open class StockInLineService( | |||||
| } | } | ||||
| // this.inventoryLotLine = savedInventoryLotLine | // this.inventoryLotLine = savedInventoryLotLine | ||||
| } | } | ||||
| createStockLedgerForStockIn(stockInLine) | |||||
| // Update JO Status | // Update JO Status | ||||
| if (stockInLine.jobOrder != null) { //TODO Improve | if (stockInLine.jobOrder != null) { //TODO Improve | ||||
| val jo = stockInLine.jobOrder | val jo = stockInLine.jobOrder | ||||
| @@ -953,7 +953,7 @@ println("Keeping all suggestions (including rejected ones for display)") | |||||
| itemDescription = item.name, | itemDescription = item.name, | ||||
| lotId = inventoryLotLine?.id, | lotId = inventoryLotLine?.id, | ||||
| lotNo = inventoryLotLine?.inventoryLot?.lotNo, | lotNo = inventoryLotLine?.inventoryLot?.lotNo, | ||||
| storeLocation = inventoryLotLine?.warehouse?.name, | |||||
| storeLocation = inventoryLotLine?.warehouse?.code, | |||||
| requiredQty = pickOrderLine.qty, | requiredQty = pickOrderLine.qty, | ||||
| actualPickQty = rejectedStockOutLine.qty ?: BigDecimal.ZERO, // 直接使用,不需要 toBigDecimal() | actualPickQty = rejectedStockOutLine.qty ?: BigDecimal.ZERO, // 直接使用,不需要 toBigDecimal() | ||||
| missQty = (pickOrderLine.qty ?: BigDecimal.ZERO).minus(rejectedStockOutLine.qty ?: BigDecimal.ZERO), // 直接使用 | missQty = (pickOrderLine.qty ?: BigDecimal.ZERO).minus(rejectedStockOutLine.qty ?: BigDecimal.ZERO), // 直接使用 | ||||
| @@ -1,5 +1,5 @@ | |||||
| spring: | spring: | ||||
| datasource: | datasource: | ||||
| jdbc-url: jdbc:mysql://127.0.0.1:3306/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 | |||||
| jdbc-url: jdbc:mysql://127.0.0.1:3308/fpsmsdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 | |||||
| username: root | username: root | ||||
| password: secret | password: secret | ||||
| @@ -0,0 +1,5 @@ | |||||
| --liquibase formatted sql | |||||
| --changeset author:add_type_and_item_id_to_stock_ledger | |||||
| ALTER TABLE `fpsmsdb`.`pick_execution_issue` | |||||
| CHANGE COLUMN `handle_status` `handle_status` ENUM('pending', "completed") NULL DEFAULT 'pending' ; | |||||