浏览代码

update

master
CANCERYS\kw093 3 天前
父节点
当前提交
f216a3d4a4
共有 14 个文件被更改,包括 111 次插入32 次删除
  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 查看文件

@@ -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) */ /** 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"; 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"; public static final String SCHEDULE_PROD_ROUGH = "SCHEDULE.prod.rough";




+ 30
- 0
src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt 查看文件

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


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


//@Volatile //@Volatile
//var scheduledRoughProd: ScheduledFuture<*>? = null //var scheduledRoughProd: ScheduledFuture<*>? = null
@@ -110,6 +114,7 @@ open class SchedulerService(
scheduleM18MasterData(); scheduleM18MasterData();
schedulePostCompletedDnGrn(); schedulePostCompletedDnGrn();
scheduleGrnCodeSync(); scheduleGrnCodeSync();
scheduleInventoryLotExpiry();
//scheduleRoughProd(); //scheduleRoughProd();
//scheduleDetailedProd(); //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 // Function for schedule
// --------------------------- FP-MTMS --------------------------- // // --------------------------- FP-MTMS --------------------------- //


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/common/scheduler/web/SchedulerController.kt 查看文件

@@ -61,6 +61,12 @@ class SchedulerController(
return "M18 GRN code sync (read/an) triggered" 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") @GetMapping("/trigger/post-completed-dn-grn")
fun triggerPostCompletedDnGrn( fun triggerPostCompletedDnGrn(
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) receiptDate: LocalDate? = null, @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) receiptDate: LocalDate? = null,


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRecord.kt 查看文件

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


+ 9
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt 查看文件

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


println("Test PDF saved to: ${tempPdfFile.absolutePath}") 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]) @Transactional(rollbackFor = [Exception::class])
open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDoResult { open fun releaseDeliveryOrderWithoutTicket(request: ReleaseDoRequest): ReleaseDoResult {
println(" DEBUG: Starting releaseDeliveryOrderWithoutTicket for DO ID: ${request.id}") println(" DEBUG: Starting releaseDeliveryOrderWithoutTicket for DO ID: ${request.id}")


+ 19
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt 查看文件

@@ -5,8 +5,10 @@ import com.ffii.fpsms.modules.stock.entity.projection.CurrentInventoryItemInfo
import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Transactional
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
@@ -145,4 +147,21 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long
fun findAllByStoreIdsAndHasStockInLine( fun findAllByStoreIdsAndHasStockInLine(
@Param("storeIds") storeIds: List<String> @Param("storeIds") storeIds: List<String>
): List<InventoryLotLine> ): 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 查看文件

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

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


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt 查看文件

@@ -1162,7 +1162,7 @@ open class StockInLineService(
this.type = stockInLine.type this.type = stockInLine.type
this.itemId = item.id this.itemId = item.id
this.itemCode = item.code 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() this.date = LocalDate.now()
} }




+ 22
- 29
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt 查看文件

@@ -656,31 +656,22 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long {
val statusLower = request.status.trim().lowercase() val statusLower = request.status.trim().lowercase()
val isPickEnd = statusLower == "completed" || statusLower == "partially_completed" val isPickEnd = statusLower == "completed" || statusLower == "partially_completed"
val deltaQty = request.qty 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 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}") println("Updated StockOutLine: ${savedStockOutLine.id} with status: ${savedStockOutLine.status}")
@@ -1295,7 +1286,8 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
UpdateStockOutLineStatusRequest( UpdateStockOutLineStatusRequest(
id = line.stockOutLineId, id = line.stockOutLineId,
status = newStatus, // 例如前端传来的 "completed" status = newStatus, // 例如前端传来的 "completed"
qty = 0.0 // 不改变现有 qty
qty = 0.0, // 不改变现有 qty
skipLedgerWrite = true
) )
) )
@@ -1308,7 +1300,8 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse {
UpdateStockOutLineStatusRequest( UpdateStockOutLineStatusRequest(
id = line.stockOutLineId, id = line.stockOutLineId,
status = newStatus, status = newStatus,
qty = submitQty.toDouble()
qty = submitQty.toDouble(),
skipLedgerWrite = true
) )
) )


@@ -1509,8 +1502,8 @@ affectedConsoCodes.forEach { consoCode ->
this.type = stockOutLine.type this.type = stockOutLine.type
this.itemId = item.id this.itemId = item.id
this.itemCode = item.code 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() this.date = LocalDate.now()
} }


@@ -1607,8 +1600,8 @@ affectedConsoCodes.forEach { consoCode ->
this.type = "NOR" this.type = "NOR"
this.itemId = item.id this.itemId = item.id
this.itemCode = item.code 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() this.date = LocalDate.now()
} }


+ 7
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt 查看文件

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


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


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockOutRequest.kt 查看文件

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


+ 2
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/StockTakeRecordReponse.kt 查看文件

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


+ 2
- 0
src/main/resources/application-prod.yml 查看文件

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


m18: m18:
config: config:


+ 2
- 0
src/main/resources/application.yml 查看文件

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


spring: spring:
servlet: servlet:


正在加载...
取消
保存