Ver código fonte

update

master
CANCERYS\kw093 2 semanas atrás
pai
commit
5fd679eb88
15 arquivos alterados com 769 adições e 20 exclusões
  1. +3
    -3
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt
  2. +1
    -2
      src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt
  3. +45
    -1
      src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssueRepository.kt
  4. +572
    -1
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt
  5. +7
    -7
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  6. +51
    -1
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickExecutionIssueController.kt
  7. +34
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/PickExecutionIssueRequest.kt
  8. +34
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SubmitIssueRequest.kt
  9. +1
    -0
      src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt
  10. +10
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  11. +1
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt
  12. +3
    -2
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  13. +1
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt
  14. +1
    -1
      src/main/resources/application-db-local.yml
  15. +5
    -0
      src/main/resources/db/changelog/changes/20260115_01_Enson/01_alter_table.sql

+ 3
- 3
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt Ver arquivo

@@ -474,7 +474,7 @@ open class JoPickOrderService(
expiryDate = il.expiryDate?.let {
"${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
},
location = warehouse?.name,
location = warehouse?.code,
availableQty = availableQty?.toDouble(),
requiredQty = spl.qty?.toDouble() ?: 0.0,
actualPickQty = sol?.qty ?: 0.0,
@@ -701,7 +701,7 @@ open class JoPickOrderService(
"expiryDate" to il.expiryDate?.let {
"${it.year}-${"%02d".format(it.monthValue)}-${"%02d".format(it.dayOfMonth)}"
},
"location" to warehouse?.name,
"location" to warehouse?.code,
"availableQty" to availableQty?.toDouble(),
"requiredQty" to (spl.qty?.toDouble() ?: 0.0),
"actualPickQty" to (sol?.qty ?: 0.0),
@@ -1989,7 +1989,7 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLo
expiryDate = il.expiryDate?.let {
"${it.year}-${String.format("%02d", it.monthValue)}-${String.format("%02d", it.dayOfMonth)}"
},
location = warehouse?.name,
location = warehouse?.code,
availableQty = availableQty?.toDouble(),
requiredQty = spl.qty?.toDouble() ?: 0.0,
actualPickQty = sol?.qty ?: 0.0,


+ 1
- 2
src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssue.kt Ver arquivo

@@ -138,8 +138,7 @@ val doPickOrderId: Long? = null,

enum class HandleStatus {
pending,
sort_and_repair,
dispose
completed
}
enum class IssueCategory {
lot_issue, // 正常 Issue Button 提交


+ 45
- 1
src/main/java/com/ffii/fpsms/modules/pickOrder/entity/PickExecutionIssueRepository.kt Ver arquivo

@@ -2,11 +2,13 @@
package com.ffii.fpsms.modules.pickOrder.entity

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.repository.query.Param
import org.springframework.stereotype.Repository
import com.ffii.fpsms.modules.pickOrder.enums.PickExecutionIssueEnum

import java.time.LocalDate
import org.springframework.transaction.annotation.Transactional
@Repository
interface PickExecutionIssueRepository : JpaRepository<PickExecutionIssue, Long> {
fun findByPickOrderIdAndDeletedFalse(pickOrderId: Long): List<PickExecutionIssue>
@@ -76,6 +78,48 @@ fun findMaterialIssues(): List<PickExecutionIssue>
fun getBadItemList(): 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
}



+ 572
- 1
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt Ver arquivo

@@ -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.pickOrder.enums.PickOrderLineStatus
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
open class PickExecutionIssueService(
private val pickExecutionIssueRepository: PickExecutionIssueRepository,
@@ -45,7 +52,8 @@ open class PickExecutionIssueService(
private val pickOrderLineRepository: PickOrderLineRepository,
private val doPickOrderService: DoPickOrderService,
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
}
}

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)
}
}
}

+ 7
- 7
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt Ver arquivo

@@ -934,7 +934,7 @@ open class PickOrderService(
lotId = ill.id,
lotNo = il?.lotNo,
expiryDate = il?.expiryDate,
location = w?.name,
location = w?.code,
stockUnit = ill.stockUom?.uom?.udfudesc ?: uomDesc,
availableQty = availableQty,
requiredQty = spl?.qty ?: zero,
@@ -3771,7 +3771,7 @@ ORDER BY
"id" to lotId,
"lotNo" to il?.lotNo,
"expiryDate" to il?.expiryDate?.toString(),
"location" to w?.name,
"location" to w?.code,
"stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"),
"availableQty" to availableQty,
"requiredQty" to (spl.qty ?: zero),
@@ -3857,7 +3857,7 @@ ORDER BY
"id" to inventoryLotLineId,
"lotNo" to il?.lotNo,
"expiryDate" to il?.expiryDate?.toString(),
"location" to w?.name,
"location" to w?.code,
"stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"),
"availableQty" to availableQty,
"requiredQty" to (suggestion?.qty ?: zero),
@@ -4957,7 +4957,7 @@ open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map<Stri
"id" to ill.id,
"lotNo" to il?.lotNo,
"expiryDate" to il?.expiryDate,
"location" to w?.name,
"location" to w?.code,
"stockUnit" to (ill.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A"),
"availableQty" to ((ill.inQty ?: 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?),
"lotId" to ill?.id,
"lotNo" to (il?.lotNo ?: ""),
"location" to (w?.name ?: ""),
"location" to (w?.code ?: ""),
"availableQty" to available,
"noLot" to (ill == null)
)
@@ -5219,7 +5219,7 @@ open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map<Stri
id = illId,
lotNo = il?.lotNo,
expiryDate = il?.expiryDate,
location = w?.name,
location = w?.code,
stockUnit = illEntity.stockUom?.uom?.udfudesc ?: uom?.udfudesc ?: "N/A",
availableQty = availableQty,
requiredQty = spl.qty,
@@ -5274,7 +5274,7 @@ open fun getLotDetailsByDoPickOrderRecordId(doPickOrderRecordId: Long): Map<Stri
qty = sol.qty?.let { numToBigDecimal(it as? Number) },
lotId = ill?.id,
lotNo = il?.lotNo ?: "",
location = w?.name ?: "",
location = w?.code ?: "",
availableQty = available,
noLot = (ill == null)
)


+ 51
- 1
src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickExecutionIssueController.kt Ver arquivo

@@ -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.stock.web.model.PickExecutionIssueRequest
import org.springframework.web.bind.annotation.*
import com.ffii.fpsms.modules.pickOrder.web.models.*

@RestController
@RequestMapping("/pickExecution")
@@ -43,4 +44,53 @@ class PickExecutionIssueController(

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)
}
}


+ 34
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/PickExecutionIssueRequest.kt Ver arquivo

@@ -3,6 +3,9 @@ package com.ffii.fpsms.modules.stock.web.model

import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime



data class PickExecutionIssueRequest(
val pickOrderId: Long,
@@ -25,4 +28,35 @@ data class PickExecutionIssueRequest(
val issueRemark: String? = null,
val pickerName: String? = 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,
)

+ 34
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/models/SubmitIssueRequest.kt Ver arquivo

@@ -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,
)

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/productProcess/service/ProductProcessService.kt Ver arquivo

@@ -1497,6 +1497,7 @@ val sufficientStockQty = bomMaterials
productionDate = LocalDate.now(),
jobOrderId = jobOrder.id,
acceptQty =jobOrder?.reqQty?:BigDecimal.ZERO ,
//acceptQty = null,
expiryDate=null,
status="pending",
)


+ 10
- 1
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt Ver arquivo

@@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository
import java.io.Serializable
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import org.springframework.data.repository.query.Param
import java.time.LocalDate
@Repository
interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> {

@@ -78,4 +78,13 @@ fun countDistinctItemsByWarehouseIds(@Param("warehouseIds") warehouseIds: List<L
AND ill.deleted = false
""")
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>
}

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt Ver arquivo

@@ -22,6 +22,7 @@ interface StockInLineInfo {
val purchaseOrderId: Long?
@get:Value("#{target.jobOrder?.id}")
val jobOrderId: Long?

val demandQty: BigDecimal?
val acceptedQty: BigDecimal
@get:Value("#{target.purchaseOrderLine?.qty}")


+ 3
- 2
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt Ver arquivo

@@ -118,7 +118,7 @@ open class StockInLineService(
// stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn
// var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId)
}
createStockLedgerForStockIn(stockInLine)
val item = itemRepository.findById(request.itemId).orElseThrow()
// If request contains valid POL
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?
// return list of stock in line, update data grid with the list
createStockLedgerForStockIn(stockInLine)
stockInLine.apply {
this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate
this.productLotNo = request.productLotNo ?: this.productLotNo
@@ -458,6 +458,7 @@ open class StockInLineService(
}
// this.inventoryLotLine = savedInventoryLotLine
}
createStockLedgerForStockIn(stockInLine)
// Update JO Status
if (stockInLine.jobOrder != null) { //TODO Improve
val jo = stockInLine.jobOrder


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt Ver arquivo

@@ -953,7 +953,7 @@ println("Keeping all suggestions (including rejected ones for display)")
itemDescription = item.name,
lotId = inventoryLotLine?.id,
lotNo = inventoryLotLine?.inventoryLot?.lotNo,
storeLocation = inventoryLotLine?.warehouse?.name,
storeLocation = inventoryLotLine?.warehouse?.code,
requiredQty = pickOrderLine.qty,
actualPickQty = rejectedStockOutLine.qty ?: BigDecimal.ZERO, // 直接使用,不需要 toBigDecimal()
missQty = (pickOrderLine.qty ?: BigDecimal.ZERO).minus(rejectedStockOutLine.qty ?: BigDecimal.ZERO), // 直接使用


+ 1
- 1
src/main/resources/application-db-local.yml Ver arquivo

@@ -1,5 +1,5 @@
spring:
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
password: secret

+ 5
- 0
src/main/resources/db/changelog/changes/20260115_01_Enson/01_alter_table.sql Ver arquivo

@@ -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' ;

Carregando…
Cancelar
Salvar