CANCERYS\kw093 3 dni temu
rodzic
commit
f216a3d4a4
14 zmienionych plików z 111 dodań i 32 usunięć
  1. +2
    -0
      src/main/java/com/ffii/fpsms/modules/common/SettingNames.java
  2. +30
    -0
      src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt
  3. +6
    -0
      src/main/java/com/ffii/fpsms/modules/common/scheduler/web/SchedulerController.kt
  4. +2
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt
  5. +9
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt
  6. +19
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  7. +5
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt
  8. +1
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  9. +22
    -29
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt
  10. +7
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt
  11. +2
    -1
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt
  12. +2
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt
  13. +2
    -0
      src/main/resources/application-prod.yml
  14. +2
    -0
      src/main/resources/application.yml

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/common/SettingNames.java Wyświetl plik

@@ -35,6 +35,8 @@ public abstract class SettingNames {

/** Backfill M18 AN document code (grn_code) via GET /root/api/read/an (default 1:20 AM daily) */
public static final String SCHEDULE_GRN_CODE_SYNC = "SCHEDULE.grn.grnCode.m18";
/** Mark expired inventory lot lines as unavailable (default 00:05 daily) */
public static final String SCHEDULE_INVENTORY_LOT_EXPIRY = "SCHEDULE.inventoryLot.expiry";

public static final String SCHEDULE_PROD_ROUGH = "SCHEDULE.prod.rough";



+ 30
- 0
src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt Wyświetl plik

@@ -12,6 +12,7 @@ import com.ffii.fpsms.m18.model.SyncResult
import com.ffii.fpsms.modules.common.SettingNames
import com.ffii.fpsms.modules.master.service.ProductionScheduleService
import com.ffii.fpsms.modules.stock.service.SearchCompletedDnService
import com.ffii.fpsms.modules.stock.service.InventoryLotLineService
import com.ffii.fpsms.modules.settings.service.SettingsService
import jakarta.annotation.PostConstruct
import org.springframework.beans.factory.annotation.Value
@@ -37,6 +38,7 @@ open class SchedulerService(
* missing `grn_code`. Example: 4 = from 4 days ago 00:00 to now.
*/
@Value("\${scheduler.grnCodeSync.syncOffsetDays:0}") val grnCodeSyncSyncOffsetDays: Int,
@Value("\${scheduler.inventoryLotExpiry.enabled:true}") val inventoryLotExpiryEnabled: Boolean,
val settingsService: SettingsService,
val taskScheduler: TaskScheduler,
val productionScheduleService: ProductionScheduleService,
@@ -46,6 +48,7 @@ open class SchedulerService(
val schedulerSyncLogRepository: SchedulerSyncLogRepository,
val searchCompletedDnService: SearchCompletedDnService,
val m18GrnCodeSyncService: M18GrnCodeSyncService,
val inventoryLotLineService: InventoryLotLineService,
) {
var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
@@ -64,6 +67,7 @@ open class SchedulerService(
var scheduledPostCompletedDnGrn: ScheduledFuture<*>? = null

var scheduledGrnCodeSync: ScheduledFuture<*>? = null
var scheduledInventoryLotExpiry: ScheduledFuture<*>? = null

//@Volatile
//var scheduledRoughProd: ScheduledFuture<*>? = null
@@ -110,6 +114,7 @@ open class SchedulerService(
scheduleM18MasterData();
schedulePostCompletedDnGrn();
scheduleGrnCodeSync();
scheduleInventoryLotExpiry();
//scheduleRoughProd();
//scheduleDetailedProd();
}
@@ -193,6 +198,31 @@ open class SchedulerService(
)
}

/** Mark expired inventory lot lines as unavailable daily. Set scheduler.inventoryLotExpiry.enabled=false to disable. */
fun scheduleInventoryLotExpiry() {
if (!inventoryLotExpiryEnabled) {
scheduledInventoryLotExpiry?.cancel(false)
scheduledInventoryLotExpiry = null
logger.info("Inventory lot expiry scheduler disabled (scheduler.inventoryLotExpiry.enabled=false)")
return
}
scheduledInventoryLotExpiry = commonSchedule(
scheduledInventoryLotExpiry,
SettingNames.SCHEDULE_INVENTORY_LOT_EXPIRY,
"0 5 0 * * *",
{ markExpiredInventoryLotLinesAsUnavailable() }
)
}

open fun markExpiredInventoryLotLinesAsUnavailable() {
try {
val updatedRows = inventoryLotLineService.markExpiredLotLinesAsUnavailable()
logger.info("Scheduler - Inventory lot expiry update done, rows updated=$updatedRows")
} catch (e: Exception) {
logger.error("Scheduler - Inventory lot expiry update failed: ${e.message}", e)
}
}

// Function for schedule
// --------------------------- FP-MTMS --------------------------- //


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/common/scheduler/web/SchedulerController.kt Wyświetl plik

@@ -61,6 +61,12 @@ class SchedulerController(
return "M18 GRN code sync (read/an) triggered"
}

@GetMapping("/trigger/inventory-lot-expiry")
fun triggerInventoryLotExpiry(): String {
schedulerService.markExpiredInventoryLotLinesAsUnavailable()
return "Inventory lot expiry status update triggered"
}

@GetMapping("/trigger/post-completed-dn-grn")
fun triggerPostCompletedDnGrn(
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) receiptDate: LocalDate? = null,


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt Wyświetl plik

@@ -96,6 +96,8 @@ class DoPickOrderRecord {
var handlerName: String? = null
@Column(name = "release_type", length = 100)
var releaseType: String? = null
@Column(name = "cartonQty")
var cartonQty: Int? = null
// Default constructor for Hibernate
constructor()


+ 9
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt Wyświetl plik

@@ -1308,6 +1308,7 @@ open class DeliveryOrderService(
val driver = A4PrintDriverRegistry.getDriver(printer.brand)
driver.print(tempPdfFile, ip, port, printQty)
}
updateRecordCartonQty(request.doPickOrderId, request.numOfCarton)
} finally {
//tempPdfFile.delete()
}
@@ -1406,6 +1407,7 @@ open class DeliveryOrderService(
)
}
}
updateRecordCartonQty(request.doPickOrderId, request.numOfCarton)

println("Test PDF saved to: ${tempPdfFile.absolutePath}")

@@ -1415,6 +1417,13 @@ open class DeliveryOrderService(

}

private fun updateRecordCartonQty(doPickOrderRecordId: Long, cartonQty: Int) {
if (cartonQty <= 0) return
val record = doPickOrderRecordRepository.findById(doPickOrderRecordId).orElse(null) ?: return
record.cartonQty = cartonQty
doPickOrderRecordRepository.save(record)
}

@Transactional(rollbackFor = [Exception::class])
open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDoResult {
println(" DEBUG: Starting releaseDeliveryOrderWithoutTicket for DO ID: ${request.id}")


+ 19
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt Wyświetl plik

@@ -5,8 +5,10 @@ import com.ffii.fpsms.modules.stock.entity.projection.CurrentInventoryItemInfo
import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
import java.io.Serializable
import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus
import org.springframework.data.repository.query.Param
@@ -145,4 +147,21 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long
fun findAllByStoreIdsAndHasStockInLine(
@Param("storeIds") storeIds: List<String>
): List<InventoryLotLine>

@Modifying
@Transactional
@Query(
value = """
UPDATE inventory_lot_line ill
INNER JOIN inventory_lot il ON il.id = ill.inventoryLotId
SET ill.status = 'unavailable'
WHERE il.deleted = 0
AND ill.deleted = 0
AND il.expiryDate IS NOT NULL
AND il.expiryDate < CURRENT_DATE
AND (ill.status IS NULL OR LOWER(ill.status) <> 'unavailable')
""",
nativeQuery = true
)
fun markExpiredLotLinesAsUnavailable(): Int
}

+ 5
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt Wyświetl plik

@@ -60,6 +60,11 @@ open class InventoryLotLineService(
@Lazy
private val jobOrderService: JobOrderService
) {
@Transactional
open fun markExpiredLotLinesAsUnavailable(): Int {
return inventoryLotLineRepository.markExpiredLotLinesAsUnavailable()
}

open fun findById(id: Long): Optional<InventoryLotLine> {
return inventoryLotLineRepository.findById(id)
}


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt Wyświetl plik

@@ -1162,7 +1162,7 @@ open class StockInLineService(
this.type = stockInLine.type
this.itemId = item.id
this.itemCode = item.code
this.uomId = inventory.uom?.id ?: itemUomService.findStockUnitByItemId(item.id!!)?.uom?.id
this.uomId = itemUomService.findStockUnitByItemId(item.id!!)?.uom?.id ?: inventory.uom?.id
this.date = LocalDate.now()
}



+ 22
- 29
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt Wyświetl plik

@@ -656,31 +656,22 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long {
val statusLower = request.status.trim().lowercase()
val isPickEnd = statusLower == "completed" || statusLower == "partially_completed"
val deltaQty = request.qty
val skipLedgerWrite = request.skipLedgerWrite == true

// non jo/do: 手工拣货增量扣减时,補寫出库 ledger(幂等 + 跳过 jo/do,避免 double write)
if (isPickEnd && deltaQty != null && deltaQty > 0 && savedStockOutLine.id != null) {
// 手工拣货增量扣减时补写出库 ledger(DO/JO 也需要写)
// 批量提交流程会显式传 skipLedgerWrite=true,避免重复写入。
if (!skipLedgerWrite && isPickEnd && deltaQty != null && deltaQty > 0 && savedStockOutLine.id != null) {
val itemId = savedStockOutLine.item?.id
val po = savedStockOutLine.pickOrderLine?.pickOrder

if (itemId != null && po != null) {
val isJoOrDo = (po.jobOrder != null || po.deliveryOrder != null)
if (!isJoOrDo) {
val exists = jdbcDao.queryForInt(
"SELECT COUNT(*) FROM stock_ledger WHERE deleted = false AND stockOutLineId = :id",
mapOf("id" to savedStockOutLine.id)
) > 0

if (!exists) {
val invBefore = inventoryRepository.findByItemId(itemId).orElse(null)
val onHandBefore = invBefore?.onHandQty?.toDouble() ?: 0.0

createStockLedgerForPickDelta(
stockOutLineId = savedStockOutLine.id!!,
deltaQty = BigDecimal(deltaQty.toString()),
onHandQtyBeforeUpdate = onHandBefore
)
}
}

if (itemId != null) {
val invBefore = inventoryRepository.findByItemId(itemId).orElse(null)
val onHandBefore = invBefore?.onHandQty?.toDouble() ?: 0.0

createStockLedgerForPickDelta(
stockOutLineId = savedStockOutLine.id!!,
deltaQty = BigDecimal(deltaQty.toString()),
onHandQtyBeforeUpdate = onHandBefore
)
}
}
println("Updated StockOutLine: ${savedStockOutLine.id} with status: ${savedStockOutLine.status}")
@@ -1295,7 +1286,8 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
UpdateStockOutLineStatusRequest(
id = line.stockOutLineId,
status = newStatus, // 例如前端传来的 "completed"
qty = 0.0 // 不改变现有 qty
qty = 0.0, // 不改变现有 qty
skipLedgerWrite = true
)
)
@@ -1308,7 +1300,8 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
UpdateStockOutLineStatusRequest(
id = line.stockOutLineId,
status = newStatus,
qty = submitQty.toDouble()
qty = submitQty.toDouble(),
skipLedgerWrite = true
)
)

@@ -1509,8 +1502,8 @@ affectedConsoCodes.forEach { consoCode ->
this.type = stockOutLine.type
this.itemId = item.id
this.itemCode = item.code
this.uomId = inventory.uom?.id
?: itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(item.id!!)?.uom?.id
this.uomId = itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(item.id!!)?.uom?.id
?: inventory.uom?.id
this.date = LocalDate.now()
}

@@ -1607,8 +1600,8 @@ affectedConsoCodes.forEach { consoCode ->
this.type = "NOR"
this.itemId = item.id
this.itemCode = item.code
this.uomId = inventory.uom?.id
?: itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(item.id!!)?.uom?.id
this.uomId = itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(item.id!!)?.uom?.id
?: inventory.uom?.id
this.date = LocalDate.now()
}


+ 7
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt Wyświetl plik

@@ -40,6 +40,7 @@ import java.sql.ResultSet
import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository
import com.ffii.fpsms.modules.stock.entity.InventoryRepository
import com.ffii.fpsms.modules.stock.entity.StockLedger
import com.ffii.fpsms.modules.master.entity.UomConversionRepository
import java.time.LocalDate
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import java.math.RoundingMode
@@ -59,7 +60,8 @@ class StockTakeRecordService(
val stockInLineRepository: StockInLineRepository,
val inventoryLotRepository: InventoryLotRepository,
val stockLedgerRepository: StockLedgerRepository,
val inventoryRepository: InventoryRepository
val inventoryRepository: InventoryRepository,
val uomConversionRepository: UomConversionRepository

) {
private val logger: Logger = LoggerFactory.getLogger(StockTakeRecordService::class.java)
@@ -1706,6 +1708,10 @@ fun searchStockTransactions(request: SearchStockTransactionRequest): RecordsRes<
itemId = ledger.itemId ?: 0L,
itemCode = ledger.itemCode,
itemName = ledger.inventory?.item?.name,
uomId = ledger.uomId,
uomDesc = ledger.uomId?.let { uomId ->
uomConversionRepository.findByIdAndDeletedFalse(uomId)?.udfudesc
},
balanceQty = ledger.balance?.let { balance: Double -> BigDecimal(balance.toString()) },
qty = if (ledger.inQty != null && ledger.inQty!! > 0) {
BigDecimal(ledger.inQty.toString())


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt Wyświetl plik

@@ -59,7 +59,8 @@ data class UpdateStockOutLineStatusRequest(
val id: Long,
val status: String,
val qty: Double? = null,
val remarks: String? = null
val remarks: String? = null,
val skipLedgerWrite: Boolean? = false
)
data class UpdateStockOutLineStatusByQRCodeAndLotNoRequest(
val pickOrderLineId: Long,


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt Wyświetl plik

@@ -127,6 +127,8 @@ data class StockTransactionResponse(
val itemId: Long,
val itemCode: String?,
val itemName: String?,
val uomId: Long? = null,
val uomDesc: String? = null,
val balanceQty: BigDecimal? = null,
val qty: BigDecimal,
val type: String?,


+ 2
- 0
src/main/resources/application-prod.yml Wyświetl plik

@@ -27,6 +27,8 @@ scheduler:
grnCodeSync:
enabled: true
syncOffsetDays: 10 # from (today − 10) 00:00 to now, rows missing grn_code
inventoryLotExpiry:
enabled: true

m18:
config:


+ 2
- 0
src/main/resources/application.yml Wyświetl plik

@@ -19,6 +19,8 @@ scheduler:
enabled: false # set true in prod; backfills grn_code from M18 GET /root/api/read/an
# Lookback: created from start of (today − N days) through now, missing grn_code. E.g. 4 = last 4 days + today.
syncOffsetDays: 0
inventoryLotExpiry:
enabled: true

spring:
servlet:


Ładowanie…
Anuluj
Zapisz