| @@ -510,6 +510,7 @@ open class DeliveryOrderService( | |||
| this.item = pickOrderLine.item | |||
| this.status = StockOutLineStatus.PENDING.status | |||
| this.qty = 0.0 | |||
| this.type = "Nor" | |||
| } | |||
| stockOutLineRepository.save(line) | |||
| } | |||
| @@ -1178,6 +1179,7 @@ open class DeliveryOrderService( | |||
| StockOutLineStatus.PENDING.status // 有正常库存批次时使用 PENDING | |||
| } | |||
| this.qty = 0.0 | |||
| this.type = "Nor" | |||
| } | |||
| stockOutLineRepository.save(line) | |||
| } | |||
| @@ -96,7 +96,7 @@ class DoPickOrderController( | |||
| return doReleaseCoordinatorService.getBatchReleaseProgress(jobId) | |||
| } | |||
| @GetMapping("/ticket-release-table/{startDate}/{endDate}") | |||
| @GetMapping("/ticket-release-table/{startDate}&{endDate}") | |||
| fun getTicketReleaseTable( | |||
| @PathVariable startDate: LocalDate, | |||
| @PathVariable endDate: LocalDate | |||
| @@ -49,6 +49,7 @@ import com.ffii.fpsms.modules.jobOrder.web.model.LotDetailResponse | |||
| import com.ffii.fpsms.modules.stock.entity.enum.StockInLineStatus | |||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | |||
| import java.time.LocalDate | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.MaterialPickStatusItem | |||
| @Service | |||
| open class JoPickOrderService( | |||
| private val joPickOrderRepository: JoPickOrderRepository, | |||
| @@ -2249,5 +2250,98 @@ open fun deleteJoPickOrderJobOrderProductProcessPickOrder(jobOrderId: Long): Mes | |||
| errorPosition = null | |||
| ) | |||
| } | |||
| open fun getMaterialPickStatus(): List<MaterialPickStatusItem> { | |||
| val joPickOrders = joPickOrderRepository.findAll() | |||
| return joPickOrders | |||
| .filter { joPickOrder -> | |||
| val jobOrder = joPickOrder.jobOrderId?.let { | |||
| jobOrderRepository.findById(it).orElse(null) | |||
| } | |||
| jobOrder?.status != JobOrderStatus.COMPLETED | |||
| } | |||
| .map { joPickOrder -> | |||
| // Get related entities for additional data | |||
| val jobOrder = joPickOrder.jobOrderId?.let { | |||
| jobOrderRepository.findById(it).orElse(null) | |||
| } | |||
| val item = joPickOrder.itemId?.let { | |||
| itemsRepository.findById(it).orElse(null) | |||
| } | |||
| val pickOrder = joPickOrder.pickOrderId?.let { | |||
| pickOrderRepository.findById(it).orElse(null) | |||
| } | |||
| // Get stock out lines for this pick order and item to get start/end times | |||
| // First get all pick order lines for this pick order | |||
| val pickOrderLines = pickOrder?.let { po -> | |||
| pickOrderLineRepository.findByPickOrderId(po.id!!) | |||
| } ?: emptyList() | |||
| // Then get stock out lines for each pick order line that matches the item | |||
| val stockOutLines = pickOrderLines | |||
| .filter { it.item?.id == joPickOrder.itemId } | |||
| .flatMap { pol -> | |||
| stockOutLineRepository.findAllByPickOrderLineIdAndDeletedFalse(pol.id!!) | |||
| .mapNotNull { solInfo -> | |||
| // Convert StockOutLineInfo to StockOutLine entity to access startTime/endTime | |||
| // We need to fetch the actual entity | |||
| stockOutLineRepository.findById(solInfo.id).orElse(null) | |||
| } | |||
| } | |||
| // Get earliest startTime and latest endTime from stock out lines | |||
| val pickStartTime = stockOutLines | |||
| .mapNotNull { it.startTime } | |||
| .minOrNull() | |||
| val pickEndTime = stockOutLines | |||
| .mapNotNull { it.endTime } | |||
| .maxOrNull() | |||
| // Count items to pick from pick order lines | |||
| val numberOfItemsToPick = pickOrder?.let { po -> | |||
| pickOrderLineRepository.findByPickOrderId(po.id!!).size | |||
| } ?: 0 | |||
| // Count items with issues from pick execution issues | |||
| val numberOfItemsWithIssue = joPickOrder.id?.let { joPickOrderId -> | |||
| pickExecutionIssueRepository.findByPickOrderIdAndDeletedFalse(joPickOrder.pickOrderId ?: 0L) | |||
| .filter { it.joPickOrderId == joPickOrderId } | |||
| .size | |||
| } ?: 0 | |||
| // Get job order quantity and UOM - access via bom | |||
| val jobOrderQty = jobOrder?.reqQty?.toDouble() | |||
| val uom = jobOrder?.bom?.uom?.code | |||
| // Get item name | |||
| val itemName = item?.name | |||
| // Determine pick status - prioritize pickOrder.status over matchStatus | |||
| val pickStatus = when { | |||
| pickOrder?.status != null -> pickOrder.status!!.value | |||
| joPickOrder.matchStatus != null -> joPickOrder.matchStatus!!.value | |||
| else -> null | |||
| } | |||
| MaterialPickStatusItem( | |||
| id = joPickOrder.id ?: 0L, | |||
| pickOrderId = joPickOrder.pickOrderId, | |||
| pickOrderCode = joPickOrder.pickOrderCode, | |||
| jobOrderId = joPickOrder.jobOrderId, | |||
| jobOrderCode = joPickOrder.jobOrderCode, | |||
| itemId = joPickOrder.itemId, | |||
| itemCode = joPickOrder.itemCode, | |||
| itemName = itemName, | |||
| jobOrderQty = jobOrderQty, | |||
| uom = uom, | |||
| pickStartTime = pickStartTime, | |||
| pickEndTime = pickEndTime, | |||
| numberOfItemsToPick = numberOfItemsToPick, | |||
| numberOfItemsWithIssue = numberOfItemsWithIssue, | |||
| pickStatus = pickStatus | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| @@ -508,6 +508,7 @@ open class JobOrderService( | |||
| this.item = pickOrderLine.item | |||
| this.status = StockOutLineStatus.PENDING.status | |||
| this.qty = 0.0 | |||
| this.type = "Nor" | |||
| } | |||
| stockOutLineRepository.save(line) | |||
| } | |||
| @@ -40,6 +40,7 @@ import com.ffii.fpsms.modules.jobOrder.web.model.UpdateJoPickOrderHandledByReque | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportFGStockInLabelRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.MaterialPickStatusItem | |||
| import java.time.LocalDate | |||
| @RestController | |||
| @@ -320,4 +321,8 @@ fun checkJobOrderCreated(@Valid @RequestBody request: CheckJobOrderCreatedReques | |||
| fun updateJoReqQty(@Valid @RequestBody request: UpdateJoReqQtyRequest): MessageResponse { | |||
| return jobOrderService.updateJoReqQty(request) | |||
| } | |||
| @GetMapping("/material-pick-status") | |||
| fun getMaterialPickStatus(): List<MaterialPickStatusItem> { | |||
| return joPickOrderService.getMaterialPickStatus() | |||
| } | |||
| } | |||
| @@ -11,3 +11,20 @@ data class SearchJobOrderInfoRequest( | |||
| val pageNum: Int?, | |||
| val jobTypeName: String?, | |||
| ) | |||
| data class MaterialPickStatusItem( | |||
| val id: Long, | |||
| val pickOrderId: Long?, | |||
| val pickOrderCode: String?, | |||
| val jobOrderId: Long?, | |||
| val jobOrderCode: String?, | |||
| val itemId: Long?, | |||
| val itemCode: String?, | |||
| val itemName: String?, | |||
| val jobOrderQty: Double?, | |||
| val uom: String?, | |||
| val pickStartTime: LocalDateTime?, | |||
| val pickEndTime: LocalDateTime?, | |||
| val numberOfItemsToPick: Int, | |||
| val numberOfItemsWithIssue: Int, | |||
| val pickStatus: String? | |||
| ) | |||
| @@ -74,6 +74,7 @@ fun findMaterialIssues(): List<PickExecutionIssue> | |||
| ORDER BY p.created DESC | |||
| """) | |||
| fun getBadItemList(): List<PickExecutionIssue> | |||
| fun findByPickOrderId(pickOrderId: Long): List<PickExecutionIssue> | |||
| } | |||
| @@ -22,4 +22,5 @@ fun findAllPickOrdersByItemId(@Param("itemId") itemId: Long): List<PickOrder> | |||
| """) | |||
| fun findAllByPickOrderId(@Param("pickOrderId") pickOrderId: Long): List<PickOrderLine> | |||
| fun findAllByPickOrderIdAndDeletedFalse(pickOrderId: Long): List<PickOrderLine> | |||
| fun findByPickOrderId(pickOrderId: Long): List<PickOrderLine> | |||
| } | |||
| @@ -1147,6 +1147,7 @@ open class PickOrderService( | |||
| this.status = | |||
| com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status | |||
| this.qty = 0.0 | |||
| this.type = "Nor" | |||
| } | |||
| stockOutLIneRepository.save(line) | |||
| precreated++ | |||
| @@ -1945,6 +1946,7 @@ open class PickOrderService( | |||
| this.status = | |||
| com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status | |||
| this.qty = 0.0 | |||
| this.type = "Nor" | |||
| } | |||
| stockOutLIneRepository.save(line) | |||
| precreated++ | |||
| @@ -1587,5 +1587,45 @@ open class ProductProcessService( | |||
| message = "ProductProcess Line Created successfully with seqNo ${originalSeqNo + 1}", | |||
| errorPosition = null, | |||
| ) | |||
| }} | |||
| } | |||
| open fun getJobProcessStatus(): List<JobProcessStatusResponse> { | |||
| val productProcesses = productProcessRepository.findAllByDeletedIsFalse() | |||
| .filter { it.status != ProductProcessStatus.COMPLETED } | |||
| return productProcesses.map { process -> | |||
| val jobOrder = jobOrderRepository.findById(process.jobOrder?.id ?: 0L).orElse(null) | |||
| val lines = productProcessLineRepository.findByProductProcess_Id(process.id ?: 0L) | |||
| .sortedBy { it.seqNo } | |||
| val bom=bomRepository.findById(process.bom?.id ?: 0L).orElse(null) | |||
| val bomProcesses = bomProcessRepository.findByBomId(bom?.id ?: 0L).sortedBy { it.seqNo } | |||
| JobProcessStatusResponse( | |||
| jobOrderId = jobOrder?.id ?: 0L, | |||
| jobOrderCode = jobOrder?.code ?: "", | |||
| itemCode = process.item?.code ?: "", | |||
| itemName = process.item?.name ?: "", | |||
| planEndTime = jobOrder?.planEnd, | |||
| processes = (0 until 6).map { index -> | |||
| if (index < lines.size) { | |||
| val line = lines[index] | |||
| ProcessStatusInfo( | |||
| equipmentCode = bomProcesses[index].equipment?.code ?: "", | |||
| startTime = line.startTime, | |||
| endTime = line.endTime, | |||
| isRequired = true | |||
| ) | |||
| } else { | |||
| ProcessStatusInfo( | |||
| startTime = null, | |||
| endTime = null, | |||
| isRequired = false, | |||
| equipmentCode = null, | |||
| ) | |||
| } | |||
| } | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| @@ -225,4 +225,8 @@ class ProductProcessController( | |||
| fun updateProductProcessLineProcessingTimeSetupTimeChangeoverTime(@PathVariable lineId: Long, @RequestBody request: UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest): MessageResponse { | |||
| return productProcessService.UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTime(request) | |||
| } | |||
| @GetMapping("/Demo/JobProcessStatus") | |||
| fun getJobProcessStatus(): List<JobProcessStatusResponse> { | |||
| return productProcessService.getJobProcessStatus() | |||
| } | |||
| } | |||
| @@ -218,4 +218,19 @@ data class UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest( | |||
| val processingTime: Int?, | |||
| val setupTime: Int?, | |||
| val changeoverTime: Int? | |||
| ) | |||
| data class ProcessStatusInfo( | |||
| val equipmentCode: String?, | |||
| val startTime: LocalDateTime?, | |||
| val endTime: LocalDateTime?, | |||
| val isRequired: Boolean | |||
| ) | |||
| data class JobProcessStatusResponse( | |||
| val jobOrderId: Long, | |||
| val jobOrderCode: String, | |||
| val itemCode: String, | |||
| val itemName: String, | |||
| val planEndTime: LocalDateTime?, | |||
| val processes: List<ProcessStatusInfo> | |||
| ) | |||
| @@ -70,4 +70,12 @@ fun findAllByWarehouseCodeAndDeletedIsFalse(@Param("warehouseCode") warehouseCod | |||
| AND ill.deleted = false | |||
| """) | |||
| fun countDistinctItemsByWarehouseIds(@Param("warehouseIds") warehouseIds: List<Long>): Long | |||
| @Query(""" | |||
| SELECT COUNT(ill.id) | |||
| FROM InventoryLotLine ill | |||
| WHERE ill.warehouse.id IN :warehouseIds | |||
| AND ill.deleted = false | |||
| """) | |||
| fun countAllByWarehouseIds(@Param("warehouseIds") warehouseIds: List<Long>): Long | |||
| } | |||
| @@ -7,6 +7,9 @@ import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.util.Optional | |||
| import java.time.LocalDate | |||
| import org.springframework.data.repository.query.Param | |||
| @Repository | |||
| interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | |||
| fun findAllStockInLineInfoByStockInIdAndDeletedFalse(stockInId: Long): List<StockInLineInfo> | |||
| @@ -31,4 +34,25 @@ interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | |||
| AND sil.deleted = false | |||
| """) | |||
| fun findByReceiptDateAndDeletedFalse(date: LocalDate): List<StockInLine> | |||
| @Query(""" | |||
| SELECT sil FROM StockInLine sil | |||
| WHERE sil.deleted = false | |||
| AND (:type IS NULL OR sil.type IS NOT NULL) | |||
| AND (:startDate IS NULL OR DATE(sil.created) >= :startDate) | |||
| AND (:endDate IS NULL OR DATE(sil.created) <= :endDate) | |||
| AND (:itemCode IS NULL OR sil.item.code LIKE CONCAT('%', :itemCode, '%')) | |||
| AND (:itemName IS NULL OR sil.item.name LIKE CONCAT('%', :itemName, '%')) | |||
| AND (:type IS NULL OR sil.type = :type) | |||
| ORDER BY sil.created DESC | |||
| """) | |||
| fun searchStockInLines( | |||
| @Param("startDate") startDate: LocalDate?, | |||
| @Param("endDate") endDate: LocalDate?, | |||
| @Param("itemCode") itemCode: String?, | |||
| @Param("itemName") itemName: String?, | |||
| @Param("type") type: String? | |||
| ): List<StockInLine> | |||
| //AND sil.type IS NOT NULL | |||
| @Query("SELECT sil FROM StockInLine sil WHERE sil.item.id IN :itemIds AND sil.deleted = false") | |||
| fun findAllByItemIdInAndDeletedFalse(itemIds: List<Long>): List<StockInLine> | |||
| } | |||
| @@ -21,4 +21,5 @@ interface StockInRepository : AbstractRepository<StockIn, Long> { | |||
| select si.code from StockIn si where si.code like :prefix% and si.deleted = false order by si.code desc limit 1 | |||
| """) | |||
| fun findLatestCodeByPrefix(prefix: String): String? | |||
| } | |||
| @@ -7,13 +7,13 @@ import jakarta.persistence.JoinColumn | |||
| import jakarta.persistence.OneToOne | |||
| import jakarta.persistence.Table | |||
| import jakarta.validation.constraints.NotNull | |||
| import java.time.LocalDate | |||
| @Entity | |||
| @Table(name = "stock_ledger") | |||
| open class StockLedger: BaseEntity<Long>() { | |||
| // @OneToOne | |||
| // @JoinColumn(name = "stockInLineId") | |||
| // open var stockInLine: StockInLine? = null | |||
| @OneToOne | |||
| @JoinColumn(name = "stockInLineId") | |||
| open var stockInLine: StockInLine? = null | |||
| @OneToOne | |||
| @JoinColumn(name = "stockOutLineId") | |||
| @@ -32,4 +32,12 @@ open class StockLedger: BaseEntity<Long>() { | |||
| @Column(name = "balance") | |||
| open var balance: Double? = null | |||
| @Column(name = "type") | |||
| open var type: String? = null | |||
| @Column(name = "itemId") | |||
| open var itemId: Long? = null | |||
| @Column(name = "itemCode") | |||
| open var itemCode: String? = null | |||
| @Column(name = "date") | |||
| open var date: LocalDate? = null | |||
| } | |||
| @@ -1,8 +1,52 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.data.repository.query.Param | |||
| import org.springframework.stereotype.Repository | |||
| import java.time.LocalDate | |||
| @Repository | |||
| interface StockLedgerRepository: AbstractRepository<StockLedger, Long> { | |||
| @Query(""" | |||
| SELECT sl FROM StockLedger sl | |||
| LEFT JOIN sl.stockInLine sil | |||
| LEFT JOIN sl.stockOutLine sol | |||
| LEFT JOIN sl.inventory inv | |||
| LEFT JOIN inv.item i | |||
| WHERE sl.deleted = false | |||
| AND (:itemCode IS NULL OR sl.itemCode LIKE CONCAT('%', :itemCode, '%')) | |||
| AND (:itemName IS NULL OR i.name LIKE CONCAT('%', :itemName, '%')) | |||
| AND (:type IS NULL OR sl.type = :type) | |||
| AND (:startDate IS NULL OR DATE(sl.created) >= :startDate) | |||
| AND (:endDate IS NULL OR DATE(sl.created) <= :endDate) | |||
| ORDER BY sl.created ASC, sl.itemId | |||
| """) | |||
| fun findStockTransactions( | |||
| @Param("itemCode") itemCode: String?, | |||
| @Param("itemName") itemName: String?, | |||
| @Param("type") type: String?, | |||
| @Param("startDate") startDate: LocalDate?, | |||
| @Param("endDate") endDate: LocalDate? | |||
| ): List<StockLedger> | |||
| @Query(""" | |||
| SELECT COUNT(sl) FROM StockLedger sl | |||
| LEFT JOIN sl.inventory inv | |||
| LEFT JOIN inv.item i | |||
| WHERE sl.deleted = false | |||
| AND (:itemCode IS NULL OR sl.itemCode LIKE CONCAT('%', :itemCode, '%')) | |||
| AND (:itemName IS NULL OR i.name LIKE CONCAT('%', :itemName, '%')) | |||
| AND (:type IS NULL OR sl.type = :type) | |||
| AND (:startDate IS NULL OR DATE(sl.created) >= :startDate) | |||
| AND (:endDate IS NULL OR DATE(sl.created) <= :endDate) | |||
| """) | |||
| fun countStockTransactions( | |||
| @Param("itemCode") itemCode: String?, | |||
| @Param("itemName") itemName: String?, | |||
| @Param("type") type: String?, | |||
| @Param("startDate") startDate: LocalDate?, | |||
| @Param("endDate") endDate: LocalDate? | |||
| ): Long | |||
| } | |||
| @@ -34,5 +34,6 @@ open class StockOut: BaseEntity<Long>(){ | |||
| @Column(name = "remarks") | |||
| open var remarks: String? = null | |||
| @Column(name = "stockTakeId") | |||
| open var stockTakeId: Long? = null | |||
| } | |||
| @@ -4,6 +4,9 @@ import com.ffii.core.support.AbstractRepository | |||
| import com.ffii.fpsms.modules.stock.entity.projection.StockOutLineInfo | |||
| import com.ffii.fpsms.modules.stock.web.model.StockOutStatus | |||
| import org.springframework.stereotype.Repository | |||
| import org.springframework.data.repository.query.Param | |||
| import java.time.LocalDate | |||
| import org.springframework.data.jpa.repository.Query | |||
| @Repository | |||
| interface StockOutLIneRepository: AbstractRepository<StockOutLine, Long> { | |||
| @@ -21,5 +24,27 @@ interface StockOutLIneRepository: AbstractRepository<StockOutLine, Long> { | |||
| inventoryLotLineId: Long | |||
| ): List<StockOutLine> | |||
| fun existsByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(pickOrderLineId: Long, inventoryLotLineId: Long): Boolean | |||
| //fun findAllByPickOrderLineIdAndDeletedFalse(pickOrderLineId: Long): List<StockOutLine> | |||
| @Query(""" | |||
| SELECT sol FROM StockOutLine sol | |||
| WHERE sol.deleted = false | |||
| AND sol.type IS NOT NULL | |||
| AND (:startDate IS NULL OR DATE(sol.created) >= :startDate) | |||
| AND (:endDate IS NULL OR DATE(sol.created) <= :endDate) | |||
| AND (:itemCode IS NULL OR sol.item.code LIKE %:itemCode%) | |||
| AND (:itemName IS NULL OR sol.item.name LIKE %:itemName%) | |||
| AND (:type IS NULL OR sol.type = :type) | |||
| ORDER BY sol.created DESC | |||
| """) | |||
| fun searchStockOutLines( | |||
| @Param("startDate") startDate: LocalDate?, | |||
| @Param("endDate") endDate: LocalDate?, | |||
| @Param("itemCode") itemCode: String?, | |||
| @Param("itemName") itemName: String?, | |||
| @Param("type") type: String? | |||
| ): List<StockOutLine> | |||
| @Query("SELECT sol FROM StockOutLine sol WHERE sol.item.id = :itemId AND sol.deleted = false") | |||
| fun findAllByItemIdAndDeletedFalse(itemId: Long): List<StockOutLine> | |||
| @Query("SELECT sol FROM StockOutLine sol WHERE sol.item.id IN :itemIds AND sol.deleted = false") | |||
| fun findAllByItemIdInAndDeletedFalse(itemIds: List<Long>): List<StockOutLine> | |||
| } | |||
| @@ -48,4 +48,8 @@ open class StockOutLine: BaseEntity<Long>() { | |||
| @Column(name = "type") | |||
| open var type: String? = null | |||
| @Column(name = "startTime") | |||
| open var startTime: LocalDateTime? = null | |||
| @Column(name = "endTime") | |||
| open var endTime: LocalDateTime? = null | |||
| } | |||
| @@ -7,4 +7,5 @@ import java.util.Optional | |||
| @Repository | |||
| interface StockOutRepository: AbstractRepository<StockOut, Long> { | |||
| fun findByConsoPickOrderCode(consoPickOrderCode: String) : Optional<StockOut> | |||
| fun findByStockTakeIdAndDeletedFalse(stockTakeId: Long): StockOut? | |||
| } | |||
| @@ -5,20 +5,31 @@ import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.data.repository.query.Param | |||
| import java.time.LocalDate | |||
| @Repository | |||
| interface StockTakeRecordRepository : AbstractRepository<StockTakeRecord, Long> { | |||
| fun findAllByStockTakeIdAndDeletedIsFalse(stockTakeId: Long): List<StockTakeRecord>; | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeRecord?; | |||
| @Query(""" | |||
| SELECT w.stockTakeSection, str.stockTake.id, COUNT(str.id) as count | |||
| FROM StockTakeRecord str | |||
| INNER JOIN Warehouse w ON str.warehouse.id = w.id | |||
| WHERE str.deleted = false | |||
| AND w.deleted = false | |||
| AND w.stockTakeSection IS NOT NULL | |||
| AND w.stockTakeSection != '' | |||
| AND str.stockTake.id IS NOT NULL | |||
| GROUP BY w.stockTakeSection, str.stockTake.id | |||
| SELECT sl FROM StockLedger sl | |||
| LEFT JOIN sl.stockInLine sil | |||
| LEFT JOIN sl.stockOutLine sol | |||
| LEFT JOIN sl.inventory inv | |||
| LEFT JOIN inv.item i | |||
| WHERE sl.deleted = false | |||
| AND (:itemCode IS NULL OR sl.itemCode LIKE CONCAT('%', :itemCode, '%')) | |||
| AND (:itemName IS NULL OR i.name LIKE CONCAT('%', :itemName, '%')) | |||
| AND (:type IS NULL OR sl.type = :type) | |||
| AND (:startDate IS NULL OR DATE(sl.created) >= :startDate) | |||
| AND (:endDate IS NULL OR DATE(sl.created) <= :endDate) | |||
| ORDER BY COALESCE(sl.date, DATE(sl.created)) ASC, sl.created ASC, sl.itemId | |||
| """) | |||
| fun countStockTakeRecordsBySectionAndStockTakeId(): List<Array<Any>> | |||
| } | |||
| fun countStockTakeRecordsBySectionAndStockTakeId(): List<Array<Any>> | |||
| } | |||
| @@ -48,7 +48,9 @@ import java.io.File | |||
| import kotlin.jvm.optionals.getOrNull | |||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | |||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintQrCodeForDoRequest | |||
| import com.ffii.fpsms.modules.stock.service.InventoryLotLineService | |||
| import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryRepository | |||
| @Serializable | |||
| data class QrContent(val itemId: Long, val stockInLineId: Long) | |||
| @@ -72,7 +74,10 @@ open class StockInLineService( | |||
| private val itemUomRepository: ItemUomRespository, | |||
| private val printerService: PrinterService, | |||
| private val stockTakeLineRepository: StockTakeLineRepository, | |||
| private val inventoryLotLineService: InventoryLotLineService, | |||
| private val deliveryOrderRepository: DeliveryOrderRepository, | |||
| private val stockLedgerRepository: StockLedgerRepository, | |||
| private val inventoryRepository: InventoryRepository | |||
| ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | |||
| open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | |||
| @@ -113,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) { | |||
| @@ -153,6 +158,7 @@ open class StockInLineService( | |||
| // if (pol.qty!! < request.acceptedQty) { | |||
| // throw BadRequestException() | |||
| // } | |||
| stockInLine.apply { | |||
| this.item = item | |||
| itemNo = item.code | |||
| @@ -162,6 +168,7 @@ open class StockInLineService( | |||
| if (jo != null && jo?.bom != null) { | |||
| // For job orders, demandQty comes from BOM's outputQty | |||
| this.demandQty = jo?.bom?.outputQty | |||
| } else if (pol != null) { | |||
| // For purchase orders, demandQty comes from PurchaseOrderLine's qty | |||
| this.demandQty = pol.qty | |||
| @@ -169,6 +176,7 @@ open class StockInLineService( | |||
| dnNo = request.dnNo | |||
| receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now() | |||
| productLotNo = request.productLotNo | |||
| this.type = "Nor" | |||
| status = StockInLineStatus.PENDING.status | |||
| } | |||
| val savedSIL = saveAndFlush(stockInLine) | |||
| @@ -398,6 +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 | |||
| @@ -646,4 +655,80 @@ open class StockInLineService( | |||
| } | |||
| } | |||
| } | |||
| open fun newStockInCreate(request: NewStockInCreateRequest): MessageResponse { | |||
| val newLotNo = if (request.lotNo == null) assignLotNo() else request.lotNo | |||
| val inventoryLotLine = inventoryLotLineRepository.findByIdAndDeletedIsFalse( | |||
| request.inventoryLotLineId ?: throw IllegalArgumentException("Inventory lot ID not found") | |||
| ) ?: throw IllegalArgumentException("Inventory lot line not found") | |||
| val inventoryLot = inventoryLotRepository.findByIdAndDeletedFalse( | |||
| inventoryLotLine?.inventoryLot?.id ?: throw IllegalArgumentException("Inventory lot ID not found") | |||
| ) ?: throw IllegalArgumentException("Inventory lot not found") | |||
| val stockIn=StockIn().apply { | |||
| this.code=stockTake?.code | |||
| this.status=request.stockIntype | |||
| //this.stockTake=stockTake | |||
| } | |||
| stockInRepository.save(stockIn) | |||
| val stockInLine = StockInLine().apply { | |||
| //this.stockTakeLine=stockTakeLine | |||
| this.item=inventoryLot.item | |||
| this.itemNo=request.itemNo | |||
| this.stockIn = stockIn | |||
| this.demandQty=request.demandQty | |||
| this.acceptedQty=request.acceptedQty | |||
| this.expiryDate=inventoryLot.expiryDate | |||
| this.inventoryLot=inventoryLot | |||
| this.inventoryLotLine=inventoryLotLine | |||
| this.lotNo=newLotNo | |||
| this.status = "completed" | |||
| this.type = request.stockInLineType | |||
| } | |||
| stockInLineRepository.save(stockInLine) | |||
| val updateRequest = SaveInventoryLotLineRequest( | |||
| id = inventoryLotLine.id, | |||
| inventoryLotId = inventoryLotLine.inventoryLot?.id, | |||
| warehouseId = inventoryLotLine.warehouse?.id, | |||
| stockUomId = inventoryLotLine.stockUom?.id, | |||
| inQty = request.acceptedQty, | |||
| outQty = inventoryLotLine.outQty, | |||
| holdQty = inventoryLotLine.holdQty, | |||
| status = inventoryLotLine.status?.value, | |||
| remarks = inventoryLotLine.remarks | |||
| ) | |||
| inventoryLotLineService.saveInventoryLotLine(updateRequest) | |||
| return MessageResponse( | |||
| id = stockInLine.id, | |||
| code = null, | |||
| name = stockInLine.item?.name, | |||
| type = "success", | |||
| message = "Stock in line created successfully", | |||
| errorPosition = null | |||
| ) | |||
| } | |||
| @Transactional | |||
| private fun createStockLedgerForStockIn(stockInLine: StockInLine) { | |||
| val item = stockInLine.item ?: return | |||
| val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return | |||
| val inQty = stockInLine.acceptedQty?.toDouble() ?: 0.0 | |||
| // 直接使用 inventory.onHandQty 作为 balance(已经是更新后的值) | |||
| val newBalance = (inventory.onHandQty ?: BigDecimal.ZERO).toDouble() | |||
| val stockLedger = StockLedger().apply { | |||
| this.stockInLine = stockInLine | |||
| this.inventory = inventory | |||
| this.inQty = inQty | |||
| this.outQty = null | |||
| this.balance = newBalance | |||
| this.type = stockInLine.type | |||
| this.itemId = item.id | |||
| this.itemCode = item.code | |||
| this.date = LocalDate.now() | |||
| } | |||
| stockLedgerRepository.saveAndFlush(stockLedger) | |||
| } | |||
| } | |||
| @@ -37,6 +37,8 @@ import com.ffii.fpsms.modules.bag.web.model.CreateBagLotLineRequest | |||
| import com.ffii.fpsms.modules.common.CodeGenerator | |||
| import org.springframework.context.annotation.Lazy | |||
| import com.ffii.fpsms.modules.bag.service.BagService | |||
| import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryRepository | |||
| import java.time.LocalTime | |||
| @Service | |||
| @@ -46,7 +48,6 @@ open class StockOutLineService( | |||
| private val stockOutRepository: StockOutRepository, | |||
| private val stockOutLineRepository: StockOutLIneRepository, | |||
| private val itemRepository: ItemsRepository, | |||
| private val inventoryRepository: InventoryRepository, | |||
| private val itemUomRespository: ItemUomRespository, | |||
| private val pickOrderRepository: PickOrderRepository, | |||
| private val inventoryLotLineRepository: InventoryLotLineRepository, | |||
| @@ -59,7 +60,9 @@ private val deliveryOrderRepository: DeliveryOrderRepository, | |||
| private val doPickOrderLineRepository: DoPickOrderLineRepository, | |||
| private val doPickOrderLineRecordRepository: DoPickOrderLineRecordRepository, | |||
| private val inventoryLotLineService: InventoryLotLineService, | |||
| private val bagService: BagService | |||
| private val bagService: BagService, | |||
| private val stockLedgerRepository: StockLedgerRepository, | |||
| private val inventoryRepository: InventoryRepository | |||
| ): AbstractBaseEntityService<StockOutLine, Long, StockOutLIneRepository>(jdbcDao, stockOutLineRepository) { | |||
| @Throws(IOException::class) | |||
| @Transactional | |||
| @@ -135,6 +138,7 @@ val existingStockOutLine = stockOutLineRepository.findByPickOrderLineIdAndInvent | |||
| ) | |||
| val item = itemRepository.findById(pickOrderLine.item!!.id!!).orElseThrow() | |||
| val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | |||
| val stockOutLine = StockOutLine() | |||
| .apply { | |||
| this.item = item | |||
| @@ -143,8 +147,10 @@ val existingStockOutLine = stockOutLineRepository.findByPickOrderLineIdAndInvent | |||
| this.inventoryLotLine = inventoryLotLine | |||
| this.pickOrderLine = pickOrderLine | |||
| this.status = StockOutLineStatus.PENDING.status | |||
| this.type = "Nor" | |||
| } | |||
| val savedStockOutLine = saveAndFlush(stockOutLine) | |||
| createStockLedgerForStockOut(savedStockOutLine) | |||
| val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) | |||
| // println("triggering") | |||
| return MessageResponse( | |||
| @@ -231,7 +237,7 @@ open fun createWithoutConso(request: CreateStockOutLineWithoutConsoRequest): Mes | |||
| IllegalArgumentException("InventoryLotLine not found with ID: ${request.inventoryLotLineId}") | |||
| } | |||
| println("Found inventoryLotLine: ${inventoryLotLine.id}") | |||
| val stockOutLine = StockOutLine() | |||
| .apply { | |||
| this.item = item | |||
| @@ -240,10 +246,12 @@ open fun createWithoutConso(request: CreateStockOutLineWithoutConsoRequest): Mes | |||
| this.inventoryLotLine = inventoryLotLine | |||
| this.pickOrderLine = updatedPickOrderLine | |||
| this.status = StockOutLineStatus.PENDING.status | |||
| this.type = "Nor" | |||
| } | |||
| println("Created stockOutLine with qty: ${request.qty}") | |||
| val savedStockOutLine = saveAndFlush(stockOutLine) | |||
| createStockLedgerForStockOut(savedStockOutLine) | |||
| println("Saved stockOutLine with ID: ${savedStockOutLine.id}") | |||
| val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) | |||
| @@ -536,7 +544,12 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||
| println("Updating StockOutLine ID: ${request.id}") | |||
| println("Current status: ${stockOutLine.status}") | |||
| println("New status: ${request.status}") | |||
| if (request.status == "checked") { | |||
| stockOutLine.startTime = LocalDateTime.now() | |||
| } | |||
| if (request.status == "completed") { | |||
| stockOutLine.endTime = LocalDateTime.now() | |||
| } | |||
| // 2. 更新自身 status/qty | |||
| stockOutLine.status = request.status | |||
| if (request.qty != null) { | |||
| @@ -582,14 +595,14 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||
| bagService.createBagLotLinesByBagId(createBagLotLineRequest) | |||
| println(" ✓ BagLotLine created successfully for item ${item.code}") | |||
| } else { | |||
| println(" ⚠️ Warning: lotNo is null, skipping BagLotLine creation") | |||
| println(" Warning: lotNo is null, skipping BagLotLine creation") | |||
| } | |||
| } else { | |||
| println(" ⚠️ Warning: Bag not found for itemId ${item.id}, skipping BagLotLine creation") | |||
| println(" Warning: Bag not found for itemId ${item.id}, skipping BagLotLine creation") | |||
| } | |||
| } | |||
| } catch (e: Exception) { | |||
| println(" ⚠️ Error creating BagLotLine: ${e.message}") | |||
| println(" Error creating BagLotLine: ${e.message}") | |||
| e.printStackTrace() | |||
| // 不中断主流程,只记录错误 | |||
| } | |||
| @@ -891,7 +904,7 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta | |||
| ) | |||
| } | |||
| println("🔍 Checking inventory lot:") | |||
| println(" Checking inventory lot:") | |||
| println(" - Found inventory lot:") | |||
| println(" - Lot No: ${inventoryLot.lotNo}") | |||
| println(" - Item ID: ${inventoryLot.item?.id}") | |||
| @@ -906,10 +919,16 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta | |||
| println(" - Item ID match: $itemIdMatch (Expected: ${inventoryLot.item?.id}, Got: ${request.itemId})") | |||
| if (lotNoMatch && itemIdMatch) { | |||
| // 匹配成功,更新状态 | |||
| println(" MATCH SUCCESS: Lot and Item both match!") | |||
| stockOutLine.status = request.status | |||
| if (request.status == "checked") { | |||
| stockOutLine.startTime = LocalDateTime.now() | |||
| } | |||
| if (request.status == "completed") { | |||
| stockOutLine.endTime = LocalDateTime.now() | |||
| } | |||
| val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) | |||
| println(" Status updated successfully:") | |||
| @@ -1099,14 +1118,14 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { | |||
| bagService.createBagLotLinesByBagId(createBagLotLineRequest) | |||
| println(" ✓ BagLotLine created successfully for item ${item.code}") | |||
| } else { | |||
| println(" ⚠️ Warning: lotNo is null, skipping BagLotLine creation") | |||
| println(" Warning: lotNo is null, skipping BagLotLine creation") | |||
| } | |||
| } else { | |||
| println(" ⚠️ Warning: Bag not found for itemId ${item.id}, skipping BagLotLine creation") | |||
| println(" Warning: Bag not found for itemId ${item.id}, skipping BagLotLine creation") | |||
| } | |||
| } | |||
| } catch (e: Exception) { | |||
| println(" ⚠️ Error creating BagLotLine: ${e.message}") | |||
| println(" Error creating BagLotLine: ${e.message}") | |||
| e.printStackTrace() | |||
| // 不中断主流程,只记录错误 | |||
| } | |||
| @@ -1165,4 +1184,28 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { | |||
| ) | |||
| ) | |||
| } | |||
| }} | |||
| } | |||
| @Transactional | |||
| private fun createStockLedgerForStockOut(stockOutLine: StockOutLine) { | |||
| val item = stockOutLine.item ?: return | |||
| val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return | |||
| val outQty = stockOutLine.qty?.toDouble() ?: 0.0 | |||
| // 直接使用 inventory.onHandQty 作为 balance(已经是更新后的值) | |||
| val newBalance = (inventory.onHandQty ?: BigDecimal.ZERO).toDouble() | |||
| val stockLedger = StockLedger().apply { | |||
| this.stockOutLine = stockOutLine | |||
| this.inventory = inventory | |||
| this.inQty = null | |||
| this.outQty = outQty | |||
| this.balance = newBalance | |||
| this.type = stockOutLine.type | |||
| this.itemId = item.id | |||
| this.itemCode = item.code | |||
| this.date = LocalDate.now() | |||
| } | |||
| stockLedgerRepository.saveAndFlush(stockLedger) | |||
| } | |||
| } | |||
| @@ -14,7 +14,9 @@ import org.springframework.stereotype.Service | |||
| import java.time.LocalDateTime | |||
| import java.math.BigDecimal | |||
| import com.ffii.fpsms.modules.user.entity.UserRepository | |||
| import org.springframework.data.domain.PageRequest | |||
| import com.ffii.core.response.RecordsRes | |||
| import java.time.LocalDate | |||
| import com.ffii.fpsms.modules.stock.service.InventoryLotLineService | |||
| import com.ffii.fpsms.modules.stock.entity.StockTakeLine | |||
| import com.ffii.fpsms.modules.stock.entity.StockTakeLineRepository | |||
| @@ -29,6 +31,16 @@ import com.ffii.fpsms.modules.stock.entity.StockInLine | |||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLot | |||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | |||
| import jakarta.persistence.EntityManager | |||
| import jakarta.persistence.PersistenceContext | |||
| import org.hibernate.Session | |||
| import org.hibernate.jdbc.Work | |||
| import java.sql.Connection | |||
| import java.sql.PreparedStatement | |||
| 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 | |||
| @Service | |||
| class StockTakeRecordService( | |||
| val stockTakeRepository: StockTakeRepository, | |||
| @@ -42,7 +54,10 @@ class StockTakeRecordService( | |||
| val stockOutLineRepository: StockOutLIneRepository, | |||
| val stockInRepository: StockInRepository, | |||
| val stockInLineRepository: StockInLineRepository, | |||
| val inventoryLotRepository: InventoryLotRepository | |||
| val inventoryLotRepository: InventoryLotRepository, | |||
| val stockLedgerRepository: StockLedgerRepository, | |||
| val inventoryRepository: InventoryRepository | |||
| ) { | |||
| private val logger: Logger = LoggerFactory.getLogger(StockTakeRecordService::class.java) | |||
| @@ -138,6 +153,7 @@ class StockTakeRecordService( | |||
| } | |||
| // 9. 计算 TotalItemNumber:获取该 section 下所有 InventoryLotLine,按 item 分组,计算不同的 item 数量 | |||
| val totalItemNumber = inventoryLotLineRepository.countDistinctItemsByWarehouseIds(warehouseIds).toInt() | |||
| val totalInventoryLotNumber = inventoryLotLineRepository.countAllByWarehouseIds(warehouseIds).toInt() | |||
| // 8. 使用 stockTakeSection 作为 stockTakeSession | |||
| val reStockTakeTrueFalse = if (latestStockTake != null) { | |||
| // 检查该 stock take 下该 section 的记录中是否有 notMatch 状态 | |||
| @@ -158,7 +174,7 @@ class StockTakeRecordService( | |||
| lastStockTakeDate = latestStockTake?.actualStart?.toLocalDate(), | |||
| status = status?:"", | |||
| currentStockTakeItemNumber = 0, | |||
| totalInventoryLotNumber = 0, | |||
| totalInventoryLotNumber = totalInventoryLotNumber, | |||
| stockTakeId = latestStockTake?.id ?: 0, | |||
| stockTakerName = stockTakerName, | |||
| TotalItemNumber = totalItemNumber, | |||
| @@ -286,6 +302,7 @@ class StockTakeRecordService( | |||
| } | |||
| // 9. 计算 TotalItemNumber:获取该 section 下所有 InventoryLotLine,按 item 分组,计算不同的 item 数量 | |||
| val totalItemNumber = inventoryLotLineRepository.countDistinctItemsByWarehouseIds(warehouseIds).toInt() | |||
| val totalInventoryLotNumber = inventoryLotLineRepository.countAllByWarehouseIds(warehouseIds).toInt() | |||
| // 9. 使用 stockTakeSection 作为 stockTakeSession | |||
| result.add( | |||
| AllPickedStockTakeListReponse( | |||
| @@ -294,7 +311,7 @@ class StockTakeRecordService( | |||
| lastStockTakeDate = latestStockTake?.actualStart?.toLocalDate(), | |||
| status = status?:"", | |||
| currentStockTakeItemNumber = 0, // 临时设为 0,测试性能 | |||
| totalInventoryLotNumber = 0, // 临时设为 0,测试性能 | |||
| totalInventoryLotNumber = totalInventoryLotNumber, // 临时设为 0,测试性能 | |||
| stockTakeId = latestStockTake?.id ?: 0, | |||
| stockTakerName = stockTakerName, | |||
| approverName = approverName, | |||
| @@ -376,92 +393,103 @@ class StockTakeRecordService( | |||
| ) | |||
| } | |||
| } | |||
| open fun getInventoryLotDetailsByStockTakeSection(stockTakeSection: String, stockTakeId: Long? = null): List<InventoryLotDetailResponse> { | |||
| println("getInventoryLotDetailsByStockTakeSection called with section: $stockTakeSection, stockTakeId: $stockTakeId") | |||
| val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(stockTakeSection) | |||
| if (warehouses.isEmpty()) { | |||
| logger.warn("No warehouses found for stockTakeSection: $stockTakeSection") | |||
| return emptyList() | |||
| } | |||
| val warehouseIds = warehouses.mapNotNull { it.id } | |||
| println("Found ${warehouses.size} warehouses for section $stockTakeSection") | |||
| val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds) | |||
| println("Found ${inventoryLotLines.size} inventory lot lines") | |||
| val stockTakeRecordsMap = if (stockTakeId != null) { | |||
| val allStockTakeRecords = stockTakeRecordRepository.findAll() | |||
| .filter { | |||
| !it.deleted && | |||
| it.stockTake?.id == stockTakeId && | |||
| it.warehouse?.id in warehouseIds | |||
| } | |||
| // 按 lotId 和 warehouseId 建立映射 | |||
| allStockTakeRecords.associateBy { | |||
| Pair(it.lotId ?: 0L, it.warehouse?.id ?: 0L) | |||
| open fun getInventoryLotDetailsByStockTakeSection( | |||
| stockTakeSection: String, | |||
| stockTakeId: Long? = null, | |||
| pageNum: Int = 0, | |||
| pageSize: Int = 10 | |||
| ): RecordsRes<InventoryLotDetailResponse> { | |||
| println("getInventoryLotDetailsByStockTakeSection called with section: $stockTakeSection, stockTakeId: $stockTakeId, pageNum: $pageNum, pageSize: $pageSize") | |||
| val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(stockTakeSection) | |||
| if (warehouses.isEmpty()) { | |||
| logger.warn("No warehouses found for stockTakeSection: $stockTakeSection") | |||
| return RecordsRes(emptyList(), 0) | |||
| } | |||
| val warehouseIds = warehouses.mapNotNull { it.id } | |||
| println("Found ${warehouses.size} warehouses for section $stockTakeSection") | |||
| val inventoryLotLines = inventoryLotLineRepository.findAllByWarehouseIdInAndDeletedIsFalse(warehouseIds) | |||
| println("Found ${inventoryLotLines.size} inventory lot lines") | |||
| val stockTakeRecordsMap = if (stockTakeId != null) { | |||
| val allStockTakeRecords = stockTakeRecordRepository.findAll() | |||
| .filter { | |||
| !it.deleted && | |||
| it.stockTake?.id == stockTakeId && | |||
| it.warehouse?.id in warehouseIds | |||
| } | |||
| } else { | |||
| emptyMap() | |||
| // 按 lotId 和 warehouseId 建立映射 | |||
| allStockTakeRecords.associateBy { | |||
| Pair(it.lotId ?: 0L, it.warehouse?.id ?: 0L) | |||
| } | |||
| } else { | |||
| emptyMap() | |||
| } | |||
| val allResults = inventoryLotLines.map { ill -> | |||
| val inventoryLot = ill.inventoryLot | |||
| val item = inventoryLot?.item | |||
| val warehouse = ill.warehouse | |||
| val availableQty = (ill.inQty ?: BigDecimal.ZERO) | |||
| .subtract(ill.outQty ?: BigDecimal.ZERO) | |||
| .subtract(ill.holdQty ?: BigDecimal.ZERO) | |||
| return inventoryLotLines.map { ill -> | |||
| val inventoryLot = ill.inventoryLot | |||
| val item = inventoryLot?.item | |||
| val warehouse = ill.warehouse | |||
| val availableQty = (ill.inQty ?: BigDecimal.ZERO) | |||
| .subtract(ill.outQty ?: BigDecimal.ZERO) | |||
| .subtract(ill.holdQty ?: BigDecimal.ZERO) | |||
| val stockTakeRecord = if (stockTakeId != null && inventoryLot?.id != null && warehouse?.id != null) { | |||
| stockTakeRecordsMap[Pair(inventoryLot.id, warehouse.id)] | |||
| } else { | |||
| null | |||
| } | |||
| val inventoryLotLineId = ill.id | |||
| val stockTakeLine = stockTakeLineRepository.findByInventoryLotLineIdAndStockTakeIdAndDeletedIsFalse(inventoryLotLineId, stockTakeId!!) | |||
| InventoryLotDetailResponse( | |||
| id = ill.id ?: 0L, | |||
| inventoryLotId = inventoryLot?.id ?: 0L, | |||
| itemId = item?.id ?: 0L, | |||
| itemCode = item?.code, | |||
| itemName = item?.name, | |||
| lotNo = inventoryLot?.lotNo, | |||
| expiryDate = inventoryLot?.expiryDate, | |||
| productionDate = inventoryLot?.productionDate, | |||
| stockInDate = inventoryLot?.stockInDate, | |||
| inQty = ill.inQty, | |||
| remarks = stockTakeRecord?.remarks, | |||
| outQty = ill.outQty, | |||
| holdQty = ill.holdQty, | |||
| availableQty = availableQty, | |||
| uom = ill.stockUom?.uom?.udfudesc, | |||
| warehouseCode = warehouse?.code, | |||
| warehouseName = warehouse?.name, | |||
| status = ill.status?.name, | |||
| warehouseSlot = warehouse?.slot, | |||
| warehouseArea = warehouse?.area, | |||
| warehouse = warehouse?.warehouse, | |||
| varianceQty = stockTakeRecord?.varianceQty, | |||
| stockTakeRecordId = stockTakeRecord?.id, | |||
| stockTakeRecordStatus = stockTakeRecord?.status, | |||
| firstStockTakeQty = stockTakeRecord?.pickerFirstStockTakeQty, | |||
| secondStockTakeQty = stockTakeRecord?.pickerSecondStockTakeQty, | |||
| firstBadQty = stockTakeRecord?.pickerFirstBadQty, | |||
| secondBadQty = stockTakeRecord?.pickerSecondBadQty, | |||
| approverQty = stockTakeRecord?.approverStockTakeQty , | |||
| approverBadQty = stockTakeRecord?.approverBadQty, | |||
| finalQty = stockTakeLine?.finalQty, | |||
| //finalQty = null, | |||
| ) | |||
| val stockTakeRecord = if (stockTakeId != null && inventoryLot?.id != null && warehouse?.id != null) { | |||
| stockTakeRecordsMap[Pair(inventoryLot.id, warehouse.id)] | |||
| } else { | |||
| null | |||
| } | |||
| val inventoryLotLineId = ill.id | |||
| val stockTakeLine = stockTakeLineRepository.findByInventoryLotLineIdAndStockTakeIdAndDeletedIsFalse(inventoryLotLineId, stockTakeId!!) | |||
| InventoryLotDetailResponse( | |||
| id = ill.id ?: 0L, | |||
| inventoryLotId = inventoryLot?.id ?: 0L, | |||
| itemId = item?.id ?: 0L, | |||
| itemCode = item?.code, | |||
| itemName = item?.name, | |||
| lotNo = inventoryLot?.lotNo, | |||
| expiryDate = inventoryLot?.expiryDate, | |||
| productionDate = inventoryLot?.productionDate, | |||
| stockInDate = inventoryLot?.stockInDate, | |||
| inQty = ill.inQty, | |||
| remarks = stockTakeRecord?.remarks, | |||
| outQty = ill.outQty, | |||
| holdQty = ill.holdQty, | |||
| availableQty = availableQty, | |||
| uom = ill.stockUom?.uom?.udfudesc, | |||
| warehouseCode = warehouse?.code, | |||
| warehouseName = warehouse?.name, | |||
| status = ill.status?.name, | |||
| warehouseSlot = warehouse?.slot, | |||
| warehouseArea = warehouse?.area, | |||
| warehouse = warehouse?.warehouse, | |||
| varianceQty = stockTakeRecord?.varianceQty, | |||
| stockTakeRecordId = stockTakeRecord?.id, | |||
| stockTakeRecordStatus = stockTakeRecord?.status, | |||
| firstStockTakeQty = stockTakeRecord?.pickerFirstStockTakeQty, | |||
| secondStockTakeQty = stockTakeRecord?.pickerSecondStockTakeQty, | |||
| firstBadQty = stockTakeRecord?.pickerFirstBadQty, | |||
| secondBadQty = stockTakeRecord?.pickerSecondBadQty, | |||
| approverQty = stockTakeRecord?.approverStockTakeQty , | |||
| approverBadQty = stockTakeRecord?.approverBadQty, | |||
| finalQty = stockTakeLine?.finalQty, | |||
| ) | |||
| } | |||
| // Apply pagination | |||
| val pageable = PageRequest.of(pageNum, pageSize) | |||
| val startIndex = pageable.offset.toInt() | |||
| val endIndex = minOf(startIndex + pageSize, allResults.size) | |||
| val paginatedResult = if (startIndex < allResults.size) { | |||
| allResults.subList(startIndex, endIndex) | |||
| } else { | |||
| emptyList() | |||
| } | |||
| return RecordsRes(paginatedResult, allResults.size) | |||
| } | |||
| open fun saveStockTakeRecord( | |||
| request: SaveStockTakeRecordRequest, | |||
| stockTakeId: Long, | |||
| @@ -750,30 +778,32 @@ open fun checkAndUpdateStockTakeStatus(stockTakeId: Long, stockTakeSection: Stri | |||
| val allRecordsCompleted = stockTakeRecords.isNotEmpty() && | |||
| stockTakeRecords.all { it.status == "completed" } | |||
| // 6. 如果所有记录都已创建且都是 "pass",更新 stock take 状态为 "approving" | |||
| if (allLinesHaveRecords && allRecordsPassed) { | |||
| if (allLinesHaveRecords && allRecordsCompleted) { | |||
| stockTake.status = StockTakeStatus.COMPLETED | |||
| stockTake.planEnd = java.time.LocalDateTime.now() | |||
| stockTakeRepository.save(stockTake) | |||
| println("Stock take $stockTakeId status updated to COMPLETED - all records are completed") | |||
| return mapOf("success" to true, "message" to "Stock take status updated to COMPLETED", "updated" to true) | |||
| } else if (allLinesHaveRecords && allRecordsPassed) { | |||
| // 如果所有记录都已创建且都是 "pass" 或 "completed",更新 stock take 状态为 "approving" | |||
| stockTake.status = StockTakeStatus.APPROVING | |||
| stockTake.actualEnd = java.time.LocalDateTime.now() | |||
| stockTakeRepository.save(stockTake) | |||
| println("Stock take $stockTakeId status updated to APPROVING - all records are pass") | |||
| return mapOf( | |||
| "success" to true, | |||
| "message" to "Stock take status updated to APPROVING", | |||
| "updated" to true | |||
| ) | |||
| } else if (allLinesHaveRecords && allRecordsCompleted) { | |||
| stockTake.status = StockTakeStatus.COMPLETED | |||
| stockTake.planEnd = java.time.LocalDateTime.now() | |||
| stockTakeRepository.save(stockTake) | |||
| println("Stock take $stockTakeId status updated to COMPLETED - all records are completed") | |||
| return mapOf("success" to true, "message" to "Stock take status updated to COMPLETED", "updated" to true) | |||
| } else { | |||
| return mapOf( | |||
| "success" to true, | |||
| "message" to "Conditions not met for status update", | |||
| "updated" to false, | |||
| "allLinesHaveRecords" to allLinesHaveRecords, | |||
| "allRecordsPassed" to allRecordsPassed | |||
| "allRecordsPassed" to allRecordsPassed, | |||
| "allRecordsCompleted" to allRecordsCompleted | |||
| ) | |||
| } | |||
| } catch (e: Exception) { | |||
| @@ -835,6 +865,8 @@ open fun saveApproverStockTakeRecord( | |||
| val inventoryLot = inventoryLotRepository.findByIdAndDeletedFalse( | |||
| inventoryLotLine?.inventoryLot?.id ?: throw IllegalArgumentException("Inventory lot ID not found") | |||
| ) ?: throw IllegalArgumentException("Inventory lot not found") | |||
| val inventory = inventoryRepository.findByItemId(inventoryLot.item?.id ?: throw IllegalArgumentException("Item ID not found")) | |||
| .orElse(null) ?: throw IllegalArgumentException("Inventory not found for item") | |||
| if (varianceQty !=BigDecimal.ZERO ) { | |||
| val stockTakeLine = StockTakeLine().apply { | |||
| @@ -849,13 +881,14 @@ open fun saveApproverStockTakeRecord( | |||
| stockTakeLineRepository.save(stockTakeLine) | |||
| if (varianceQty < BigDecimal.ZERO ) { | |||
| val stockOut=StockOut().apply { | |||
| this.type="stockTake" | |||
| this.status="completed" | |||
| this.handler=request.approverId | |||
| } | |||
| stockOutRepository.save(stockOut) | |||
| var stockOut = stockOutRepository.findByStockTakeIdAndDeletedFalse(stockTakeId) | |||
| ?: StockOut().apply { | |||
| this.type = "stockTake" | |||
| this.status = "completed" | |||
| this.handler = request.approverId | |||
| }.also { stockOutRepository.save(it) } | |||
| val stockOutLine = StockOutLine().apply { | |||
| this.item=inventoryLot.item | |||
| this.qty=(-varianceQty)?.toDouble() | |||
| @@ -866,21 +899,35 @@ open fun saveApproverStockTakeRecord( | |||
| } | |||
| stockOutLineRepository.save(stockOutLine) | |||
| val newBalance = (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()-(varianceQty).toDouble() | |||
| val stockLedger = StockLedger().apply { | |||
| this.inventory=inventory | |||
| this.itemId=inventoryLot.item?.id | |||
| this.itemCode=stockTakeRecord.itemCode | |||
| this.inQty=null | |||
| this.outQty=(-varianceQty)?.toDouble() | |||
| this.stockOutLine=stockOutLine | |||
| this.balance=newBalance | |||
| this.type="Adj" | |||
| this.date = LocalDate.now() | |||
| } | |||
| stockLedgerRepository.save(stockLedger) | |||
| } | |||
| if (varianceQty > BigDecimal.ZERO ) { | |||
| val stockIn=StockIn().apply { | |||
| this.code=stockTake.code | |||
| this.status="completed" | |||
| this.stockTake=stockTake | |||
| } | |||
| stockInRepository.save(stockIn) | |||
| var stockIn = stockInRepository.findByStockTakeIdAndDeletedFalse(stockTakeId) | |||
| ?: StockIn().apply { | |||
| this.code = stockTake.code | |||
| this.status = "completed" | |||
| this.stockTake = stockTake | |||
| }.also { stockInRepository.save(it) } | |||
| val stockInLine = StockInLine().apply { | |||
| this.stockTakeLine=stockTakeLine | |||
| this.item=inventoryLot.item | |||
| this.itemNo=stockTakeRecord.itemCode | |||
| this.stockIn = stockIn | |||
| this.demandQty=finalQty | |||
| this.acceptedQty=finalQty | |||
| this.demandQty=varianceQty | |||
| this.acceptedQty=varianceQty | |||
| this.expiryDate=inventoryLot.expiryDate | |||
| this.inventoryLot=inventoryLot | |||
| this.inventoryLotLine=inventoryLotLine | |||
| @@ -891,9 +938,21 @@ open fun saveApproverStockTakeRecord( | |||
| } | |||
| stockInLineRepository.save(stockInLine) | |||
| val newBalance = (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()+varianceQty.toDouble() | |||
| val stockLedger = StockLedger().apply { | |||
| this.inventory=inventory | |||
| this.itemId=inventoryLot.item?.id | |||
| this.itemCode=inventoryLot.item?.code | |||
| this.inQty=varianceQty?.toDouble() | |||
| this.stockInLine=stockInLine | |||
| this.outQty=null | |||
| this.balance=newBalance | |||
| this.type="Adj" | |||
| this.date = LocalDate.now() | |||
| } | |||
| stockLedgerRepository.save(stockLedger) | |||
| } | |||
| // val currentInQty = inventoryLotLine.inQty ?: BigDecimal.ZERO | |||
| // val newInQty = currentInQty.add(variance) | |||
| val updateRequest = SaveInventoryLotLineRequest( | |||
| id = inventoryLotLine.id, | |||
| @@ -925,9 +984,7 @@ open fun batchSaveApproverStockTakeRecords( | |||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | |||
| ?: throw IllegalArgumentException("Stock take not found: ${request.stockTakeId}") | |||
| // 2. Get all stock take records for this section where: | |||
| // - pickerFirstStockTakeQty is not 0 | |||
| // - approverStockTakeQty is null (not yet approved) | |||
| val stockTakeRecords = stockTakeRecordRepository.findAll() | |||
| .filter { | |||
| !it.deleted && | |||
| @@ -982,7 +1039,7 @@ open fun batchSaveApproverStockTakeRecords( | |||
| this.approverName = user?.name | |||
| this.approverStockTakeQty = qty | |||
| this.approverBadQty = badQty | |||
| this.varianceQty = varianceQty // 应该是 0 | |||
| this.varianceQty = varianceQty | |||
| this.status = "completed" | |||
| } | |||
| @@ -1023,13 +1080,18 @@ open fun updateStockTakeRecordStatusToNotMatch(stockTakeRecordId: Long): StockTa | |||
| return stockTakeRecordRepository.save(stockTakeRecord) | |||
| } | |||
| open fun getInventoryLotDetailsByStockTakeSectionNotMatch(stockTakeSection: String, stockTakeId: Long? = null): List<InventoryLotDetailResponse> { | |||
| println("getInventoryLotDetailsByStockTakeSectionNotMatch called with section: $stockTakeSection, stockTakeId: $stockTakeId") | |||
| open fun getInventoryLotDetailsByStockTakeSectionNotMatch( | |||
| stockTakeSection: String, | |||
| stockTakeId: Long? = null, | |||
| pageNum: Int = 0, | |||
| pageSize: Int = Int.MAX_VALUE // Default to return all if not specified | |||
| ): RecordsRes<InventoryLotDetailResponse> { | |||
| println("getInventoryLotDetailsByStockTakeSectionNotMatch called with section: $stockTakeSection, stockTakeId: $stockTakeId, pageNum: $pageNum, pageSize: $pageSize") | |||
| val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(stockTakeSection) | |||
| if (warehouses.isEmpty()) { | |||
| logger.warn("No warehouses found for stockTakeSection: $stockTakeSection") | |||
| return emptyList() | |||
| return RecordsRes(emptyList(), 0) | |||
| } | |||
| val warehouseIds = warehouses.mapNotNull { it.id } | |||
| @@ -1055,7 +1117,7 @@ open fun getInventoryLotDetailsByStockTakeSectionNotMatch(stockTakeSection: Stri | |||
| } | |||
| // 只返回有 notMatch 记录的 inventory lot lines | |||
| return inventoryLotLines.mapNotNull { ill -> | |||
| val allResults = inventoryLotLines.mapNotNull { ill -> | |||
| val inventoryLot = ill.inventoryLot | |||
| val item = inventoryLot?.item | |||
| val warehouse = ill.warehouse | |||
| @@ -1115,5 +1177,120 @@ open fun getInventoryLotDetailsByStockTakeSectionNotMatch(stockTakeSection: Stri | |||
| finalQty = stockTakeLine?.finalQty, | |||
| ) | |||
| } | |||
| // Apply pagination - if pageSize is Int.MAX_VALUE, return all results | |||
| val paginatedResult = if (pageSize >= allResults.size) { | |||
| // Return all results if pageSize is large enough | |||
| allResults | |||
| } else { | |||
| val pageable = PageRequest.of(pageNum, pageSize) | |||
| val startIndex = pageable.offset.toInt() | |||
| val endIndex = minOf(startIndex + pageSize, allResults.size) | |||
| if (startIndex < allResults.size) { | |||
| allResults.subList(startIndex, endIndex) | |||
| } else { | |||
| emptyList() | |||
| } | |||
| } | |||
| return RecordsRes(paginatedResult, allResults.size) | |||
| } | |||
| fun searchStockTransactions(request: SearchStockTransactionRequest): RecordsRes<StockTransactionResponse> { | |||
| val startTime = System.currentTimeMillis() | |||
| // 添加调试日志 | |||
| println("Search request received: itemCode=${request.itemCode}, itemName=${request.itemName}, type=${request.type}, startDate=${request.startDate}, endDate=${request.endDate}") | |||
| // 验证:itemCode 或 itemName 至少一个不为 null 或空字符串 | |||
| val itemCode = request.itemCode?.trim()?.takeIf { it.isNotEmpty() } | |||
| val itemName = request.itemName?.trim()?.takeIf { it.isNotEmpty() } | |||
| if (itemCode == null && itemName == null) { | |||
| println("Search validation failed: both itemCode and itemName are null/empty") | |||
| return RecordsRes(emptyList(), 0) | |||
| } | |||
| // request.startDate 和 request.endDate 已经是 LocalDate? 类型,不需要转换 | |||
| val startDate = request.startDate | |||
| val endDate = request.endDate | |||
| println("Processed params: itemCode=$itemCode, itemName=$itemName, startDate=$startDate, endDate=$endDate") | |||
| // 使用 Repository 查询(更简单、更快) | |||
| val total = stockLedgerRepository.countStockTransactions( | |||
| itemCode = itemCode, | |||
| itemName = itemName, | |||
| type = request.type, | |||
| startDate = startDate, | |||
| endDate = endDate | |||
| ) | |||
| println("Total count: $total") | |||
| // 如果 pageSize 是默认值(100)或未设置,使用 total 作为 pageSize | |||
| val actualPageSize = if (request.pageSize == 100) { | |||
| total.toInt().coerceAtLeast(1) | |||
| } else { | |||
| request.pageSize | |||
| } | |||
| // 计算 offset | |||
| val offset = request.pageNum * actualPageSize | |||
| // 查询所有符合条件的记录 | |||
| val ledgers = stockLedgerRepository.findStockTransactions( | |||
| itemCode = itemCode, | |||
| itemName = itemName, | |||
| type = request.type, | |||
| startDate = startDate, | |||
| endDate = endDate | |||
| ) | |||
| println("Found ${ledgers.size} ledgers") | |||
| val transactions = ledgers.map { ledger -> | |||
| val stockInLine = ledger.stockInLine | |||
| val stockOutLine = ledger.stockOutLine | |||
| StockTransactionResponse( | |||
| id = stockInLine?.id ?: stockOutLine?.id ?: 0L, | |||
| transactionType = if (ledger.inQty != null && ledger.inQty!! > 0) "IN" else "OUT", | |||
| itemId = ledger.itemId ?: 0L, | |||
| itemCode = ledger.itemCode, | |||
| itemName = ledger.inventory?.item?.name, | |||
| balanceQty = ledger.balance?.let { balance: Double -> BigDecimal(balance.toString()) }, | |||
| qty = if (ledger.inQty != null && ledger.inQty!! > 0) { | |||
| BigDecimal(ledger.inQty.toString()) | |||
| } else { | |||
| BigDecimal(ledger.outQty?.toString() ?: "0") | |||
| }, | |||
| type = ledger.type, | |||
| status = stockInLine?.status ?: stockOutLine?.status ?: "", | |||
| transactionDate = ledger.created, | |||
| date = ledger.date, | |||
| lotNo = stockInLine?.lotNo ?: (stockOutLine?.inventoryLotLine?.inventoryLot?.lotNo), | |||
| stockInId = stockInLine?.stockIn?.id, | |||
| stockOutId = stockOutLine?.stockOut?.id, | |||
| remarks = stockInLine?.remarks | |||
| ) | |||
| } | |||
| // 按 date 排序(从旧到新),如果 date 为 null 则使用 transactionDate 的日期部分 | |||
| val sortedTransactions = transactions.sortedWith( | |||
| compareBy<StockTransactionResponse>( | |||
| { it.date ?: it.transactionDate?.toLocalDate() }, | |||
| { it.transactionDate } | |||
| ) | |||
| ) | |||
| // 应用分页 | |||
| val paginatedTransactions = sortedTransactions.drop(offset).take(actualPageSize) | |||
| val totalTime = System.currentTimeMillis() - startTime | |||
| println("Total time (Repository query): ${totalTime}ms, count: ${paginatedTransactions.size}, total: $total") | |||
| return RecordsRes(paginatedTransactions, total.toInt()) | |||
| } | |||
| } | |||
| } | |||
| @@ -480,6 +480,7 @@ open class SuggestedPickLotService( | |||
| this.qty = (suggestion.qty ?: BigDecimal.ZERO).toDouble() | |||
| this.status = StockOutLineStatus.PENDING.status | |||
| this.deleted = false | |||
| this.type = "Nor" | |||
| } | |||
| val savedStockOutLine = stockOutLIneRepository.save(stockOutLine) | |||
| @@ -529,6 +530,7 @@ open class SuggestedPickLotService( | |||
| this.inventoryLotLine = suggestedLotLine | |||
| this.pickOrderLine = updatedPickOrderLine | |||
| this.status = StockOutLineStatus.PENDING.status | |||
| this.type = "Nor" | |||
| } | |||
| val savedStockOutLine = stockOutLIneRepository.saveAndFlush(stockOutLine) | |||
| @@ -10,7 +10,8 @@ import org.springframework.web.bind.annotation.RequestBody | |||
| import org.springframework.http.ResponseEntity | |||
| import com.ffii.fpsms.modules.stock.web.model.* | |||
| import org.slf4j.LoggerFactory | |||
| import com.ffii.core.response.RecordsRes | |||
| import java.time.LocalDate | |||
| @RestController | |||
| @RequestMapping("/stockTakeRecord") | |||
| class StockTakeRecordController( | |||
| @@ -29,9 +30,18 @@ class StockTakeRecordController( | |||
| @GetMapping("/inventoryLotDetailsBySectionNotMatch") | |||
| fun getInventoryLotDetailsByStockTakeSectionNotMatch( | |||
| @RequestParam stockTakeSection: String, | |||
| @RequestParam(required = false) stockTakeId: Long? | |||
| ): List<InventoryLotDetailResponse> { | |||
| return stockOutRecordService.getInventoryLotDetailsByStockTakeSectionNotMatch(stockTakeSection, stockTakeId) | |||
| @RequestParam(required = false) stockTakeId: Long?, | |||
| @RequestParam(required = false, defaultValue = "0") pageNum: Int, | |||
| @RequestParam(required = false) pageSize: Int? | |||
| ): RecordsRes<InventoryLotDetailResponse> { | |||
| // If pageSize is null, use a large number to return all records | |||
| val actualPageSize = pageSize ?: Int.MAX_VALUE | |||
| return stockOutRecordService.getInventoryLotDetailsByStockTakeSectionNotMatch( | |||
| stockTakeSection, | |||
| stockTakeId, | |||
| pageNum, | |||
| actualPageSize | |||
| ) | |||
| } | |||
| @GetMapping("/inventoryLotDetails") | |||
| @@ -42,12 +52,14 @@ class StockTakeRecordController( | |||
| } | |||
| @GetMapping("/inventoryLotDetailsBySection") | |||
| fun getInventoryLotDetailsByStockTakeSection( | |||
| @RequestParam stockTakeSection: String, | |||
| @RequestParam(required = false) stockTakeId: Long? | |||
| ): List<InventoryLotDetailResponse> { | |||
| return stockOutRecordService.getInventoryLotDetailsByStockTakeSection(stockTakeSection, stockTakeId) | |||
| } | |||
| fun getInventoryLotDetailsByStockTakeSection( | |||
| @RequestParam stockTakeSection: String, | |||
| @RequestParam(required = false) stockTakeId: Long?, | |||
| @RequestParam(required = false, defaultValue = "0") pageNum: Int, | |||
| @RequestParam(required = false, defaultValue = "10") pageSize: Int | |||
| ): RecordsRes<InventoryLotDetailResponse> { | |||
| return stockOutRecordService.getInventoryLotDetailsByStockTakeSection(stockTakeSection, stockTakeId, pageNum, pageSize) | |||
| } | |||
| @PostMapping("/saveStockTakeRecord") | |||
| fun saveStockTakeRecord( | |||
| @@ -177,4 +189,27 @@ class StockTakeRecordController( | |||
| )) | |||
| } | |||
| } | |||
| @GetMapping("/searchStockTransactions") | |||
| fun searchStockTransactions( | |||
| @RequestParam(required = false) itemCode: String?, | |||
| @RequestParam(required = false) itemName: String?, | |||
| @RequestParam(required = false) type: String?, | |||
| @RequestParam(required = false) startDate: LocalDate?, | |||
| @RequestParam(required = false) endDate: LocalDate?, | |||
| @RequestParam(required = false, defaultValue = "0") pageNum: Int, | |||
| @RequestParam(required = false, defaultValue = "100") pageSize: Int | |||
| ): RecordsRes<StockTransactionResponse> { | |||
| val request = SearchStockTransactionRequest( | |||
| startDate = startDate, | |||
| endDate = endDate, | |||
| itemCode = itemCode, | |||
| itemName = itemName, | |||
| type = type, | |||
| pageNum = pageNum, | |||
| pageSize = pageSize | |||
| ) | |||
| return stockOutRecordService.searchStockTransactions(request) | |||
| } | |||
| } | |||
| @@ -70,3 +70,19 @@ data class SaveInventoryLotLineForSil ( | |||
| val qty: BigDecimal, | |||
| val warehouseId: Long? | |||
| ) | |||
| data class NewStockInCreateRequest( | |||
| val inventoryLotLineId: Long, | |||
| val itemId: Long, | |||
| val itemNo: String, | |||
| val demandQty: BigDecimal, | |||
| val acceptedQty: BigDecimal, | |||
| val expiryDate: LocalDate, | |||
| val lotNo: String?, | |||
| val stockIntype: String, | |||
| val stockInLineType: String, | |||
| val warehouseId: Long, | |||
| val stockUomId: Long, | |||
| ) | |||
| @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonFormat | |||
| import java.time.LocalDate | |||
| import java.math.BigDecimal | |||
| import java.time.LocalDateTime | |||
| import com.ffii.core.response.RecordsRes | |||
| data class AllPickedStockTakeListReponse( | |||
| val id: Long, | |||
| val stockTakeSession: String, | |||
| @@ -98,4 +100,34 @@ data class BatchSaveApproverStockTakeRecordResponse( | |||
| val successCount: Int, | |||
| val errorCount: Int, | |||
| val errors: List<String> | |||
| ) | |||
| data class SearchStockTransactionRequest( | |||
| val startDate: LocalDate? = null, | |||
| val endDate: LocalDate? = null, | |||
| val itemCode: String? = null, | |||
| val itemName: String? = null, | |||
| val type: String? = null, | |||
| val pageNum: Int = 0, | |||
| val pageSize: Int = 100 | |||
| ) | |||
| data class StockTransactionResponse( | |||
| val id: Long, | |||
| val transactionType: String, | |||
| val itemId: Long, | |||
| val itemCode: String?, | |||
| val itemName: String?, | |||
| val balanceQty: BigDecimal? = null, | |||
| val qty: BigDecimal, | |||
| val type: String?, | |||
| val date: LocalDate?, | |||
| val status: String, | |||
| val transactionDate: LocalDateTime?, | |||
| val lotNo: String?, | |||
| val stockInId: Long?, | |||
| val stockOutId: Long?, | |||
| val remarks: String? | |||
| ) | |||
| data class StockTransactionListResponse( | |||
| val records: RecordsRes<StockTransactionResponse> | |||
| ) | |||
| @@ -0,0 +1,7 @@ | |||
| --liquibase formatted sql | |||
| --changeset author:add_time_fields_to_productprocessline | |||
| ALTER TABLE `stock_out_line` | |||
| ADD COLUMN `startTime` DATETIME NULL AFTER `type`, | |||
| ADD COLUMN `endTime` DATETIME NULL AFTER `startTime`; | |||
| @@ -0,0 +1,8 @@ | |||
| --liquibase formatted sql | |||
| --changeset author:add_type_and_item_id_to_stock_ledger | |||
| ALTER TABLE `stock_ledger` | |||
| ADD COLUMN `type` varchar(255) NULL AFTER `balance`, | |||
| ADD COLUMN `itemId` INT NULL AFTER `inventoryId`, | |||
| ADD COLUMN `itemCode` varchar(255) NULL AFTER `itemId`; | |||
| @@ -0,0 +1,7 @@ | |||
| --liquibase formatted sql | |||
| --changeset author:add_type_and_item_id_to_stock_ledger | |||
| ALTER TABLE `stock_ledger` | |||
| ADD COLUMN `date` varchar(255) NULL AFTER `deleted`; | |||
| @@ -0,0 +1,7 @@ | |||
| --liquibase formatted sql | |||
| --changeset author:add_type_and_item_id_to_stock_ledger | |||
| ALTER TABLE `stock_out` | |||
| ADD COLUMN `stockTakeId` INT NULL AFTER `remarks`; | |||