|
|
|
@@ -31,6 +31,7 @@ import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus |
|
|
|
import com.ffii.fpsms.modules.master.entity.ItemUomRespository |
|
|
|
import com.ffii.fpsms.modules.master.entity.WarehouseRepository |
|
|
|
import com.ffii.fpsms.modules.master.service.ItemUomService |
|
|
|
//import com.ffii.fpsms.modules.master.service.BomOutputQtyService |
|
|
|
import com.ffii.fpsms.modules.master.service.PrinterService |
|
|
|
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder |
|
|
|
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine |
|
|
|
@@ -110,6 +111,7 @@ open class StockInLineService( |
|
|
|
@Value("\${scheduler.m18Grn.createEnabled:false}") private val m18GrnCreateEnabled: Boolean, |
|
|
|
/** Recent window for PO stock-in alerts (pending / receiving) in nav / list UI. */ |
|
|
|
@Value("\${fpsms.purchase-stock-in-alert.lookback-days:7}") private val purchaseStockInAlertLookbackDays: Int, |
|
|
|
// private val bomOutputQtyService: BomOutputQtyService, |
|
|
|
) : AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { |
|
|
|
|
|
|
|
private val logger = LoggerFactory.getLogger(StockInLineService::class.java) |
|
|
|
@@ -124,6 +126,63 @@ open class StockInLineService( |
|
|
|
return stockInLineRepository.findStockInLineInfoByIdAndStatusAndDeletedFalse(id = stockInLineId, status = StockInLineStatus.RECEIVED.status).orElseThrow() |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Soft-delete a PO stock-in line that has not been put away yet. |
|
|
|
* Also soft-deletes linked inventory lot / lot lines when no stock was received into inventory. |
|
|
|
*/ |
|
|
|
@Transactional |
|
|
|
open fun softDelete(id: Long): MessageResponse { |
|
|
|
val sil = stockInLineRepository.findById(id).orElseThrow { |
|
|
|
ResponseStatusException(HttpStatus.NOT_FOUND, "stockInLineId $id not found") |
|
|
|
} |
|
|
|
if (sil.deleted == true) { |
|
|
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Stock in line already deleted") |
|
|
|
} |
|
|
|
|
|
|
|
val status = (sil.status ?: "").lowercase() |
|
|
|
if (status == StockInLineStatus.COMPLETE.status || status == StockInLineStatus.PARTIALLY_COMPLETE.status) { |
|
|
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot delete: stock in line already put away") |
|
|
|
} |
|
|
|
|
|
|
|
val lotId = sil.inventoryLot?.id |
|
|
|
val putAwayQty = if (lotId != null) { |
|
|
|
inventoryLotLineRepository.findAllByInventoryLotId(lotId) |
|
|
|
.sumOf { it.inQty ?: BigDecimal.ZERO } |
|
|
|
} else { |
|
|
|
BigDecimal.ZERO |
|
|
|
} |
|
|
|
if (putAwayQty > BigDecimal.ZERO) { |
|
|
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot delete: stock in line already put away") |
|
|
|
} |
|
|
|
|
|
|
|
sil.deleted = true |
|
|
|
saveAndFlush(sil) |
|
|
|
|
|
|
|
sil.inventoryLot?.let { lot -> |
|
|
|
lot.deleted = true |
|
|
|
inventoryLotRepository.saveAndFlush(lot) |
|
|
|
inventoryLotLineRepository.findAllByInventoryLotId(lot.id!!).forEach { line -> |
|
|
|
line.deleted = true |
|
|
|
inventoryLotLineRepository.save(line) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
sil.purchaseOrderLine?.let { pol -> |
|
|
|
updatePurchaseOrderLineStatus(pol) |
|
|
|
pol.purchaseOrder?.let { updatePurchaseOrderStatus(it) } |
|
|
|
} |
|
|
|
|
|
|
|
return MessageResponse( |
|
|
|
id = sil.id, |
|
|
|
code = sil.itemNo, |
|
|
|
name = sil.item?.name, |
|
|
|
type = "stock in line deleted", |
|
|
|
message = "delete success", |
|
|
|
errorPosition = null, |
|
|
|
entity = null, |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
open fun purchaseStockInAlertSinceDays(overrideDays: Int?): LocalDateTime { |
|
|
|
val d = (overrideDays ?: purchaseStockInAlertLookbackDays).coerceIn(1, 90) |
|
|
|
return LocalDateTime.now().minusDays(d.toLong()) |
|
|
|
@@ -366,9 +425,8 @@ open class StockInLineService( |
|
|
|
} |
|
|
|
// Set demandQty based on source |
|
|
|
if (jo != null && jo?.bom != null) { |
|
|
|
// For job orders, demandQty comes from BOM's outputQty |
|
|
|
//this.demandQty = bomOutputQtyService.stockOutputQty(jo?.bom) |
|
|
|
this.demandQty = jo?.bom?.outputQty |
|
|
|
|
|
|
|
} else if (pol != null && item.id != null) { |
|
|
|
val m18UomId = pol.uomM18?.id |
|
|
|
val qtyM18 = pol.qtyM18 |
|
|
|
@@ -936,6 +994,7 @@ open class StockInLineService( |
|
|
|
// Set demandQty based on source |
|
|
|
if (this.jobOrder != null && this.jobOrder?.bom != null) { |
|
|
|
// For job orders, demandQty comes from BOM's outputQty |
|
|
|
//this.demandQty = this.jobOrder?.bom?.let { bomOutputQtyService.stockOutputQty(it) } ?: this.demandQty |
|
|
|
this.demandQty = this.jobOrder?.bom?.outputQty ?: this.demandQty |
|
|
|
} else if (this.purchaseOrderLine != null && this.item?.id != null) { |
|
|
|
val itemId = this.item!!.id!! |
|
|
|
@@ -1020,7 +1079,7 @@ open class StockInLineService( |
|
|
|
if (putAwayStockQty >= requiredStockQty) { |
|
|
|
val _tStatus = System.nanoTime() |
|
|
|
stockInLine.apply { |
|
|
|
val isWipJobOrder = stockInLine.jobOrder?.bom?.description == "WIP" |
|
|
|
val isWipJobOrder = stockInLine.jobOrder?.bom?.bomKind == "WIP" |
|
|
|
this.status = if (isWipJobOrder) { |
|
|
|
StockInLineStatus.COMPLETE.status |
|
|
|
} else { |
|
|
|
@@ -1315,8 +1374,8 @@ open class StockInLineService( |
|
|
|
val item = stockInLine.item ?: return |
|
|
|
|
|
|
|
val _tInv = System.nanoTime() |
|
|
|
val inventory = inventoryRepository.findFirstByItemIdAndDeletedIsFalseOrderByIdAsc(item.id!!) ?: return |
|
|
|
_logStep("inventoryRepository.findFirstByItemIdAndDeletedIsFalseOrderByIdAsc", _tInv) |
|
|
|
val inventory = itemUomService.findInventoryForItemBaseUom(item.id!!) ?: return |
|
|
|
_logStep("inventoryRepository.findInventoryForItemBaseUom", _tInv) |
|
|
|
|
|
|
|
// ✅ 修复:查询最新的 stock_ledger 记录,基于前一笔 balance 计算 |
|
|
|
val _tLatest = System.nanoTime() |
|
|
|
|