| @@ -510,6 +510,7 @@ open class DeliveryOrderService( | |||||
| this.item = pickOrderLine.item | this.item = pickOrderLine.item | ||||
| this.status = StockOutLineStatus.PENDING.status | this.status = StockOutLineStatus.PENDING.status | ||||
| this.qty = 0.0 | this.qty = 0.0 | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| stockOutLineRepository.save(line) | stockOutLineRepository.save(line) | ||||
| } | } | ||||
| @@ -1178,6 +1179,7 @@ open class DeliveryOrderService( | |||||
| StockOutLineStatus.PENDING.status // 有正常库存批次时使用 PENDING | StockOutLineStatus.PENDING.status // 有正常库存批次时使用 PENDING | ||||
| } | } | ||||
| this.qty = 0.0 | this.qty = 0.0 | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| stockOutLineRepository.save(line) | stockOutLineRepository.save(line) | ||||
| } | } | ||||
| @@ -96,7 +96,7 @@ class DoPickOrderController( | |||||
| return doReleaseCoordinatorService.getBatchReleaseProgress(jobId) | return doReleaseCoordinatorService.getBatchReleaseProgress(jobId) | ||||
| } | } | ||||
| @GetMapping("/ticket-release-table/{startDate}/{endDate}") | |||||
| @GetMapping("/ticket-release-table/{startDate}&{endDate}") | |||||
| fun getTicketReleaseTable( | fun getTicketReleaseTable( | ||||
| @PathVariable startDate: LocalDate, | @PathVariable startDate: LocalDate, | ||||
| @PathVariable endDate: 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.enum.StockInLineStatus | ||||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.MaterialPickStatusItem | |||||
| @Service | @Service | ||||
| open class JoPickOrderService( | open class JoPickOrderService( | ||||
| private val joPickOrderRepository: JoPickOrderRepository, | private val joPickOrderRepository: JoPickOrderRepository, | ||||
| @@ -2249,5 +2250,98 @@ open fun deleteJoPickOrderJobOrderProductProcessPickOrder(jobOrderId: Long): Mes | |||||
| errorPosition = null | 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.item = pickOrderLine.item | ||||
| this.status = StockOutLineStatus.PENDING.status | this.status = StockOutLineStatus.PENDING.status | ||||
| this.qty = 0.0 | this.qty = 0.0 | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| stockOutLineRepository.save(line) | 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.JobOrderInfo | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName | 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.ExportFGStockInLabelRequest | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.MaterialPickStatusItem | |||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| @RestController | @RestController | ||||
| @@ -320,4 +321,8 @@ fun checkJobOrderCreated(@Valid @RequestBody request: CheckJobOrderCreatedReques | |||||
| fun updateJoReqQty(@Valid @RequestBody request: UpdateJoReqQtyRequest): MessageResponse { | fun updateJoReqQty(@Valid @RequestBody request: UpdateJoReqQtyRequest): MessageResponse { | ||||
| return jobOrderService.updateJoReqQty(request) | 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 pageNum: Int?, | ||||
| val jobTypeName: String?, | 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 | ORDER BY p.created DESC | ||||
| """) | """) | ||||
| fun getBadItemList(): List<PickExecutionIssue> | 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 findAllByPickOrderId(@Param("pickOrderId") pickOrderId: Long): List<PickOrderLine> | ||||
| fun findAllByPickOrderIdAndDeletedFalse(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 = | this.status = | ||||
| com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status | com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status | ||||
| this.qty = 0.0 | this.qty = 0.0 | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| stockOutLIneRepository.save(line) | stockOutLIneRepository.save(line) | ||||
| precreated++ | precreated++ | ||||
| @@ -1945,6 +1946,7 @@ open class PickOrderService( | |||||
| this.status = | this.status = | ||||
| com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status | com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus.PENDING.status | ||||
| this.qty = 0.0 | this.qty = 0.0 | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| stockOutLIneRepository.save(line) | stockOutLIneRepository.save(line) | ||||
| precreated++ | precreated++ | ||||
| @@ -1587,5 +1587,45 @@ open class ProductProcessService( | |||||
| message = "ProductProcess Line Created successfully with seqNo ${originalSeqNo + 1}", | message = "ProductProcess Line Created successfully with seqNo ${originalSeqNo + 1}", | ||||
| errorPosition = null, | 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 { | fun updateProductProcessLineProcessingTimeSetupTimeChangeoverTime(@PathVariable lineId: Long, @RequestBody request: UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest): MessageResponse { | ||||
| return productProcessService.UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTime(request) | 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 processingTime: Int?, | ||||
| val setupTime: Int?, | val setupTime: Int?, | ||||
| val changeoverTime: 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 | AND ill.deleted = false | ||||
| """) | """) | ||||
| fun countDistinctItemsByWarehouseIds(@Param("warehouseIds") warehouseIds: List<Long>): Long | 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 org.springframework.stereotype.Repository | ||||
| import java.util.Optional | import java.util.Optional | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| import org.springframework.data.repository.query.Param | |||||
| @Repository | @Repository | ||||
| interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | ||||
| fun findAllStockInLineInfoByStockInIdAndDeletedFalse(stockInId: Long): List<StockInLineInfo> | fun findAllStockInLineInfoByStockInIdAndDeletedFalse(stockInId: Long): List<StockInLineInfo> | ||||
| @@ -31,4 +34,25 @@ interface StockInLineRepository : AbstractRepository<StockInLine, Long> { | |||||
| AND sil.deleted = false | AND sil.deleted = false | ||||
| """) | """) | ||||
| fun findByReceiptDateAndDeletedFalse(date: LocalDate): List<StockInLine> | 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 | 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? | fun findLatestCodeByPrefix(prefix: String): String? | ||||
| } | } | ||||
| @@ -7,13 +7,13 @@ import jakarta.persistence.JoinColumn | |||||
| import jakarta.persistence.OneToOne | import jakarta.persistence.OneToOne | ||||
| import jakarta.persistence.Table | import jakarta.persistence.Table | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| import java.time.LocalDate | |||||
| @Entity | @Entity | ||||
| @Table(name = "stock_ledger") | @Table(name = "stock_ledger") | ||||
| open class StockLedger: BaseEntity<Long>() { | open class StockLedger: BaseEntity<Long>() { | ||||
| // @OneToOne | |||||
| // @JoinColumn(name = "stockInLineId") | |||||
| // open var stockInLine: StockInLine? = null | |||||
| @OneToOne | |||||
| @JoinColumn(name = "stockInLineId") | |||||
| open var stockInLine: StockInLine? = null | |||||
| @OneToOne | @OneToOne | ||||
| @JoinColumn(name = "stockOutLineId") | @JoinColumn(name = "stockOutLineId") | ||||
| @@ -32,4 +32,12 @@ open class StockLedger: BaseEntity<Long>() { | |||||
| @Column(name = "balance") | @Column(name = "balance") | ||||
| open var balance: Double? = null | 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 | package com.ffii.fpsms.modules.stock.entity | ||||
| import com.ffii.core.support.AbstractRepository | 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 org.springframework.stereotype.Repository | ||||
| import java.time.LocalDate | |||||
| @Repository | @Repository | ||||
| interface StockLedgerRepository: AbstractRepository<StockLedger, Long> { | 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") | @Column(name = "remarks") | ||||
| open var remarks: String? = null | 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.entity.projection.StockOutLineInfo | ||||
| import com.ffii.fpsms.modules.stock.web.model.StockOutStatus | import com.ffii.fpsms.modules.stock.web.model.StockOutStatus | ||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import org.springframework.data.repository.query.Param | |||||
| import java.time.LocalDate | |||||
| import org.springframework.data.jpa.repository.Query | |||||
| @Repository | @Repository | ||||
| interface StockOutLIneRepository: AbstractRepository<StockOutLine, Long> { | interface StockOutLIneRepository: AbstractRepository<StockOutLine, Long> { | ||||
| @@ -21,5 +24,27 @@ interface StockOutLIneRepository: AbstractRepository<StockOutLine, Long> { | |||||
| inventoryLotLineId: Long | inventoryLotLineId: Long | ||||
| ): List<StockOutLine> | ): List<StockOutLine> | ||||
| fun existsByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse(pickOrderLineId: Long, inventoryLotLineId: Long): Boolean | 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") | @Column(name = "type") | ||||
| open var type: String? = null | 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 | @Repository | ||||
| interface StockOutRepository: AbstractRepository<StockOut, Long> { | interface StockOutRepository: AbstractRepository<StockOut, Long> { | ||||
| fun findByConsoPickOrderCode(consoPickOrderCode: String) : Optional<StockOut> | 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 org.springframework.stereotype.Repository | ||||
| import java.io.Serializable | import java.io.Serializable | ||||
| import org.springframework.data.jpa.repository.Query | import org.springframework.data.jpa.repository.Query | ||||
| import org.springframework.data.repository.query.Param | |||||
| import java.time.LocalDate | |||||
| @Repository | @Repository | ||||
| interface StockTakeRecordRepository : AbstractRepository<StockTakeRecord, Long> { | interface StockTakeRecordRepository : AbstractRepository<StockTakeRecord, Long> { | ||||
| fun findAllByStockTakeIdAndDeletedIsFalse(stockTakeId: Long): List<StockTakeRecord>; | fun findAllByStockTakeIdAndDeletedIsFalse(stockTakeId: Long): List<StockTakeRecord>; | ||||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeRecord?; | fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeRecord?; | ||||
| @Query(""" | @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 kotlin.jvm.optionals.getOrNull | ||||
| import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | import com.ffii.fpsms.modules.deliveryOrder.entity.DeliveryOrderRepository | ||||
| import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintQrCodeForDoRequest | 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 | @Serializable | ||||
| data class QrContent(val itemId: Long, val stockInLineId: Long) | data class QrContent(val itemId: Long, val stockInLineId: Long) | ||||
| @@ -72,7 +74,10 @@ open class StockInLineService( | |||||
| private val itemUomRepository: ItemUomRespository, | private val itemUomRepository: ItemUomRespository, | ||||
| private val printerService: PrinterService, | private val printerService: PrinterService, | ||||
| private val stockTakeLineRepository: StockTakeLineRepository, | private val stockTakeLineRepository: StockTakeLineRepository, | ||||
| private val inventoryLotLineService: InventoryLotLineService, | |||||
| private val deliveryOrderRepository: DeliveryOrderRepository, | private val deliveryOrderRepository: DeliveryOrderRepository, | ||||
| private val stockLedgerRepository: StockLedgerRepository, | |||||
| private val inventoryRepository: InventoryRepository | |||||
| ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | ||||
| open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | ||||
| @@ -113,7 +118,7 @@ open class StockInLineService( | |||||
| // stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | // stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | ||||
| // var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | // var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | ||||
| } | } | ||||
| createStockLedgerForStockIn(stockInLine) | |||||
| val item = itemRepository.findById(request.itemId).orElseThrow() | val item = itemRepository.findById(request.itemId).orElseThrow() | ||||
| // If request contains valid POL | // If request contains valid POL | ||||
| if (pol != null) { | if (pol != null) { | ||||
| @@ -153,6 +158,7 @@ open class StockInLineService( | |||||
| // if (pol.qty!! < request.acceptedQty) { | // if (pol.qty!! < request.acceptedQty) { | ||||
| // throw BadRequestException() | // throw BadRequestException() | ||||
| // } | // } | ||||
| stockInLine.apply { | stockInLine.apply { | ||||
| this.item = item | this.item = item | ||||
| itemNo = item.code | itemNo = item.code | ||||
| @@ -162,6 +168,7 @@ open class StockInLineService( | |||||
| if (jo != null && jo?.bom != null) { | if (jo != null && jo?.bom != null) { | ||||
| // For job orders, demandQty comes from BOM's outputQty | // For job orders, demandQty comes from BOM's outputQty | ||||
| this.demandQty = jo?.bom?.outputQty | this.demandQty = jo?.bom?.outputQty | ||||
| } else if (pol != null) { | } else if (pol != null) { | ||||
| // For purchase orders, demandQty comes from PurchaseOrderLine's qty | // For purchase orders, demandQty comes from PurchaseOrderLine's qty | ||||
| this.demandQty = pol.qty | this.demandQty = pol.qty | ||||
| @@ -169,6 +176,7 @@ open class StockInLineService( | |||||
| dnNo = request.dnNo | dnNo = request.dnNo | ||||
| receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now() | receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now() | ||||
| productLotNo = request.productLotNo | productLotNo = request.productLotNo | ||||
| this.type = "Nor" | |||||
| status = StockInLineStatus.PENDING.status | status = StockInLineStatus.PENDING.status | ||||
| } | } | ||||
| val savedSIL = saveAndFlush(stockInLine) | 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? | // 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 | // return list of stock in line, update data grid with the list | ||||
| createStockLedgerForStockIn(stockInLine) | |||||
| stockInLine.apply { | stockInLine.apply { | ||||
| this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate | this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate | ||||
| this.productLotNo = request.productLotNo ?: this.productLotNo | 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 com.ffii.fpsms.modules.common.CodeGenerator | ||||
| import org.springframework.context.annotation.Lazy | import org.springframework.context.annotation.Lazy | ||||
| import com.ffii.fpsms.modules.bag.service.BagService | 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 | import java.time.LocalTime | ||||
| @Service | @Service | ||||
| @@ -46,7 +48,6 @@ open class StockOutLineService( | |||||
| private val stockOutRepository: StockOutRepository, | private val stockOutRepository: StockOutRepository, | ||||
| private val stockOutLineRepository: StockOutLIneRepository, | private val stockOutLineRepository: StockOutLIneRepository, | ||||
| private val itemRepository: ItemsRepository, | private val itemRepository: ItemsRepository, | ||||
| private val inventoryRepository: InventoryRepository, | |||||
| private val itemUomRespository: ItemUomRespository, | private val itemUomRespository: ItemUomRespository, | ||||
| private val pickOrderRepository: PickOrderRepository, | private val pickOrderRepository: PickOrderRepository, | ||||
| private val inventoryLotLineRepository: InventoryLotLineRepository, | private val inventoryLotLineRepository: InventoryLotLineRepository, | ||||
| @@ -59,7 +60,9 @@ private val deliveryOrderRepository: DeliveryOrderRepository, | |||||
| private val doPickOrderLineRepository: DoPickOrderLineRepository, | private val doPickOrderLineRepository: DoPickOrderLineRepository, | ||||
| private val doPickOrderLineRecordRepository: DoPickOrderLineRecordRepository, | private val doPickOrderLineRecordRepository: DoPickOrderLineRecordRepository, | ||||
| private val inventoryLotLineService: InventoryLotLineService, | 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) { | ): AbstractBaseEntityService<StockOutLine, Long, StockOutLIneRepository>(jdbcDao, stockOutLineRepository) { | ||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| @Transactional | @Transactional | ||||
| @@ -135,6 +138,7 @@ val existingStockOutLine = stockOutLineRepository.findByPickOrderLineIdAndInvent | |||||
| ) | ) | ||||
| val item = itemRepository.findById(pickOrderLine.item!!.id!!).orElseThrow() | val item = itemRepository.findById(pickOrderLine.item!!.id!!).orElseThrow() | ||||
| val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() | ||||
| val stockOutLine = StockOutLine() | val stockOutLine = StockOutLine() | ||||
| .apply { | .apply { | ||||
| this.item = item | this.item = item | ||||
| @@ -143,8 +147,10 @@ val existingStockOutLine = stockOutLineRepository.findByPickOrderLineIdAndInvent | |||||
| this.inventoryLotLine = inventoryLotLine | this.inventoryLotLine = inventoryLotLine | ||||
| this.pickOrderLine = pickOrderLine | this.pickOrderLine = pickOrderLine | ||||
| this.status = StockOutLineStatus.PENDING.status | this.status = StockOutLineStatus.PENDING.status | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| val savedStockOutLine = saveAndFlush(stockOutLine) | val savedStockOutLine = saveAndFlush(stockOutLine) | ||||
| createStockLedgerForStockOut(savedStockOutLine) | |||||
| val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) | val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(savedStockOutLine.id!!) | ||||
| // println("triggering") | // println("triggering") | ||||
| return MessageResponse( | return MessageResponse( | ||||
| @@ -231,7 +237,7 @@ open fun createWithoutConso(request: CreateStockOutLineWithoutConsoRequest): Mes | |||||
| IllegalArgumentException("InventoryLotLine not found with ID: ${request.inventoryLotLineId}") | IllegalArgumentException("InventoryLotLine not found with ID: ${request.inventoryLotLineId}") | ||||
| } | } | ||||
| println("Found inventoryLotLine: ${inventoryLotLine.id}") | println("Found inventoryLotLine: ${inventoryLotLine.id}") | ||||
| val stockOutLine = StockOutLine() | val stockOutLine = StockOutLine() | ||||
| .apply { | .apply { | ||||
| this.item = item | this.item = item | ||||
| @@ -240,10 +246,12 @@ open fun createWithoutConso(request: CreateStockOutLineWithoutConsoRequest): Mes | |||||
| this.inventoryLotLine = inventoryLotLine | this.inventoryLotLine = inventoryLotLine | ||||
| this.pickOrderLine = updatedPickOrderLine | this.pickOrderLine = updatedPickOrderLine | ||||
| this.status = StockOutLineStatus.PENDING.status | this.status = StockOutLineStatus.PENDING.status | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| println("Created stockOutLine with qty: ${request.qty}") | println("Created stockOutLine with qty: ${request.qty}") | ||||
| val savedStockOutLine = saveAndFlush(stockOutLine) | val savedStockOutLine = saveAndFlush(stockOutLine) | ||||
| createStockLedgerForStockOut(savedStockOutLine) | |||||
| println("Saved stockOutLine with ID: ${savedStockOutLine.id}") | println("Saved stockOutLine with ID: ${savedStockOutLine.id}") | ||||
| val mappedSavedStockOutLine = stockOutLineRepository.findStockOutLineInfoById(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("Updating StockOutLine ID: ${request.id}") | ||||
| println("Current status: ${stockOutLine.status}") | println("Current status: ${stockOutLine.status}") | ||||
| println("New status: ${request.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 | // 2. 更新自身 status/qty | ||||
| stockOutLine.status = request.status | stockOutLine.status = request.status | ||||
| if (request.qty != null) { | if (request.qty != null) { | ||||
| @@ -582,14 +595,14 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { | |||||
| bagService.createBagLotLinesByBagId(createBagLotLineRequest) | bagService.createBagLotLinesByBagId(createBagLotLineRequest) | ||||
| println(" ✓ BagLotLine created successfully for item ${item.code}") | println(" ✓ BagLotLine created successfully for item ${item.code}") | ||||
| } else { | } else { | ||||
| println(" ⚠️ Warning: lotNo is null, skipping BagLotLine creation") | |||||
| println(" Warning: lotNo is null, skipping BagLotLine creation") | |||||
| } | } | ||||
| } else { | } 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) { | } catch (e: Exception) { | ||||
| println(" ⚠️ Error creating BagLotLine: ${e.message}") | |||||
| println(" Error creating BagLotLine: ${e.message}") | |||||
| e.printStackTrace() | e.printStackTrace() | ||||
| // 不中断主流程,只记录错误 | // 不中断主流程,只记录错误 | ||||
| } | } | ||||
| @@ -891,7 +904,7 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta | |||||
| ) | ) | ||||
| } | } | ||||
| println("🔍 Checking inventory lot:") | |||||
| println(" Checking inventory lot:") | |||||
| println(" - Found inventory lot:") | println(" - Found inventory lot:") | ||||
| println(" - Lot No: ${inventoryLot.lotNo}") | println(" - Lot No: ${inventoryLot.lotNo}") | ||||
| println(" - Item ID: ${inventoryLot.item?.id}") | 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})") | println(" - Item ID match: $itemIdMatch (Expected: ${inventoryLot.item?.id}, Got: ${request.itemId})") | ||||
| if (lotNoMatch && itemIdMatch) { | if (lotNoMatch && itemIdMatch) { | ||||
| // 匹配成功,更新状态 | |||||
| println(" MATCH SUCCESS: Lot and Item both match!") | println(" MATCH SUCCESS: Lot and Item both match!") | ||||
| stockOutLine.status = request.status | 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) | val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) | ||||
| println(" Status updated successfully:") | println(" Status updated successfully:") | ||||
| @@ -1099,14 +1118,14 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { | |||||
| bagService.createBagLotLinesByBagId(createBagLotLineRequest) | bagService.createBagLotLinesByBagId(createBagLotLineRequest) | ||||
| println(" ✓ BagLotLine created successfully for item ${item.code}") | println(" ✓ BagLotLine created successfully for item ${item.code}") | ||||
| } else { | } else { | ||||
| println(" ⚠️ Warning: lotNo is null, skipping BagLotLine creation") | |||||
| println(" Warning: lotNo is null, skipping BagLotLine creation") | |||||
| } | } | ||||
| } else { | } 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) { | } catch (e: Exception) { | ||||
| println(" ⚠️ Error creating BagLotLine: ${e.message}") | |||||
| println(" Error creating BagLotLine: ${e.message}") | |||||
| e.printStackTrace() | 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.time.LocalDateTime | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import com.ffii.fpsms.modules.user.entity.UserRepository | 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.service.InventoryLotLineService | ||||
| import com.ffii.fpsms.modules.stock.entity.StockTakeLine | import com.ffii.fpsms.modules.stock.entity.StockTakeLine | ||||
| import com.ffii.fpsms.modules.stock.entity.StockTakeLineRepository | 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.StockInLineRepository | ||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLot | import com.ffii.fpsms.modules.stock.entity.InventoryLot | ||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | 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 | @Service | ||||
| class StockTakeRecordService( | class StockTakeRecordService( | ||||
| val stockTakeRepository: StockTakeRepository, | val stockTakeRepository: StockTakeRepository, | ||||
| @@ -42,7 +54,10 @@ class StockTakeRecordService( | |||||
| val stockOutLineRepository: StockOutLIneRepository, | val stockOutLineRepository: StockOutLIneRepository, | ||||
| val stockInRepository: StockInRepository, | val stockInRepository: StockInRepository, | ||||
| val stockInLineRepository: StockInLineRepository, | val stockInLineRepository: StockInLineRepository, | ||||
| val inventoryLotRepository: InventoryLotRepository | |||||
| val inventoryLotRepository: InventoryLotRepository, | |||||
| val stockLedgerRepository: StockLedgerRepository, | |||||
| val inventoryRepository: InventoryRepository | |||||
| ) { | ) { | ||||
| private val logger: Logger = LoggerFactory.getLogger(StockTakeRecordService::class.java) | private val logger: Logger = LoggerFactory.getLogger(StockTakeRecordService::class.java) | ||||
| @@ -138,6 +153,7 @@ class StockTakeRecordService( | |||||
| } | } | ||||
| // 9. 计算 TotalItemNumber:获取该 section 下所有 InventoryLotLine,按 item 分组,计算不同的 item 数量 | // 9. 计算 TotalItemNumber:获取该 section 下所有 InventoryLotLine,按 item 分组,计算不同的 item 数量 | ||||
| val totalItemNumber = inventoryLotLineRepository.countDistinctItemsByWarehouseIds(warehouseIds).toInt() | val totalItemNumber = inventoryLotLineRepository.countDistinctItemsByWarehouseIds(warehouseIds).toInt() | ||||
| val totalInventoryLotNumber = inventoryLotLineRepository.countAllByWarehouseIds(warehouseIds).toInt() | |||||
| // 8. 使用 stockTakeSection 作为 stockTakeSession | // 8. 使用 stockTakeSection 作为 stockTakeSession | ||||
| val reStockTakeTrueFalse = if (latestStockTake != null) { | val reStockTakeTrueFalse = if (latestStockTake != null) { | ||||
| // 检查该 stock take 下该 section 的记录中是否有 notMatch 状态 | // 检查该 stock take 下该 section 的记录中是否有 notMatch 状态 | ||||
| @@ -158,7 +174,7 @@ class StockTakeRecordService( | |||||
| lastStockTakeDate = latestStockTake?.actualStart?.toLocalDate(), | lastStockTakeDate = latestStockTake?.actualStart?.toLocalDate(), | ||||
| status = status?:"", | status = status?:"", | ||||
| currentStockTakeItemNumber = 0, | currentStockTakeItemNumber = 0, | ||||
| totalInventoryLotNumber = 0, | |||||
| totalInventoryLotNumber = totalInventoryLotNumber, | |||||
| stockTakeId = latestStockTake?.id ?: 0, | stockTakeId = latestStockTake?.id ?: 0, | ||||
| stockTakerName = stockTakerName, | stockTakerName = stockTakerName, | ||||
| TotalItemNumber = totalItemNumber, | TotalItemNumber = totalItemNumber, | ||||
| @@ -286,6 +302,7 @@ class StockTakeRecordService( | |||||
| } | } | ||||
| // 9. 计算 TotalItemNumber:获取该 section 下所有 InventoryLotLine,按 item 分组,计算不同的 item 数量 | // 9. 计算 TotalItemNumber:获取该 section 下所有 InventoryLotLine,按 item 分组,计算不同的 item 数量 | ||||
| val totalItemNumber = inventoryLotLineRepository.countDistinctItemsByWarehouseIds(warehouseIds).toInt() | val totalItemNumber = inventoryLotLineRepository.countDistinctItemsByWarehouseIds(warehouseIds).toInt() | ||||
| val totalInventoryLotNumber = inventoryLotLineRepository.countAllByWarehouseIds(warehouseIds).toInt() | |||||
| // 9. 使用 stockTakeSection 作为 stockTakeSession | // 9. 使用 stockTakeSection 作为 stockTakeSession | ||||
| result.add( | result.add( | ||||
| AllPickedStockTakeListReponse( | AllPickedStockTakeListReponse( | ||||
| @@ -294,7 +311,7 @@ class StockTakeRecordService( | |||||
| lastStockTakeDate = latestStockTake?.actualStart?.toLocalDate(), | lastStockTakeDate = latestStockTake?.actualStart?.toLocalDate(), | ||||
| status = status?:"", | status = status?:"", | ||||
| currentStockTakeItemNumber = 0, // 临时设为 0,测试性能 | currentStockTakeItemNumber = 0, // 临时设为 0,测试性能 | ||||
| totalInventoryLotNumber = 0, // 临时设为 0,测试性能 | |||||
| totalInventoryLotNumber = totalInventoryLotNumber, // 临时设为 0,测试性能 | |||||
| stockTakeId = latestStockTake?.id ?: 0, | stockTakeId = latestStockTake?.id ?: 0, | ||||
| stockTakerName = stockTakerName, | stockTakerName = stockTakerName, | ||||
| approverName = approverName, | 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( | open fun saveStockTakeRecord( | ||||
| request: SaveStockTakeRecordRequest, | request: SaveStockTakeRecordRequest, | ||||
| stockTakeId: Long, | stockTakeId: Long, | ||||
| @@ -750,30 +778,32 @@ open fun checkAndUpdateStockTakeStatus(stockTakeId: Long, stockTakeSection: Stri | |||||
| val allRecordsCompleted = stockTakeRecords.isNotEmpty() && | val allRecordsCompleted = stockTakeRecords.isNotEmpty() && | ||||
| stockTakeRecords.all { it.status == "completed" } | stockTakeRecords.all { it.status == "completed" } | ||||
| // 6. 如果所有记录都已创建且都是 "pass",更新 stock take 状态为 "approving" | // 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.status = StockTakeStatus.APPROVING | ||||
| stockTake.actualEnd = java.time.LocalDateTime.now() | stockTake.actualEnd = java.time.LocalDateTime.now() | ||||
| stockTakeRepository.save(stockTake) | stockTakeRepository.save(stockTake) | ||||
| println("Stock take $stockTakeId status updated to APPROVING - all records are pass") | println("Stock take $stockTakeId status updated to APPROVING - all records are pass") | ||||
| return mapOf( | return mapOf( | ||||
| "success" to true, | "success" to true, | ||||
| "message" to "Stock take status updated to APPROVING", | "message" to "Stock take status updated to APPROVING", | ||||
| "updated" to true | "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 { | } else { | ||||
| return mapOf( | return mapOf( | ||||
| "success" to true, | "success" to true, | ||||
| "message" to "Conditions not met for status update", | "message" to "Conditions not met for status update", | ||||
| "updated" to false, | "updated" to false, | ||||
| "allLinesHaveRecords" to allLinesHaveRecords, | "allLinesHaveRecords" to allLinesHaveRecords, | ||||
| "allRecordsPassed" to allRecordsPassed | |||||
| "allRecordsPassed" to allRecordsPassed, | |||||
| "allRecordsCompleted" to allRecordsCompleted | |||||
| ) | ) | ||||
| } | } | ||||
| } catch (e: Exception) { | } catch (e: Exception) { | ||||
| @@ -835,6 +865,8 @@ open fun saveApproverStockTakeRecord( | |||||
| val inventoryLot = inventoryLotRepository.findByIdAndDeletedFalse( | val inventoryLot = inventoryLotRepository.findByIdAndDeletedFalse( | ||||
| inventoryLotLine?.inventoryLot?.id ?: throw IllegalArgumentException("Inventory lot ID not found") | inventoryLotLine?.inventoryLot?.id ?: throw IllegalArgumentException("Inventory lot ID not found") | ||||
| ) ?: throw IllegalArgumentException("Inventory lot 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 ) { | if (varianceQty !=BigDecimal.ZERO ) { | ||||
| val stockTakeLine = StockTakeLine().apply { | val stockTakeLine = StockTakeLine().apply { | ||||
| @@ -849,13 +881,14 @@ open fun saveApproverStockTakeRecord( | |||||
| stockTakeLineRepository.save(stockTakeLine) | stockTakeLineRepository.save(stockTakeLine) | ||||
| if (varianceQty < BigDecimal.ZERO ) { | 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 { | val stockOutLine = StockOutLine().apply { | ||||
| this.item=inventoryLot.item | this.item=inventoryLot.item | ||||
| this.qty=(-varianceQty)?.toDouble() | this.qty=(-varianceQty)?.toDouble() | ||||
| @@ -866,21 +899,35 @@ open fun saveApproverStockTakeRecord( | |||||
| } | } | ||||
| stockOutLineRepository.save(stockOutLine) | 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 ) { | 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 { | val stockInLine = StockInLine().apply { | ||||
| this.stockTakeLine=stockTakeLine | this.stockTakeLine=stockTakeLine | ||||
| this.item=inventoryLot.item | this.item=inventoryLot.item | ||||
| this.itemNo=stockTakeRecord.itemCode | this.itemNo=stockTakeRecord.itemCode | ||||
| this.stockIn = stockIn | this.stockIn = stockIn | ||||
| this.demandQty=finalQty | |||||
| this.acceptedQty=finalQty | |||||
| this.demandQty=varianceQty | |||||
| this.acceptedQty=varianceQty | |||||
| this.expiryDate=inventoryLot.expiryDate | this.expiryDate=inventoryLot.expiryDate | ||||
| this.inventoryLot=inventoryLot | this.inventoryLot=inventoryLot | ||||
| this.inventoryLotLine=inventoryLotLine | this.inventoryLotLine=inventoryLotLine | ||||
| @@ -891,9 +938,21 @@ open fun saveApproverStockTakeRecord( | |||||
| } | } | ||||
| stockInLineRepository.save(stockInLine) | 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( | val updateRequest = SaveInventoryLotLineRequest( | ||||
| id = inventoryLotLine.id, | id = inventoryLotLine.id, | ||||
| @@ -925,9 +984,7 @@ open fun batchSaveApproverStockTakeRecords( | |||||
| val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | val stockTake = stockTakeRepository.findByIdAndDeletedIsFalse(request.stockTakeId) | ||||
| ?: throw IllegalArgumentException("Stock take not found: ${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() | val stockTakeRecords = stockTakeRecordRepository.findAll() | ||||
| .filter { | .filter { | ||||
| !it.deleted && | !it.deleted && | ||||
| @@ -982,7 +1039,7 @@ open fun batchSaveApproverStockTakeRecords( | |||||
| this.approverName = user?.name | this.approverName = user?.name | ||||
| this.approverStockTakeQty = qty | this.approverStockTakeQty = qty | ||||
| this.approverBadQty = badQty | this.approverBadQty = badQty | ||||
| this.varianceQty = varianceQty // 应该是 0 | |||||
| this.varianceQty = varianceQty | |||||
| this.status = "completed" | this.status = "completed" | ||||
| } | } | ||||
| @@ -1023,13 +1080,18 @@ open fun updateStockTakeRecordStatusToNotMatch(stockTakeRecordId: Long): StockTa | |||||
| return stockTakeRecordRepository.save(stockTakeRecord) | 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) | val warehouses = warehouseRepository.findAllByStockTakeSectionAndDeletedIsFalse(stockTakeSection) | ||||
| if (warehouses.isEmpty()) { | if (warehouses.isEmpty()) { | ||||
| logger.warn("No warehouses found for stockTakeSection: $stockTakeSection") | logger.warn("No warehouses found for stockTakeSection: $stockTakeSection") | ||||
| return emptyList() | |||||
| return RecordsRes(emptyList(), 0) | |||||
| } | } | ||||
| val warehouseIds = warehouses.mapNotNull { it.id } | val warehouseIds = warehouses.mapNotNull { it.id } | ||||
| @@ -1055,7 +1117,7 @@ open fun getInventoryLotDetailsByStockTakeSectionNotMatch(stockTakeSection: Stri | |||||
| } | } | ||||
| // 只返回有 notMatch 记录的 inventory lot lines | // 只返回有 notMatch 记录的 inventory lot lines | ||||
| return inventoryLotLines.mapNotNull { ill -> | |||||
| val allResults = inventoryLotLines.mapNotNull { ill -> | |||||
| val inventoryLot = ill.inventoryLot | val inventoryLot = ill.inventoryLot | ||||
| val item = inventoryLot?.item | val item = inventoryLot?.item | ||||
| val warehouse = ill.warehouse | val warehouse = ill.warehouse | ||||
| @@ -1115,5 +1177,120 @@ open fun getInventoryLotDetailsByStockTakeSectionNotMatch(stockTakeSection: Stri | |||||
| finalQty = stockTakeLine?.finalQty, | 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.qty = (suggestion.qty ?: BigDecimal.ZERO).toDouble() | ||||
| this.status = StockOutLineStatus.PENDING.status | this.status = StockOutLineStatus.PENDING.status | ||||
| this.deleted = false | this.deleted = false | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| val savedStockOutLine = stockOutLIneRepository.save(stockOutLine) | val savedStockOutLine = stockOutLIneRepository.save(stockOutLine) | ||||
| @@ -529,6 +530,7 @@ open class SuggestedPickLotService( | |||||
| this.inventoryLotLine = suggestedLotLine | this.inventoryLotLine = suggestedLotLine | ||||
| this.pickOrderLine = updatedPickOrderLine | this.pickOrderLine = updatedPickOrderLine | ||||
| this.status = StockOutLineStatus.PENDING.status | this.status = StockOutLineStatus.PENDING.status | ||||
| this.type = "Nor" | |||||
| } | } | ||||
| val savedStockOutLine = stockOutLIneRepository.saveAndFlush(stockOutLine) | val savedStockOutLine = stockOutLIneRepository.saveAndFlush(stockOutLine) | ||||
| @@ -10,7 +10,8 @@ import org.springframework.web.bind.annotation.RequestBody | |||||
| import org.springframework.http.ResponseEntity | import org.springframework.http.ResponseEntity | ||||
| import com.ffii.fpsms.modules.stock.web.model.* | import com.ffii.fpsms.modules.stock.web.model.* | ||||
| import org.slf4j.LoggerFactory | import org.slf4j.LoggerFactory | ||||
| import com.ffii.core.response.RecordsRes | |||||
| import java.time.LocalDate | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/stockTakeRecord") | @RequestMapping("/stockTakeRecord") | ||||
| class StockTakeRecordController( | class StockTakeRecordController( | ||||
| @@ -29,9 +30,18 @@ class StockTakeRecordController( | |||||
| @GetMapping("/inventoryLotDetailsBySectionNotMatch") | @GetMapping("/inventoryLotDetailsBySectionNotMatch") | ||||
| fun getInventoryLotDetailsByStockTakeSectionNotMatch( | fun getInventoryLotDetailsByStockTakeSectionNotMatch( | ||||
| @RequestParam stockTakeSection: String, | @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") | @GetMapping("/inventoryLotDetails") | ||||
| @@ -42,12 +52,14 @@ class StockTakeRecordController( | |||||
| } | } | ||||
| @GetMapping("/inventoryLotDetailsBySection") | @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") | @PostMapping("/saveStockTakeRecord") | ||||
| fun 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 qty: BigDecimal, | ||||
| val warehouseId: Long? | 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.time.LocalDate | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.time.LocalDateTime | import java.time.LocalDateTime | ||||
| import com.ffii.core.response.RecordsRes | |||||
| data class AllPickedStockTakeListReponse( | data class AllPickedStockTakeListReponse( | ||||
| val id: Long, | val id: Long, | ||||
| val stockTakeSession: String, | val stockTakeSession: String, | ||||
| @@ -98,4 +100,34 @@ data class BatchSaveApproverStockTakeRecordResponse( | |||||
| val successCount: Int, | val successCount: Int, | ||||
| val errorCount: Int, | val errorCount: Int, | ||||
| val errors: List<String> | 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`; | |||||