| @@ -4,6 +4,7 @@ import java.time.LocalDate | |||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||
| object CodeGenerator { | object CodeGenerator { | ||||
| // Default Value for Lot No | |||||
| private var dateFormat = DateTimeFormatter.ofPattern("yyMMdd") | private var dateFormat = DateTimeFormatter.ofPattern("yyMMdd") | ||||
| fun generateCode(prefix: String, itemId: Long, count: Int): String { | fun generateCode(prefix: String, itemId: Long, count: Int): String { | ||||
| // prefix = "ITEM" || "LOT" | // prefix = "ITEM" || "LOT" | ||||
| @@ -13,4 +14,33 @@ object CodeGenerator { | |||||
| val countStr = String.format("%04d", count) | val countStr = String.format("%04d", count) | ||||
| return "$prefix-$todayStr$itemStr$countStr" | return "$prefix-$todayStr$itemStr$countStr" | ||||
| } | } | ||||
| // Default Value for Order No | |||||
| val DEFAULT_SUFFIX_FORMAT = "%03d"; | |||||
| val DEFAULT_PATTERN = "yyyyMMdd"; | |||||
| val DEFAULT_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_PATTERN) | |||||
| val DEFAULT_MIDFIX = LocalDate.now().format(DEFAULT_FORMATTER) | |||||
| fun generateOrderNo( | |||||
| suffixFormat: String? = null, | |||||
| pattern: String? = null, | |||||
| prefix: String, | |||||
| midfix: String? = null, | |||||
| latestCode: String? | |||||
| ): String { | |||||
| val formatter = pattern?.let { DateTimeFormatter.ofPattern(it) } ?: DEFAULT_FORMATTER | |||||
| val finalSuffixFormat = suffixFormat ?: DEFAULT_SUFFIX_FORMAT | |||||
| val midfix = midfix ?: LocalDate.now().format(formatter) | |||||
| val suffix = String.format(finalSuffixFormat, 1) | |||||
| if (latestCode != null) { | |||||
| val splitLatestCode = latestCode.split("-") | |||||
| if (splitLatestCode.size > 2) { | |||||
| val latestNo = splitLatestCode[2].toInt() | |||||
| return listOf<String>(prefix, midfix, String.format(finalSuffixFormat, latestNo + 1)).joinToString("-") | |||||
| } | |||||
| } | |||||
| return listOf<String>(prefix, midfix, suffix).joinToString("-") | |||||
| } | |||||
| } | } | ||||
| @@ -93,7 +93,7 @@ open class MailTemplateService( | |||||
| val supplierEmail: String = "", | val supplierEmail: String = "", | ||||
| val supplierName: String = "", | val supplierName: String = "", | ||||
| val dnNo: String = "", | val dnNo: String = "", | ||||
| val dnDate: String = "", | |||||
| val receiptDate: String = "", | |||||
| val poNo: String = "", | val poNo: String = "", | ||||
| val supplierId: String = "", | val supplierId: String = "", | ||||
| val itemNo: String = "", | val itemNo: String = "", | ||||
| @@ -115,7 +115,7 @@ open class MailTemplateService( | |||||
| val args = mapOf( | val args = mapOf( | ||||
| "\${supplierName}" to supplierName, | "\${supplierName}" to supplierName, | ||||
| "\${dnNo}" to dnNo, | "\${dnNo}" to dnNo, | ||||
| "\${dnDate}" to dnDate, | |||||
| "\${receiptDate}" to receiptDate, | |||||
| "\${poNo}" to poNo, | "\${poNo}" to poNo, | ||||
| "\${supplierId}" to supplierId, | "\${supplierId}" to supplierId, | ||||
| "\${itemNo}" to itemNo, | "\${itemNo}" to itemNo, | ||||
| @@ -149,7 +149,7 @@ open class MailTemplateService( | |||||
| val supplierName = po?.supplier?.name ?: "N/A" | val supplierName = po?.supplier?.name ?: "N/A" | ||||
| val supplierEmail = (if((po?.supplier?.contactEmail).isNullOrEmpty()) "N/A" else po?.supplier?.contactEmail) ?: "N/A" | val supplierEmail = (if((po?.supplier?.contactEmail).isNullOrEmpty()) "N/A" else po?.supplier?.contactEmail) ?: "N/A" | ||||
| val dnNo = stockInLine.dnNo ?: "N/A" | val dnNo = stockInLine.dnNo ?: "N/A" | ||||
| val dnDate = formatter.format(stockInLine.dnDate) ?: "N/A" | |||||
| val receiptDate = formatter.format(stockInLine.receiptDate) ?: "N/A" | |||||
| val poNo = po?.code ?: "N/A" | val poNo = po?.code ?: "N/A" | ||||
| val supplierId = po?.supplier?.code ?: "N/A" // Id? | val supplierId = po?.supplier?.code ?: "N/A" // Id? | ||||
| val itemNo = item?.code ?: "N/A" | val itemNo = item?.code ?: "N/A" | ||||
| @@ -193,7 +193,7 @@ open class MailTemplateService( | |||||
| supplierEmail = supplierEmail, | supplierEmail = supplierEmail, | ||||
| supplierName = supplierName, | supplierName = supplierName, | ||||
| dnNo = dnNo, | dnNo = dnNo, | ||||
| dnDate = dnDate, | |||||
| receiptDate = receiptDate, | |||||
| poNo = poNo, | poNo = poNo, | ||||
| supplierId = supplierId, | supplierId = supplierId, | ||||
| itemNo = itemNo, | itemNo = itemNo, | ||||
| @@ -62,6 +62,9 @@ class DoPickOrder { | |||||
| @Column(name = "deleted") | @Column(name = "deleted") | ||||
| var deleted: Boolean = false | var deleted: Boolean = false | ||||
| @Column(name = "hide", nullable = false) | |||||
| var hide: Boolean = false | |||||
| // Default constructor for Hibernate | // Default constructor for Hibernate | ||||
| constructor() | constructor() | ||||
| @@ -2,6 +2,8 @@ package com.ffii.fpsms.modules.jobOrder.entity | |||||
| import com.fasterxml.jackson.annotation.JsonManagedReference | import com.fasterxml.jackson.annotation.JsonManagedReference | ||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatusConverter | |||||
| import com.ffii.fpsms.modules.master.entity.Bom | import com.ffii.fpsms.modules.master.entity.Bom | ||||
| import com.ffii.fpsms.modules.master.entity.ProductionScheduleLine | import com.ffii.fpsms.modules.master.entity.ProductionScheduleLine | ||||
| import com.ffii.fpsms.modules.user.entity.User | import com.ffii.fpsms.modules.user.entity.User | ||||
| @@ -42,10 +44,11 @@ open class JobOrder : BaseEntity<Long>() { | |||||
| @Column(name = "actualQty", precision = 14, scale = 2) | @Column(name = "actualQty", precision = 14, scale = 2) | ||||
| open var actualQty: BigDecimal? = null | open var actualQty: BigDecimal? = null | ||||
| @Size(max = 100) | |||||
| // @Size(max = 100) | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "status", nullable = false, length = 100) | @Column(name = "status", nullable = false, length = 100) | ||||
| open var status: String? = null | |||||
| @Convert(converter = JobOrderStatusConverter::class) | |||||
| open var status: JobOrderStatus? = null | |||||
| @Size(max = 500) | @Size(max = 500) | ||||
| @Column(name = "remarks", length = 500) | @Column(name = "remarks", length = 500) | ||||
| @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository | |||||
| @Repository | @Repository | ||||
| interface JobOrderBomMaterialRepository : AbstractRepository<JobOrderBomMaterial, Long> { | interface JobOrderBomMaterialRepository : AbstractRepository<JobOrderBomMaterial, Long> { | ||||
| fun findByJobOrderIdAndItemId(jobOrderId: Long, itemId: Long): JobOrderBomMaterial?; | |||||
| } | } | ||||
| @@ -20,6 +20,24 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||||
| @Query( | @Query( | ||||
| nativeQuery = true, | nativeQuery = true, | ||||
| value = """ | value = """ | ||||
| with picked_lot_no as ( | |||||
| select | |||||
| pol.itemId, | |||||
| po.joId, | |||||
| json_arrayagg( | |||||
| json_object( | |||||
| 'lotNo', il.lotNo, | |||||
| 'qty', sol.qty | |||||
| ) | |||||
| ) as pickedLotNo | |||||
| from pick_order po | |||||
| left join pick_order_line pol on pol.poId = po.id | |||||
| left join stock_out_line sol on sol.pickOrderLineId = pol.id and sol.status = 'completed' | |||||
| left join inventory_lot_line ill on ill.id = sol.inventoryLotLineId | |||||
| left join inventory_lot il on il.id = ill.inventoryLotId | |||||
| where po.joId = :id and il.lotNo is not null | |||||
| group by pol.itemId, po.joId | |||||
| ) | |||||
| select | select | ||||
| jo.id, | jo.id, | ||||
| jo.code, | jo.code, | ||||
| @@ -32,7 +50,7 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||||
| 'id', jobm.id, | 'id', jobm.id, | ||||
| 'code', i.code, | 'code', i.code, | ||||
| 'name', i.name, | 'name', i.name, | ||||
| 'lotNo', il.lotNo, | |||||
| 'pickedLotNo', pln.pickedLotNo, | |||||
| 'reqQty', jobm.reqQty, | 'reqQty', jobm.reqQty, | ||||
| 'uom', uc.udfudesc, | 'uom', uc.udfudesc, | ||||
| 'status', jobm.status | 'status', jobm.status | ||||
| @@ -46,15 +64,14 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||||
| left join job_order_bom_material jobm on jo.id = jobm.jobOrderId | left join job_order_bom_material jobm on jo.id = jobm.jobOrderId | ||||
| left join items i on i.id = jobm.itemId | left join items i on i.id = jobm.itemId | ||||
| left join uom_conversion uc on uc.id = jobm.uomId | left join uom_conversion uc on uc.id = jobm.uomId | ||||
| left join stock_out_line sol on sol.id = jobm.stockOutLineId | |||||
| left join inventory_lot_line ill on ill.id = sol.inventoryLotLineId | |||||
| left join inventory_lot il on il.id = ill.inventoryLotId | |||||
| left join picked_lot_no pln on pln.itemId = jobm.itemId and pln.joId = jo.id | |||||
| where jo.id = :id | where jo.id = :id | ||||
| group by jo.id, uc2.udfudesc | group by jo.id, uc2.udfudesc | ||||
| limit 1 | limit 1 | ||||
| """ | """ | ||||
| ) | ) | ||||
| fun findJobOrderDetailById(id: Long): JobOrderDetailWithJsonString?; | fun findJobOrderDetailById(id: Long): JobOrderDetailWithJsonString?; | ||||
| @Query( | @Query( | ||||
| nativeQuery = true, | nativeQuery = true, | ||||
| value = """ | value = """ | ||||
| @@ -14,6 +14,8 @@ interface JobOrderInfo { | |||||
| @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}") | @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}") | ||||
| val uom: String; | val uom: String; | ||||
| @get:Value("#{target.status.value}") | |||||
| val status: String; | val status: String; | ||||
| } | } | ||||
| @@ -42,8 +44,13 @@ data class JobOrderDetailPickLine( | |||||
| val id: Long?, | val id: Long?, | ||||
| val code: String?, | val code: String?, | ||||
| val name: String?, | val name: String?, | ||||
| val lotNo: String?, | |||||
| val pickedLotNo: List<JobOrderDetailPickedLotNo>?, | |||||
| val reqQty: BigDecimal?, | val reqQty: BigDecimal?, | ||||
| val uom: String?, | val uom: String?, | ||||
| val status: String? | val status: String? | ||||
| ) | |||||
| data class JobOrderDetailPickedLotNo( | |||||
| val lotNo: String?, | |||||
| val qty: BigDecimal?, | |||||
| ) | ) | ||||
| @@ -0,0 +1,10 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.enums | |||||
| enum class JobOrderStatus(val value: String) { | |||||
| PLANNING("planning"), | |||||
| PENDING("pending"), | |||||
| PROCESSING("processing"), | |||||
| PACKAGING("packaging"), | |||||
| STORING("storing"), | |||||
| COMPLETED("completed") | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| package com.ffii.fpsms.modules.jobOrder.enums | |||||
| import jakarta.persistence.AttributeConverter | |||||
| import jakarta.persistence.Converter | |||||
| // Job Order Status | |||||
| @Converter(autoApply = true) | |||||
| class JobOrderStatusConverter : AttributeConverter<JobOrderStatus, String> { | |||||
| override fun convertToDatabaseColumn(status: JobOrderStatus?): String? { | |||||
| return status?.value | |||||
| } | |||||
| override fun convertToEntityAttribute(value: String?): JobOrderStatus? { | |||||
| return value?.let { v -> | |||||
| JobOrderStatus.entries.find { it.value == v } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -24,7 +24,7 @@ open class JobOrderBomMaterialService( | |||||
| open fun createJobOrderBomMaterialRequests(joId: Long): List<CreateJobOrderBomMaterialRequest> { | open fun createJobOrderBomMaterialRequests(joId: Long): List<CreateJobOrderBomMaterialRequest> { | ||||
| val zero = BigDecimal.ZERO | val zero = BigDecimal.ZERO | ||||
| val jo = jobOrderRepository.findById(joId).getOrNull() ?: throw NoSuchElementException() | val jo = jobOrderRepository.findById(joId).getOrNull() ?: throw NoSuchElementException() | ||||
| val proportion = BigDecimal.ONE //(jo.reqQty ?: zero).divide(jo.bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP) | |||||
| val proportion = (jo.reqQty ?: zero).divide(jo.bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP) | |||||
| val jobmRequests = jo.bom?.bomMaterials?.map { bm -> | val jobmRequests = jo.bom?.bomMaterials?.map { bm -> | ||||
| val salesUnit = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | val salesUnit = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | ||||
| @@ -35,7 +35,7 @@ open class JobOrderBomMaterialService( | |||||
| reqQty = bm.qty?.times(proportion) ?: zero, | reqQty = bm.qty?.times(proportion) ?: zero, | ||||
| uomId = salesUnit?.uom?.id | uomId = salesUnit?.uom?.id | ||||
| ) | ) | ||||
| } ?: listOf() | |||||
| } ?: listOf() | |||||
| return jobmRequests | return jobmRequests | ||||
| } | } | ||||
| @@ -8,6 +8,7 @@ import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository | |||||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail | import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine | import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetailPickLine | ||||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | ||||
| import com.ffii.fpsms.modules.jobOrder.enums.JobOrderStatus | |||||
| import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest | import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderReleaseRequest | import com.ffii.fpsms.modules.jobOrder.web.model.JobOrderReleaseRequest | ||||
| import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest | import com.ffii.fpsms.modules.jobOrder.web.model.SearchJobOrderInfoRequest | ||||
| @@ -111,6 +112,7 @@ open class JobOrderService( | |||||
| val approver = request.approverId?.let { userService.find(it).getOrNull() } ?: SecurityUtils.getUser().getOrNull() | val approver = request.approverId?.let { userService.find(it).getOrNull() } ?: SecurityUtils.getUser().getOrNull() | ||||
| val prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() } | val prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() } | ||||
| val code = assignJobNo() | val code = assignJobNo() | ||||
| val status = JobOrderStatus.entries.find { it.value == request.status } | |||||
| jo.apply { | jo.apply { | ||||
| this.code = code | this.code = code | ||||
| @@ -119,7 +121,7 @@ open class JobOrderService( | |||||
| planStart = request.planStart ?: LocalDateTime.now() | planStart = request.planStart ?: LocalDateTime.now() | ||||
| planEnd = request.planEnd ?: LocalDateTime.now() | planEnd = request.planEnd ?: LocalDateTime.now() | ||||
| reqQty = request.reqQty | reqQty = request.reqQty | ||||
| status = request.status | |||||
| this.status = status | |||||
| type = request.type | type = request.type | ||||
| this.approver = approver | this.approver = approver | ||||
| this.prodScheduleLine = prodScheduleLine | this.prodScheduleLine = prodScheduleLine | ||||
| @@ -141,7 +143,7 @@ open class JobOrderService( | |||||
| open fun releaseJobOrder(request: JobOrderReleaseRequest): MessageResponse { | open fun releaseJobOrder(request: JobOrderReleaseRequest): MessageResponse { | ||||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | ||||
| jo.apply { | jo.apply { | ||||
| status = "pending" | |||||
| status = JobOrderStatus.PENDING | |||||
| } | } | ||||
| jobOrderRepository.save(jo) | jobOrderRepository.save(jo) | ||||
| @@ -168,7 +170,7 @@ open class JobOrderService( | |||||
| type = null, | type = null, | ||||
| message = null, | message = null, | ||||
| errorPosition = null, | errorPosition = null, | ||||
| entity = mapOf("status" to jo.status) | |||||
| entity = mapOf("status" to jo.status?.value) | |||||
| ) | ) | ||||
| } | } | ||||
| } | } | ||||
| @@ -13,4 +13,6 @@ interface BomMaterialRepository : AbstractRepository<BomMaterial, Long> { | |||||
| fun findAllByBomItemIdAndDeletedIsFalse(itemId: Long): List<BomMaterial> | fun findAllByBomItemIdAndDeletedIsFalse(itemId: Long): List<BomMaterial> | ||||
| fun findAllByBomIdAndDeletedIsFalse(bomId: Long): List<BomMaterial> | fun findAllByBomIdAndDeletedIsFalse(bomId: Long): List<BomMaterial> | ||||
| fun findByBomIdAndItemId(bomId: Long, itemId: Long): BomMaterial? | |||||
| } | } | ||||
| @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository | |||||
| @Repository | @Repository | ||||
| interface BomProcessMaterialRepository : AbstractRepository<BomProcessMaterial, Long> { | interface BomProcessMaterialRepository : AbstractRepository<BomProcessMaterial, Long> { | ||||
| fun findByBomProcessIdAndBomMaterialId(bomProcessId: Long, bomMaterialId: Long): BomProcessMaterial?; | |||||
| } | } | ||||
| @@ -16,4 +16,6 @@ interface BomRepository : AbstractRepository<Bom, Long> { | |||||
| fun findByItemIdAndDeletedIsFalse(itemId: Serializable): Bom? | fun findByItemIdAndDeletedIsFalse(itemId: Serializable): Bom? | ||||
| fun findBomComboByDeletedIsFalse(): List<BomCombo> | fun findBomComboByDeletedIsFalse(): List<BomCombo> | ||||
| fun findByCodeAndDeletedIsFalse(code: String): Bom? | |||||
| } | } | ||||
| @@ -3,8 +3,13 @@ package com.ffii.fpsms.modules.master.entity | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo | import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo | ||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.io.Serializable | |||||
| @Repository | @Repository | ||||
| interface WarehouseRepository : AbstractRepository<Warehouse, Long> { | interface WarehouseRepository : AbstractRepository<Warehouse, Long> { | ||||
| fun findWarehouseComboByDeletedFalse(): List<WarehouseCombo>; | fun findWarehouseComboByDeletedFalse(): List<WarehouseCombo>; | ||||
| fun findByIdAndDeletedIsFalse(id: Serializable): Warehouse?; | |||||
| fun findByCodeAndDeletedIsFalse(code: String): Warehouse?; | |||||
| } | } | ||||
| @@ -90,7 +90,8 @@ open class BomService( | |||||
| private fun saveBomEntity(req: ImportBomRequest): Bom { | private fun saveBomEntity(req: ImportBomRequest): Bom { | ||||
| val item = itemsRepository.findByCodeAndDeletedFalse(req.code) ?: itemsRepository.findByNameAndDeletedFalse(req.name) | val item = itemsRepository.findByCodeAndDeletedFalse(req.code) ?: itemsRepository.findByNameAndDeletedFalse(req.name) | ||||
| val uom = if (req.uomId != null) uomConversionRepository.findById(req.uomId!!).orElseThrow() else null | val uom = if (req.uomId != null) uomConversionRepository.findById(req.uomId!!).orElseThrow() else null | ||||
| val bom = Bom().apply { | |||||
| val bom = bomRepository.findByCodeAndDeletedIsFalse(req.code) ?: Bom() | |||||
| bom.apply { | |||||
| this.isDark = req.isDark | this.isDark = req.isDark | ||||
| this.isFloat = req.isFloat | this.isFloat = req.isFloat | ||||
| this.isDense = req.isDense | this.isDense = req.isDense | ||||
| @@ -111,10 +112,11 @@ open class BomService( | |||||
| private fun saveBomMaterial(req: ImportBomMatRequest): BomMaterial { | private fun saveBomMaterial(req: ImportBomMatRequest): BomMaterial { | ||||
| // val uom = uomConversionRepository.findByCodeAndDeletedFalse() | // val uom = uomConversionRepository.findByCodeAndDeletedFalse() | ||||
| println("printing") | |||||
| println(req) | |||||
| println(req.item) | |||||
| val bomMaterial = BomMaterial().apply { | |||||
| // println("printing") | |||||
| // println(req) | |||||
| // println(req.item) | |||||
| val bomMaterial = req.bom?.id?.let { bId -> req.item?.id?.let { iId -> bomMaterialRepository.findByBomIdAndItemId(bId, iId) } } ?: BomMaterial() | |||||
| bomMaterial.apply { | |||||
| this.item = req.item | this.item = req.item | ||||
| this.itemName = req.item!!.name | this.itemName = req.item!!.name | ||||
| this.isConsumable = req.isConsumable | this.isConsumable = req.isConsumable | ||||
| @@ -124,12 +126,12 @@ open class BomService( | |||||
| this.uom = req.uom | this.uom = req.uom | ||||
| this.uomName = req.uomName | this.uomName = req.uomName | ||||
| this.bom = req.bom | this.bom = req.bom | ||||
| } | } | ||||
| return bomMaterialRepository.saveAndFlush(bomMaterial) | return bomMaterialRepository.saveAndFlush(bomMaterial) | ||||
| } | } | ||||
| private fun saveBomProcess(req: ImportBomProcessRequest): BomProcess { | private fun saveBomProcess(req: ImportBomProcessRequest): BomProcess { | ||||
| val bomProcess = BomProcess().apply { | |||||
| val bomProcess = req.bom!!.id?.let { id -> req.seqNo?.let { seqNo -> bomProcessRepository.findBySeqNoAndBomIdAndDeletedIsFalse(seqNo.toInt(), id) } } ?: BomProcess() | |||||
| bomProcess.apply { | |||||
| this.process = req.process | this.process = req.process | ||||
| this.equipment = req.equipment | this.equipment = req.equipment | ||||
| this.description = req.description | this.description = req.description | ||||
| @@ -142,7 +144,8 @@ open class BomService( | |||||
| return bomProcessRepository.saveAndFlush(bomProcess) | return bomProcessRepository.saveAndFlush(bomProcess) | ||||
| } | } | ||||
| fun saveBomProcessMaterial(req: ImportBomProcessMaterialRequest): BomProcessMaterial { | fun saveBomProcessMaterial(req: ImportBomProcessMaterialRequest): BomProcessMaterial { | ||||
| val bomProcessMaterial = BomProcessMaterial().apply { | |||||
| val bomProcessMaterial = req.bomProcess?.id?.let { pid -> req.bomMaterial?.id?.let { mid -> bomProcessMaterialRepository.findByBomProcessIdAndBomMaterialId(pid, mid)} } ?: BomProcessMaterial() | |||||
| bomProcessMaterial.apply { | |||||
| this.bomProcess = req.bomProcess | this.bomProcess = req.bomProcess | ||||
| this.bomMaterial = req.bomMaterial | this.bomMaterial = req.bomMaterial | ||||
| } | } | ||||
| @@ -489,7 +492,7 @@ open class BomService( | |||||
| // val folder = File(folderPath) | // val folder = File(folderPath) | ||||
| val resolver = PathMatchingResourcePatternResolver() | val resolver = PathMatchingResourcePatternResolver() | ||||
| // val excels = resolver.getResources("bomImport/*.xlsx") | // val excels = resolver.getResources("bomImport/*.xlsx") | ||||
| val excels = resolver.getResources("file:C:/Users/2Fi/Desktop/test folder/*.xlsx") | |||||
| val excels = resolver.getResources("file:C:/Users/ffii_/Downloads/bom/new/*.xlsx") | |||||
| // val excels = resolver.getResources("file:C:/Users/2Fi/Desktop/Third Wave of BOM Excel/*.xlsx") | // val excels = resolver.getResources("file:C:/Users/2Fi/Desktop/Third Wave of BOM Excel/*.xlsx") | ||||
| println("size: ${excels.size}") | println("size: ${excels.size}") | ||||
| val logExcel = ClassPathResource("excelTemplate/bom_excel_issue_log.xlsx") | val logExcel = ClassPathResource("excelTemplate/bom_excel_issue_log.xlsx") | ||||
| @@ -210,6 +210,10 @@ open class ItemsService( | |||||
| return itemsRepository.findByIdAndDeletedFalse(id); | return itemsRepository.findByIdAndDeletedFalse(id); | ||||
| } | } | ||||
| open fun findByCode(code: String): Items? { | |||||
| return itemsRepository.findByCodeAndDeletedFalse(code); | |||||
| } | |||||
| open fun findByM18Id(m18Id: Long): Items? { | open fun findByM18Id(m18Id: Long): Items? { | ||||
| return itemsRepository.findByM18IdAndDeletedIsFalse(m18Id) | return itemsRepository.findByM18IdAndDeletedIsFalse(m18Id) | ||||
| } | } | ||||
| @@ -6,6 +6,7 @@ import com.ffii.fpsms.modules.master.entity.ItemsRepository | |||||
| import com.ffii.fpsms.modules.master.entity.Warehouse | import com.ffii.fpsms.modules.master.entity.Warehouse | ||||
| import com.ffii.fpsms.modules.master.entity.WarehouseRepository | import com.ffii.fpsms.modules.master.entity.WarehouseRepository | ||||
| import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo | import com.ffii.fpsms.modules.master.entity.projections.WarehouseCombo | ||||
| import com.ffii.fpsms.modules.master.web.models.SaveWarehouseRequest | |||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository | import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLineRepository | ||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository | ||||
| import com.ffii.fpsms.modules.stock.entity.StockInLine | import com.ffii.fpsms.modules.stock.entity.StockInLine | ||||
| @@ -14,6 +15,7 @@ import com.ffii.fpsms.modules.stock.entity.StockInRepository | |||||
| import com.ffii.fpsms.modules.stock.service.InventoryLotService | import com.ffii.fpsms.modules.stock.service.InventoryLotService | ||||
| import com.ffii.fpsms.modules.stock.service.StockInService | import com.ffii.fpsms.modules.stock.service.StockInService | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import kotlin.jvm.optionals.getOrNull | |||||
| @Service | @Service | ||||
| open class WarehouseService( | open class WarehouseService( | ||||
| @@ -28,4 +30,25 @@ open class WarehouseService( | |||||
| open fun findCombo(): List<WarehouseCombo> { | open fun findCombo(): List<WarehouseCombo> { | ||||
| return warehouseRepository.findWarehouseComboByDeletedFalse(); | return warehouseRepository.findWarehouseComboByDeletedFalse(); | ||||
| } | } | ||||
| open fun findById(id: Long): Warehouse? { | |||||
| return warehouseRepository.findByIdAndDeletedIsFalse(id); | |||||
| } | |||||
| open fun findByCode(code: String): Warehouse? { | |||||
| return warehouseRepository.findByCodeAndDeletedIsFalse(code); | |||||
| } | |||||
| open fun saveWarehouse(request: SaveWarehouseRequest): Warehouse { | |||||
| val warehouse = request.id?.let { warehouseRepository.findById(it).getOrNull() } ?: Warehouse(); | |||||
| warehouse.apply { | |||||
| code = request.code | |||||
| name = request.name | |||||
| description = request.description | |||||
| capacity = request.capacity | |||||
| }; | |||||
| return warehouseRepository.save(warehouse); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,11 @@ | |||||
| package com.ffii.fpsms.modules.master.web.models | |||||
| import java.math.BigDecimal | |||||
| data class SaveWarehouseRequest( | |||||
| val id: Long? = null, | |||||
| val code: String, | |||||
| val name: String, | |||||
| val description: String, | |||||
| val capacity: BigDecimal, | |||||
| ) | |||||
| @@ -80,7 +80,9 @@ open class PickOrderService( | |||||
| private val doPickOrderService: DoPickOrderService, | private val doPickOrderService: DoPickOrderService, | ||||
| private val routerRepository: RouterRepository, | private val routerRepository: RouterRepository, | ||||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | ||||
| private val doPickOrderRepository: DoPickOrderRepository | |||||
| private val doPickOrderRepository: DoPickOrderRepository, | |||||
| private val userRepository: UserRepository | |||||
| ) : AbstractBaseEntityService<PickOrder, Long, PickOrderRepository>(jdbcDao, pickOrderRepository) { | ) : AbstractBaseEntityService<PickOrder, Long, PickOrderRepository>(jdbcDao, pickOrderRepository) { | ||||
| @@ -2765,19 +2767,22 @@ if (existingRecords.isNotEmpty()) { | |||||
| println("❌ Pick order not found with ID: $pickOrderId") | println("❌ Pick order not found with ID: $pickOrderId") | ||||
| return emptyList() | return emptyList() | ||||
| } | } | ||||
| if (doPickOrderRepository.findByPickOrderId(pickOrderId).firstOrNull()?.hide == true) { | |||||
| println("🔍 Pick order $pickOrderId is hidden, returning empty list") | |||||
| return emptyList() | |||||
| } | |||||
| println("🔍 Found pick order: ${pickOrder.code}, type: ${pickOrder.type?.value}, status: ${pickOrder.status?.value}") | println("🔍 Found pick order: ${pickOrder.code}, type: ${pickOrder.type?.value}, status: ${pickOrder.status?.value}") | ||||
| if (pickOrder.type?.value != "do") { | if (pickOrder.type?.value != "do") { | ||||
| println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") | println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") | ||||
| return emptyList() | return emptyList() | ||||
| } | } | ||||
| if (pickOrder.status?.value !in listOf("assigned", "released", "picking", "completed")) { | |||||
| val allowedstatuses= listOf("assigned", "released", "picking", "completed") | |||||
| if (pickOrder.status?.value !in allowedstatuses) { | |||||
| println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") | println("❌ Pick order status is not in allowed states: ${pickOrder.status?.value}") | ||||
| return emptyList() | return emptyList() | ||||
| } | } | ||||
| val deliveryOrder = pickOrder.deliveryOrder | val deliveryOrder = pickOrder.deliveryOrder | ||||
| val shop = deliveryOrder?.shop | val shop = deliveryOrder?.shop | ||||
| val supplier = deliveryOrder?.supplier | val supplier = deliveryOrder?.supplier | ||||
| @@ -3164,17 +3169,21 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||||
| println("❌ User not found: $userId") | println("❌ User not found: $userId") | ||||
| return emptyMap() | return emptyMap() | ||||
| } | } | ||||
| val statusList = listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED) | |||||
| // Get all pick orders assigned to user with PENDING or RELEASED status that have doId | // Get all pick orders assigned to user with PENDING or RELEASED status that have doId | ||||
| val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( | val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( | ||||
| user, | user, | ||||
| listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED) | |||||
| statusList | |||||
| ).filter { it.deliveryOrder != null } // Only pick orders with doId | ).filter { it.deliveryOrder != null } // Only pick orders with doId | ||||
| println("🔍 DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId") | println("🔍 DEBUG: Found ${allAssignedPickOrders.size} pick orders assigned to user $userId") | ||||
| val visiblePickOrders = allAssignedPickOrders.filter { pickOrder -> | |||||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrder.id!!) | |||||
| doPickOrders.none { it.hide } // 只显示 hide = false 的订单 | |||||
| } | |||||
| // ✅ NEW LOGIC: Filter based on assignment and status | // ✅ NEW LOGIC: Filter based on assignment and status | ||||
| val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) { | |||||
| val filteredPickOrders = if (visiblePickOrders.isNotEmpty()) { | |||||
| // Check if there are any RELEASED orders assigned to this user (active work) | // Check if there are any RELEASED orders assigned to this user (active work) | ||||
| val assignedReleasedOrders = allAssignedPickOrders.filter { | val assignedReleasedOrders = allAssignedPickOrders.filter { | ||||
| it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId | it.status == PickOrderStatus.RELEASED && it.assignTo?.id == userId | ||||
| @@ -3341,11 +3350,11 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||||
| println("✅ Total result count: ${results.size}") | println("✅ Total result count: ${results.size}") | ||||
| // Filter out lots with null availableQty (rejected lots) | // Filter out lots with null availableQty (rejected lots) | ||||
| val filteredResults = results.filter { row -> | |||||
| val availableQty = row["availableQty"] | |||||
| availableQty != null | |||||
| } | |||||
| // val filteredResults = results.filter { row -> | |||||
| //val availableQty = row["availableQty"] | |||||
| // availableQty != null | |||||
| // } | |||||
| val filteredResults = results | |||||
| println("✅ Filtered result count: ${filteredResults.size}") | println("✅ Filtered result count: ${filteredResults.size}") | ||||
| // ✅ Transform flat results into hierarchical structure | // ✅ Transform flat results into hierarchical structure | ||||
| @@ -3432,6 +3441,7 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||||
| "pickOrderLines" to pickOrderLinesMap as Any? | "pickOrderLines" to pickOrderLinesMap as Any? | ||||
| ) | ) | ||||
| } | } | ||||
| // Fix the type issues in the getPickOrdersByDateAndStore method | // Fix the type issues in the getPickOrdersByDateAndStore method | ||||
| open fun getPickOrdersByDateAndStore(storeId: String): Map<String, Any?> { | open fun getPickOrdersByDateAndStore(storeId: String): Map<String, Any?> { | ||||
| println("=== Debug: getPickOrdersByDateAndStore ===") | println("=== Debug: getPickOrdersByDateAndStore ===") | ||||
| @@ -3663,4 +3673,132 @@ open fun confirmLotSubstitution(req: LotSubstitutionConfirmRequest): MessageResp | |||||
| errorPosition = null | errorPosition = null | ||||
| ) | ) | ||||
| } | } | ||||
| } | |||||
| open fun updateDoPickOrderHideStatus(pickOrderId: Long, hide: Boolean): MessageResponse { | |||||
| return try { | |||||
| // ✅ 修复:根据 pickOrderId 查找 do_pick_order 记录 | |||||
| val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId) | |||||
| if (doPickOrders.isEmpty()) { | |||||
| MessageResponse( | |||||
| id = null, | |||||
| name = "Pick order not found", | |||||
| code = "ERROR", | |||||
| type = "pickorder", | |||||
| message = "No do_pick_order found for pickOrderId $pickOrderId", | |||||
| errorPosition = "pickOrderId" | |||||
| ) | |||||
| } else { | |||||
| // ✅ 更新所有相关的 do_pick_order 记录 | |||||
| doPickOrders.forEach { doPickOrder -> | |||||
| doPickOrder.hide = hide | |||||
| doPickOrderRepository.save(doPickOrder) | |||||
| } | |||||
| MessageResponse( | |||||
| id = null, | |||||
| name = "Hide status updated", | |||||
| code = "SUCCESS", | |||||
| type = "pickorder", | |||||
| message = "Updated hide status for ${doPickOrders.size} do_pick_order records to $hide", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } catch (e: Exception) { | |||||
| println("❌ Error in updateDoPickOrderHideStatus: ${e.message}") | |||||
| e.printStackTrace() | |||||
| MessageResponse( | |||||
| id = null, | |||||
| name = "Failed to update hide status", | |||||
| code = "ERROR", | |||||
| type = "pickorder", | |||||
| message = "Failed to update hide status: ${e.message}", | |||||
| errorPosition = null | |||||
| ) | |||||
| } | |||||
| } | |||||
| open fun getCompletedDoPickOrders( | |||||
| userId: Long, | |||||
| pickOrderCode: String?, | |||||
| shopName: String?, | |||||
| deliveryNo: String?, | |||||
| ticketNo: String? | |||||
| ): List<Map<String, Any?>> { | |||||
| return try { | |||||
| println("=== getCompletedDoPickOrders ===") | |||||
| println("userId: $userId") | |||||
| println("pickOrderCode: $pickOrderCode") | |||||
| println("shopName: $shopName") | |||||
| println("deliveryNo: $deliveryNo") | |||||
| println("ticketNo: $ticketNo") | |||||
| // ✅ 修复:使用正确的方法获取已完成的 pick orders | |||||
| val user = userRepository.findById(userId).orElse(null) | |||||
| if (user == null) { | |||||
| println("User not found: $userId") | |||||
| return emptyList() | |||||
| } | |||||
| // ✅ 修复:使用正确的方法获取已完成的 pick orders | |||||
| val completedPickOrders = pickOrderRepository.findAllByAssignToIdAndStatusIn( | |||||
| userId, | |||||
| listOf(PickOrderStatus.COMPLETED) | |||||
| ) | |||||
| println("Found ${completedPickOrders.size} completed pick orders for user $userId") | |||||
| val result = mutableListOf<Map<String, Any?>>() | |||||
| for (pickOrder in completedPickOrders) { | |||||
| // 获取该 pick order 的 FG pick orders | |||||
| val fgPickOrders = getFgPickOrdersByPickOrderId(pickOrder.id!!) | |||||
| if (fgPickOrders.isNotEmpty()) { | |||||
| val firstFgOrder = fgPickOrders[0] as Map<String, Any?> // ✅ 修复:转换为 Map | |||||
| // ✅ 修复:使用正确的属性名进行搜索过滤,并处理空值 | |||||
| val matchesSearch = when { | |||||
| pickOrderCode != null && !(pickOrder.code?.contains(pickOrderCode, ignoreCase = true) ?: false) -> false | |||||
| shopName != null && !((firstFgOrder["shopName"] as? String)?.contains(shopName, ignoreCase = true) ?: false) -> false | |||||
| deliveryNo != null && !((firstFgOrder["deliveryNo"] as? String)?.contains(deliveryNo, ignoreCase = true) ?: false) -> false | |||||
| ticketNo != null && !((firstFgOrder["ticketNo"] as? String)?.contains(ticketNo, ignoreCase = true) ?: false) -> false | |||||
| else -> true | |||||
| } | |||||
| if (matchesSearch) { | |||||
| result.add(mapOf( | |||||
| "id" to pickOrder.id, | |||||
| "pickOrderId" to pickOrder.id, | |||||
| "pickOrderCode" to pickOrder.code, | |||||
| "pickOrderConsoCode" to pickOrder.consoCode, | |||||
| "pickOrderStatus" to pickOrder.status.toString(), | |||||
| "deliveryOrderId" to firstFgOrder["deliveryOrderId"], | |||||
| "deliveryNo" to firstFgOrder["deliveryNo"], | |||||
| "deliveryDate" to firstFgOrder["deliveryDate"], | |||||
| "shopId" to firstFgOrder["shopId"], | |||||
| "shopCode" to firstFgOrder["shopCode"], | |||||
| "shopName" to firstFgOrder["shopName"], | |||||
| "shopAddress" to firstFgOrder["shopAddress"], | |||||
| "ticketNo" to firstFgOrder["ticketNo"], | |||||
| "shopPoNo" to firstFgOrder["shopPoNo"], | |||||
| "numberOfCartons" to firstFgOrder["numberOfCartons"], | |||||
| "truckNo" to firstFgOrder["truckNo"], | |||||
| "storeId" to firstFgOrder["storeId"], | |||||
| "completedDate" to (pickOrder.completeDate?.toString() ?: pickOrder.modified?.toString()), | |||||
| "fgPickOrders" to fgPickOrders | |||||
| )) | |||||
| } | |||||
| } | |||||
| } | |||||
| // 按完成时间倒序排列 | |||||
| result.sortByDescending { it["completedDate"] as String? } | |||||
| println("Returning ${result.size} completed DO pick orders") | |||||
| result | |||||
| } catch (e: Exception) { | |||||
| println("❌ Error in getCompletedDoPickOrders: ${e.message}") | |||||
| e.printStackTrace() | |||||
| emptyList() | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -287,4 +287,21 @@ fun getPickOrdersByStore(@PathVariable storeId: String): Map<String, Any?> { | |||||
| fun confirmLotSubstitution(@RequestBody req: LotSubstitutionConfirmRequest): MessageResponse { | fun confirmLotSubstitution(@RequestBody req: LotSubstitutionConfirmRequest): MessageResponse { | ||||
| return pickOrderService.confirmLotSubstitution(req) | return pickOrderService.confirmLotSubstitution(req) | ||||
| } | } | ||||
| @PostMapping("/update-hide-status/{pickOrderId}") | |||||
| fun updatePickOrderHideStatus( | |||||
| @PathVariable pickOrderId: Long, | |||||
| @RequestParam hide: Boolean | |||||
| ): MessageResponse { | |||||
| return pickOrderService.updateDoPickOrderHideStatus(pickOrderId, hide) | |||||
| } | |||||
| @GetMapping("/completed-do-pick-orders/{userId}") | |||||
| fun getCompletedDoPickOrders( | |||||
| @PathVariable userId: Long, | |||||
| @RequestParam(required = false) pickOrderCode: String?, | |||||
| @RequestParam(required = false) shopName: String?, | |||||
| @RequestParam(required = false) deliveryNo: String?, | |||||
| @RequestParam(required = false) ticketNo: String? | |||||
| ): List<Map<String, Any?>> { | |||||
| return pickOrderService.getCompletedDoPickOrders(userId, pickOrderCode, shopName, deliveryNo, ticketNo) | |||||
| } | |||||
| } | } | ||||
| @@ -209,7 +209,7 @@ open class PurchaseOrderService( | |||||
| } | } | ||||
| val mappedPoLine = pol.map { thisPol -> | val mappedPoLine = pol.map { thisPol -> | ||||
| val inLine = stockInLine.filter { it.purchaseOrderLineId == thisPol.id } | val inLine = stockInLine.filter { it.purchaseOrderLineId == thisPol.id } | ||||
| .filter { it.dnDate != null } | |||||
| .filter { it.receiptDate != null } | |||||
| val categoryCode = thisPol.item?.qcCategory?.code | val categoryCode = thisPol.item?.qcCategory?.code | ||||
| val qcItems = thisPol.item?.qcCategory?.qcItemCategory?.map { | val qcItems = thisPol.item?.qcCategory?.qcItemCategory?.map { | ||||
| QcForPoLine( | QcForPoLine( | ||||
| @@ -25,8 +25,8 @@ interface EscalationLogInfo { | |||||
| @get:Value("#{target.stockInLine?.dnNo}") | @get:Value("#{target.stockInLine?.dnNo}") | ||||
| val dnNo: String? | val dnNo: String? | ||||
| @get:Value("#{target.stockInLine?.dnDate}") | |||||
| val dnDate: LocalDateTime? | |||||
| // @get:Value("#{target.stockInLine?.dnDate}") | |||||
| // val dnDate: LocalDateTime? | |||||
| @get:Value("#{target.stockInLine?.item?.code} - #{target.stockInLine?.item?.name}") | @get:Value("#{target.stockInLine?.item?.code} - #{target.stockInLine?.item?.name}") | ||||
| val item: String? | val item: String? | ||||
| @@ -13,6 +13,9 @@ import org.springframework.data.repository.query.Param | |||||
| @Repository | @Repository | ||||
| interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | ||||
| fun findByIdAndDeletedIsFalse(id: Serializable): InventoryLotLine; | |||||
| fun findInventoryLotLineInfoByInventoryLotItemIdIn(ids: List<Serializable>): List<InventoryLotLineInfo> | fun findInventoryLotLineInfoByInventoryLotItemIdIn(ids: List<Serializable>): List<InventoryLotLineInfo> | ||||
| @Query("select ill from InventoryLotLine ill where :id is null or ill.inventoryLot.item.id = :id order by ill.id desc") | @Query("select ill from InventoryLotLine ill where :id is null or ill.inventoryLot.item.id = :id order by ill.id desc") | ||||
| @@ -35,6 +38,9 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long | |||||
| fun findAllByIdIn(ids: List<Serializable>): List<InventoryLotLine> | fun findAllByIdIn(ids: List<Serializable>): List<InventoryLotLine> | ||||
| fun findAllByInventoryLotId(id: Serializable): List<InventoryLotLine> | fun findAllByInventoryLotId(id: Serializable): List<InventoryLotLine> | ||||
| fun findByInventoryLotStockInLineIdAndWarehouseId(inventoryLotStockInLineId: Long, warehouseId: Long): InventoryLotLine? | |||||
| fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: String): List<InventoryLotLine> | fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: String): List<InventoryLotLine> | ||||
| fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: InventoryLotLineStatus): List<InventoryLotLine> | fun findAllByInventoryLotItemIdAndStatus(itemId: Long, status: InventoryLotLineStatus): List<InventoryLotLine> | ||||
| } | } | ||||
| @@ -40,6 +40,10 @@ open class StockIn : BaseEntity<Long>() { | |||||
| @JoinColumn(name = "stockOutId") | @JoinColumn(name = "stockOutId") | ||||
| open var stockOutId: StockOut? = null | open var stockOutId: StockOut? = null | ||||
| @ManyToOne | |||||
| @JoinColumn(name = "stockTakeId") | |||||
| open var stockTake: StockTake? = null | |||||
| @Column(name = "orderDate") | @Column(name = "orderDate") | ||||
| open var orderDate: LocalDateTime? = null | open var orderDate: LocalDateTime? = null | ||||
| @@ -37,6 +37,10 @@ open class StockInLine : BaseEntity<Long>() { | |||||
| @JoinColumn(name = "purchaseOrderLineId") | @JoinColumn(name = "purchaseOrderLineId") | ||||
| open var purchaseOrderLine: PurchaseOrderLine? = null | open var purchaseOrderLine: PurchaseOrderLine? = null | ||||
| @ManyToOne | |||||
| @JoinColumn(name = "stockTakeLineId") | |||||
| open var stockTakeLine: StockTakeLine? = null | |||||
| @NotNull | @NotNull | ||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "stockInId", nullable = false) | @JoinColumn(name = "stockInId", nullable = false) | ||||
| @@ -1,9 +1,19 @@ | |||||
| 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.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.io.Serializable | |||||
| @Repository | @Repository | ||||
| interface StockInRepository : AbstractRepository<StockIn, Long> { | interface StockInRepository : AbstractRepository<StockIn, Long> { | ||||
| fun findByIdAndDeletedFalse(id: Long): StockIn? | |||||
| fun findByPurchaseOrderIdAndDeletedFalse(purchaseOrderId: Long): StockIn? | fun findByPurchaseOrderIdAndDeletedFalse(purchaseOrderId: Long): StockIn? | ||||
| // @Query(""" | |||||
| // select si from StockIn si where si.stockTake.id = :stockTakeId and si.deleted = false | |||||
| // """) | |||||
| fun findByStockTakeIdAndDeletedFalse(stockTakeId: Long): StockIn? | |||||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockIn? | |||||
| } | } | ||||
| @@ -1,6 +1,8 @@ | |||||
| package com.ffii.fpsms.modules.stock.entity | package com.ffii.fpsms.modules.stock.entity | ||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.stock.enums.StockTakeStatus | |||||
| import com.ffii.fpsms.modules.stock.enums.StockTakeStatusConverter | |||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| import jakarta.validation.constraints.Size | import jakarta.validation.constraints.Size | ||||
| @@ -28,10 +30,11 @@ open class StockTake: BaseEntity<Long>() { | |||||
| @Column(name = "actualEnd") | @Column(name = "actualEnd") | ||||
| open var actualEnd: LocalDateTime? = null | open var actualEnd: LocalDateTime? = null | ||||
| @Size(max = 20) | |||||
| // @Size(max = 20) | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "status", nullable = false, length = 20) | @Column(name = "status", nullable = false, length = 20) | ||||
| open var status: String? = null | |||||
| @Convert(converter = StockTakeStatusConverter::class) | |||||
| open var status: StockTakeStatus? = null | |||||
| @Size(max = 500) | @Size(max = 500) | ||||
| @Column(name = "remarks", length = 500) | @Column(name = "remarks", length = 500) | ||||
| @@ -2,6 +2,8 @@ package com.ffii.fpsms.modules.stock.entity | |||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.fpsms.modules.master.entity.UomConversion | import com.ffii.fpsms.modules.master.entity.UomConversion | ||||
| import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatus | |||||
| import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatusConverter | |||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| import jakarta.validation.constraints.Size | import jakarta.validation.constraints.Size | ||||
| @@ -16,7 +18,6 @@ open class StockTakeLine : BaseEntity<Long>() { | |||||
| @JoinColumn(name = "stockTakeId", nullable = false) | @JoinColumn(name = "stockTakeId", nullable = false) | ||||
| open var stockTake: StockTake? = null | open var stockTake: StockTake? = null | ||||
| @NotNull | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "inventoryLotLineId", nullable = false) | @JoinColumn(name = "inventoryLotLineId", nullable = false) | ||||
| open var inventoryLotLine: InventoryLotLine? = null | open var inventoryLotLine: InventoryLotLine? = null | ||||
| @@ -34,10 +35,11 @@ open class StockTakeLine : BaseEntity<Long>() { | |||||
| @Column(name = "completeDate") | @Column(name = "completeDate") | ||||
| open var completeDate: LocalDateTime? = null | open var completeDate: LocalDateTime? = null | ||||
| @Size(max = 20) | |||||
| // @Size(max = 20) | |||||
| @NotNull | @NotNull | ||||
| @Column(name = "status", nullable = false, length = 20) | @Column(name = "status", nullable = false, length = 20) | ||||
| open var status: String? = null | |||||
| @Convert(converter = StockTakeLineStatusConverter::class) | |||||
| open var status: StockTakeLineStatus? = null | |||||
| @Size(max = 500) | @Size(max = 500) | ||||
| @Column(name = "remarks", length = 500) | @Column(name = "remarks", length = 500) | ||||
| @@ -2,7 +2,9 @@ package com.ffii.fpsms.modules.stock.entity | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.io.Serializable | |||||
| @Repository | @Repository | ||||
| interface StockTakeLineRepository : AbstractRepository<StockTakeLine, Long> { | interface StockTakeLineRepository : AbstractRepository<StockTakeLine, Long> { | ||||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeLine?; | |||||
| } | } | ||||
| @@ -1,8 +1,16 @@ | |||||
| 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.stereotype.Repository | import org.springframework.stereotype.Repository | ||||
| import java.io.Serializable | |||||
| @Repository | @Repository | ||||
| interface StockTakeRepository : AbstractRepository<StockTake, Long> { | interface StockTakeRepository : AbstractRepository<StockTake, Long> { | ||||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTake; | |||||
| @Query(""" | |||||
| select st.code from StockTake st where st.code like :prefix% order by st.code desc limit 1 | |||||
| """) | |||||
| fun findLatestCodeByPrefix(prefix: String): String? | |||||
| } | } | ||||
| @@ -43,7 +43,7 @@ interface StockInLineInfo { | |||||
| @get:Value("#{target.item?.type}") | @get:Value("#{target.item?.type}") | ||||
| val itemType: String | val itemType: String | ||||
| val dnNo: String | val dnNo: String | ||||
| val dnDate: LocalDateTime? | |||||
| // val dnDate: LocalDateTime? | |||||
| // val qcDecision: LocalDateTime? | // val qcDecision: LocalDateTime? | ||||
| @get:Value("#{target.escalationLog.^[status.value == 'pending']?.handler?.id}") | @get:Value("#{target.escalationLog.^[status.value == 'pending']?.handler?.id}") | ||||
| val handlerId: Long? | val handlerId: Long? | ||||
| @@ -0,0 +1,7 @@ | |||||
| package com.ffii.fpsms.modules.stock.enums | |||||
| enum class StockTakeStatus(val value: String) { | |||||
| PENDING("pending"), | |||||
| STOCKTAKING("stockTaking"), | |||||
| COMPLETED("completed"), | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| package com.ffii.fpsms.modules.stock.enums | |||||
| import jakarta.persistence.AttributeConverter | |||||
| import jakarta.persistence.Converter | |||||
| // Stock Take Status | |||||
| @Converter(autoApply = true) | |||||
| class StockTakeStatusConverter : AttributeConverter<StockTakeStatus, String>{ | |||||
| override fun convertToDatabaseColumn(status: StockTakeStatus?): String? { | |||||
| return status?.value | |||||
| } | |||||
| override fun convertToEntityAttribute(value: String?): StockTakeStatus? { | |||||
| return value?.let { v -> | |||||
| StockTakeStatus.entries.find { it.value == v } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,6 @@ | |||||
| package com.ffii.fpsms.modules.stock.enums | |||||
| enum class StockTakeLineStatus(val value: String) { | |||||
| PENDING("pending"), | |||||
| COMPLETED("completed"), | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| package com.ffii.fpsms.modules.stock.enums | |||||
| import jakarta.persistence.AttributeConverter | |||||
| import jakarta.persistence.Converter | |||||
| // Stock Take Line Status | |||||
| @Converter(autoApply = true) | |||||
| class StockTakeLineStatusConverter : AttributeConverter<StockTakeLineStatus, String> { | |||||
| override fun convertToDatabaseColumn(status: StockTakeLineStatus?): String? { | |||||
| return status?.value | |||||
| } | |||||
| override fun convertToEntityAttribute(value: String?): StockTakeLineStatus? { | |||||
| return value?.let { v -> | |||||
| StockTakeLineStatus.entries.find { it.value == v } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -26,6 +26,8 @@ import com.ffii.core.utils.ZebraPrinterUtil | |||||
| import com.ffii.fpsms.modules.master.entity.ItemUomRespository | import com.ffii.fpsms.modules.master.entity.ItemUomRespository | ||||
| import com.ffii.fpsms.modules.master.entity.WarehouseRepository | import com.ffii.fpsms.modules.master.entity.WarehouseRepository | ||||
| import com.ffii.fpsms.modules.master.service.PrinterService | import com.ffii.fpsms.modules.master.service.PrinterService | ||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder | |||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderLine | |||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository | import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository | ||||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | ||||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus | import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus | ||||
| @@ -50,7 +52,7 @@ data class QrContent(val itemId: Long, val stockInLineId: Long) | |||||
| @Service | @Service | ||||
| open class StockInLineService( | open class StockInLineService( | ||||
| private val jdbcDao: JdbcDao, | private val jdbcDao: JdbcDao, | ||||
| private val purchaseOrderRepository: PurchaseOrderRepository, | |||||
| private val poRepository: PurchaseOrderRepository, | |||||
| private val polRepository: PurchaseOrderLineRepository, | private val polRepository: PurchaseOrderLineRepository, | ||||
| private val qcItemsRepository: QcItemRepository, | private val qcItemsRepository: QcItemRepository, | ||||
| private val qcResultRepository: QcResultRepository, | private val qcResultRepository: QcResultRepository, | ||||
| @@ -65,6 +67,7 @@ open class StockInLineService( | |||||
| private val warehouseRepository: WarehouseRepository, | private val warehouseRepository: WarehouseRepository, | ||||
| private val itemUomRespository: ItemUomRespository, | private val itemUomRespository: ItemUomRespository, | ||||
| private val printerService: PrinterService, | private val printerService: PrinterService, | ||||
| private val stockTakeLineRepository: StockTakeLineRepository, | |||||
| ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | ||||
| open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | ||||
| @@ -77,44 +80,74 @@ open class StockInLineService( | |||||
| @Transactional | @Transactional | ||||
| open fun create(request: SaveStockInLineRequest): MessageResponse { | open fun create(request: SaveStockInLineRequest): MessageResponse { | ||||
| val stockInLine = StockInLine() | val stockInLine = StockInLine() | ||||
| val item = itemRepository.findById(request.itemId).orElseThrow() | |||||
| val purchaseOrderLine = polRepository.findById(request.purchaseOrderLineId).orElseThrow() | |||||
| var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | |||||
| purchaseOrderLine.apply { | |||||
| status = PurchaseOrderLineStatus.RECEIVING | |||||
| } | |||||
| val pol = polRepository.saveAndFlush(purchaseOrderLine) | |||||
| val pol = if (request.purchaseOrderLineId != null) | |||||
| request.purchaseOrderLineId?.let { polRepository.findById(it).orElseThrow() } | |||||
| else null | |||||
| val stl = if (request.stockTakeId != null) | |||||
| request.stockTakeId?.let { stockTakeLineRepository.findById(it).getOrNull() } | |||||
| else null | |||||
| var stockIn = if (request.stockInId != null) request.stockInId?.let { stockInRepository.findByIdAndDeletedFalse(it) } | |||||
| else if (pol != null) pol.purchaseOrder?.id?.let { stockInRepository.findByPurchaseOrderIdAndDeletedFalse(it) } | |||||
| else if (request.stockTakeId != null) request.stockTakeId?.let { stockInRepository.findByStockTakeIdAndDeletedFalse(it) } | |||||
| else null | |||||
| if (stockIn == null) { | if (stockIn == null) { | ||||
| stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | |||||
| stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = pol?.purchaseOrder?.id)).entity as StockIn | |||||
| // stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn | |||||
| // var stockIn = stockInRepository.findByPurchaseOrderIdAndDeletedFalse(request.purchaseOrderId) | |||||
| } | |||||
| val item = itemRepository.findById(request.itemId).orElseThrow() | |||||
| // If request contains valid POL | |||||
| if (pol != null) { | |||||
| val po = pol.purchaseOrder; | |||||
| pol.apply { | |||||
| status = PurchaseOrderLineStatus.RECEIVING | |||||
| } | |||||
| val savedPol = polRepository.saveAndFlush(pol) | |||||
| stockInLine.apply { | |||||
| this.purchaseOrderLine = savedPol | |||||
| } | |||||
| // update po status to receiving | // update po status to receiving | ||||
| val po = purchaseOrderRepository.findById(request.purchaseOrderId).orElseThrow() | |||||
| po.apply { | |||||
| status = PurchaseOrderStatus.RECEIVING | |||||
| if (po != null) { | |||||
| po.apply { | |||||
| status = PurchaseOrderStatus.RECEIVING | |||||
| } | |||||
| val savedPo = poRepository.save(po) | |||||
| stockInLine.apply { | |||||
| this.purchaseOrder = savedPo | |||||
| } | |||||
| } | } | ||||
| purchaseOrderRepository.save(po) | |||||
| } | } | ||||
| val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!) | |||||
| if (pol.qty!! < request.acceptedQty) { | |||||
| throw BadRequestException() | |||||
| // If request contains valid stock take id | |||||
| if (stl != null) { | |||||
| stockInLine.apply { | |||||
| this.stockTakeLine = stl | |||||
| } | |||||
| } | } | ||||
| // val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!) | |||||
| // if (pol.qty!! < request.acceptedQty) { | |||||
| // throw BadRequestException() | |||||
| // } | |||||
| stockInLine.apply { | stockInLine.apply { | ||||
| this.item = item | this.item = item | ||||
| itemNo = item.code | itemNo = item.code | ||||
| this.purchaseOrder = purchaseOrderLine.purchaseOrder | |||||
| this.purchaseOrderLine = purchaseOrderLine | |||||
| this.stockIn = stockIn | this.stockIn = stockIn | ||||
| acceptedQty = request.acceptedQty | acceptedQty = request.acceptedQty | ||||
| dnNo = request.dnNo | dnNo = request.dnNo | ||||
| dnDate = request.dnDate?.atStartOfDay() | |||||
| receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now() | |||||
| productLotNo = request.productLotNo | productLotNo = request.productLotNo | ||||
| status = StockInLineStatus.PENDING.status | status = StockInLineStatus.PENDING.status | ||||
| } | } | ||||
| val savedInLine = saveAndFlush(stockInLine) | |||||
| val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedInLine.id!!) | |||||
| val savedSIL = saveAndFlush(stockInLine) | |||||
| val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedSIL.id!!) | |||||
| return MessageResponse( | return MessageResponse( | ||||
| id = savedInLine.id, | |||||
| code = savedInLine.itemNo, | |||||
| name = savedInLine.item!!.name, | |||||
| id = savedSIL.id, | |||||
| code = savedSIL.itemNo, | |||||
| name = savedSIL.item!!.name, | |||||
| type = "stock in line created: status = pending", | type = "stock in line created: status = pending", | ||||
| message = "save success", | message = "save success", | ||||
| errorPosition = null, | errorPosition = null, | ||||
| @@ -156,7 +189,7 @@ open class StockInLineService( | |||||
| itemId = request.itemId | itemId = request.itemId | ||||
| ) | ) | ||||
| val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) | val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) | ||||
| val convertedBaseQty = if (stockItemUom != null && purchaseItemUom != null) { | |||||
| val convertedBaseQty = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) { | |||||
| (line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) | (line.qty) * (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) | ||||
| } else { | } else { | ||||
| (line.qty) | (line.qty) | ||||
| @@ -267,66 +300,50 @@ open class StockInLineService( | |||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| @Transactional | @Transactional | ||||
| open fun updatePurchaseOrderStatus(request: SaveStockInLineRequest) { | |||||
| if (request.status == StockInLineStatus.COMPLETE.status) { | |||||
| val unfinishedLines = polRepository | |||||
| .findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(purchaseOrderId = request.purchaseOrderId, status = PurchaseOrderLineStatus.COMPLETED) | |||||
| if (unfinishedLines.isEmpty()) { | |||||
| val po = purchaseOrderRepository.findById(request.purchaseOrderId).orElseThrow() | |||||
| po.apply { | |||||
| status = PurchaseOrderStatus.COMPLETED | |||||
| } | |||||
| purchaseOrderRepository.saveAndFlush(po) | |||||
| } else { | |||||
| open fun updatePurchaseOrderStatus(po : PurchaseOrder) { | |||||
| val unfinishedPol = polRepository | |||||
| .findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(po.id!!, | |||||
| PurchaseOrderLineStatus.COMPLETED) | |||||
| // If all POL is completed | |||||
| if (unfinishedPol.isEmpty()) { | |||||
| po.apply { | |||||
| status = PurchaseOrderStatus.COMPLETED | |||||
| } | } | ||||
| poRepository.saveAndFlush(po) | |||||
| } else { | |||||
| } | } | ||||
| } | } | ||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| @Transactional | @Transactional | ||||
| fun updatePurchaseOrderLineStatus(request: SaveStockInLineRequest) { | |||||
| println(request.status) | |||||
| if (request.status == StockInLineStatus.RECEIVING.status) { | |||||
| val unQcedLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(purchaseOrderLineId = request.purchaseOrderLineId) | |||||
| .filter { | |||||
| it.status != StockInLineStatus.RECEIVING.status | |||||
| && it.status != StockInLineStatus.RECEIVED.status | |||||
| && it.status != StockInLineStatus.COMPLETE.status | |||||
| && it.status != StockInLineStatus.REJECT.status | |||||
| } | |||||
| if (unQcedLines.isEmpty()) { | |||||
| // all stock in lines finished | |||||
| // change status of purchase order line | |||||
| val purchaseOrderLine = polRepository.findById(request.purchaseOrderLineId).orElseThrow() | |||||
| purchaseOrderLine.apply { | |||||
| status = PurchaseOrderLineStatus.RECEIVING | |||||
| fun updatePurchaseOrderLineStatus(pol: PurchaseOrderLine) { | |||||
| val stockInLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(pol.id!!) | |||||
| val qcLines = stockInLines.filter { it.status == StockInLineStatus.PENDING.status | |||||
| || it.status == StockInLineStatus.ESCALATED.status } | |||||
| val receivingLines = stockInLines.filter { it.status == StockInLineStatus.RECEIVED.status | |||||
| || it.status == StockInLineStatus.RECEIVING.status } | |||||
| // TODO: also check the qty | |||||
| if (stockInLines.isEmpty()) { // No Stock In Line | |||||
| pol.apply { status = PurchaseOrderLineStatus.PENDING } | |||||
| } else { | |||||
| if (qcLines.isEmpty()) { // No pending QC lines | |||||
| if (receivingLines.isEmpty()) { // No receiving lines | |||||
| pol.apply { | |||||
| status = PurchaseOrderLineStatus.COMPLETED | |||||
| } | |||||
| } else { // Only remain receiving lines | |||||
| pol.apply { | |||||
| status = PurchaseOrderLineStatus.RECEIVING | |||||
| } | |||||
| } | } | ||||
| polRepository.saveAndFlush(purchaseOrderLine) | |||||
| } else { | } else { | ||||
| // still have unQcedLines lines | |||||
| } | |||||
| } | |||||
| if (request.status == StockInLineStatus.COMPLETE.status || request.status == StockInLineStatus.REJECT.status) { | |||||
| // val unfinishedLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndStatusNotAndDeletedFalse(purchaseOrderLineId = request.purchaseOrderLineId, status = request.status!!) | |||||
| val unfinishedLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(purchaseOrderLineId = request.purchaseOrderLineId) | |||||
| .filter { | |||||
| it.status != StockInLineStatus.COMPLETE.status | |||||
| && it.status != StockInLineStatus.REJECT.status | |||||
| } | |||||
| println("unfinishedLines") | |||||
| println(unfinishedLines) | |||||
| if (unfinishedLines.isEmpty()) { | |||||
| // all stock in lines finished | |||||
| // change status of purchase order line | |||||
| val purchaseOrderLine = polRepository.findById(request.purchaseOrderLineId).orElseThrow() | |||||
| purchaseOrderLine.apply { | |||||
| status = PurchaseOrderLineStatus.COMPLETED | |||||
| // still have pending QC lines | |||||
| pol.apply { | |||||
| status = PurchaseOrderLineStatus.RECEIVING | |||||
| } | } | ||||
| polRepository.saveAndFlush(purchaseOrderLine) | |||||
| } else { | |||||
| // still have unfinished lines | |||||
| } | } | ||||
| } | } | ||||
| polRepository.saveAndFlush(pol) | |||||
| } | } | ||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| @Transactional | @Transactional | ||||
| @@ -353,7 +370,7 @@ open class StockInLineService( | |||||
| this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate// maybe need to change the request to LocalDateTime | this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate// maybe need to change the request to LocalDateTime | ||||
| this.productLotNo = request.productLotNo ?: this.productLotNo | this.productLotNo = request.productLotNo ?: this.productLotNo | ||||
| this.dnNo = request.dnNo ?: this.dnNo | this.dnNo = request.dnNo ?: this.dnNo | ||||
| this.dnDate = request.dnDate?.atStartOfDay() ?: this.dnDate | |||||
| // this.dnDate = request.dnDate?.atStartOfDay() ?: this.dnDate | |||||
| this.acceptedQty = request.acceptedQty | this.acceptedQty = request.acceptedQty | ||||
| this.demandQty = request.acceptQty | this.demandQty = request.acceptQty | ||||
| this.invoiceNo = request.invoiceNo | this.invoiceNo = request.invoiceNo | ||||
| @@ -372,7 +389,7 @@ open class StockInLineService( | |||||
| val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) | val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(request.itemId) | ||||
| val stockItemUom = itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(request.itemId) | val stockItemUom = itemUomRespository.findByItemIdAndStockUnitIsTrueAndDeletedIsFalse(request.itemId) | ||||
| val ratio = if (stockItemUom != null && purchaseItemUom != null) { | |||||
| val ratio = if (request.stockTakeLineId == null && stockItemUom != null && purchaseItemUom != null) { | |||||
| (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) | (purchaseItemUom.ratioN!! / purchaseItemUom.ratioD!!) / (stockItemUom.ratioN!! / stockItemUom.ratioD!!) | ||||
| } else { | } else { | ||||
| BigDecimal.ONE | BigDecimal.ONE | ||||
| @@ -416,76 +433,6 @@ open class StockInLineService( | |||||
| errorPosition = "request.acceptedQty", | errorPosition = "request.acceptedQty", | ||||
| ) | ) | ||||
| } | } | ||||
| // if (request.acceptedQty.compareTo(stockInLine.acceptedQty) != 0) { | |||||
| // // Partial Accept | |||||
| // if (request.acceptedQty <= BigDecimal(0)) { | |||||
| // return MessageResponse( | |||||
| // id = null, | |||||
| // code = null, | |||||
| // name = null, | |||||
| // type = "acceptedQty == 0", | |||||
| // message = "acceptedQty cannot be 0", | |||||
| // errorPosition = "request.acceptedQty", | |||||
| // ) | |||||
| // } | |||||
| // val newStockInLine = StockInLine() | |||||
| // newStockInLine.apply { | |||||
| // this.item = stockInLine.item | |||||
| // this.itemNo = stockInLine.itemNo | |||||
| // this.purchaseOrder = stockInLine.purchaseOrderLine?.purchaseOrder | |||||
| // this.purchaseOrderLine = stockInLine.purchaseOrderLine | |||||
| // this.productLotNo = stockInLine.productLotNo | |||||
| // this.dnNo = stockInLine.dnNo | |||||
| // this.invoiceNo = stockInLine.invoiceNo | |||||
| // this.remarks = stockInLine.remarks ?: request.remarks | |||||
| // this.receiptDate = stockInLine.receiptDate | |||||
| // this.stockIn = stockInLine.stockIn | |||||
| // this.demandQty = stockInLine.demandQty | |||||
| // this.acceptedQty = stockInLine.acceptedQty!!.minus(request.acceptedQty) | |||||
| // this.price = stockInLine.price | |||||
| // this.priceUnit = stockInLine.priceUnit | |||||
| // this.inventoryLot = stockInLine.inventoryLot | |||||
| // this.lotNo = stockInLine.lotNo | |||||
| // this.productionDate = stockInLine.productionDate | |||||
| // this.expiryDate = stockInLine.expiryDate | |||||
| // this.status = StockInLineStatus.RECEIVING.status// stockInLine.status // this does update status | |||||
| // this.user = stockInLine.user | |||||
| // } | |||||
| // saveQcResultWhenStockIn(request, stockInLine) | |||||
| // var savedInventoryLot: InventoryLot? = null | |||||
| // var savedInventoryLotLine: InventoryLotLine? = null // maybe remove this later | |||||
| // if (request.status == StockInLineStatus.RECEIVED.status) { | |||||
| // savedInventoryLot = saveInventoryLotWhenStockIn(request = request, stockInLine = stockInLine) | |||||
| // } | |||||
| // if (request.status == StockInLineStatus.COMPLETE.status) { | |||||
| // savedInventoryLotLine = | |||||
| // saveInventoryLotLineWhenStockIn(request = request, stockInLine = stockInLine) | |||||
| // } | |||||
| // stockInLine.apply { | |||||
| // this.acceptedQty = request.acceptedQty | |||||
| // this.status = StockInLineStatus.RECEIVED.status//request.status | |||||
| // this.inventoryLot = savedInventoryLot ?: stockInLine.inventoryLot | |||||
| // this.inventoryLotLine = savedInventoryLotLine | |||||
| // this.lotNo = savedInventoryLot?.lotNo ?: stockInLine.lotNo | |||||
| // } | |||||
| // | |||||
| // val stockInLineEntries = listOf(stockInLine, newStockInLine) | |||||
| // val savedEntries = stockInLineRepository.saveAllAndFlush(stockInLineEntries) | |||||
| // val ids = savedEntries.map { it.id!! } | |||||
| // val lineInfoList = stockInLineRepository.findStockInLineInfoByIdInAndDeletedFalse(ids) | |||||
| // // check if all line completed | |||||
| // updatePurchaseOrderLineStatus(request) | |||||
| // | |||||
| // return MessageResponse( | |||||
| // id = stockInLine.id, | |||||
| // code = null, | |||||
| // name = null, | |||||
| // type = "Save success", | |||||
| // message = "created 2 stock in line", | |||||
| // errorPosition = null, | |||||
| // entity = lineInfoList | |||||
| // ) | |||||
| // } | |||||
| } else if (request.qcAccept == false) { | } else if (request.qcAccept == false) { | ||||
| if (request.escalationLog != null) { | if (request.escalationLog != null) { | ||||
| // Escalated | // Escalated | ||||
| @@ -505,7 +452,14 @@ open class StockInLineService( | |||||
| } | } | ||||
| val savedStockInLine = saveAndFlush(stockInLine) | val savedStockInLine = saveAndFlush(stockInLine) | ||||
| // check if all line completed | // check if all line completed | ||||
| updatePurchaseOrderLineStatus(request) | |||||
| if (savedStockInLine.purchaseOrderLine != null) { | |||||
| val pol = savedStockInLine.purchaseOrderLine | |||||
| if (pol != null) { | |||||
| updatePurchaseOrderLineStatus(pol) | |||||
| updatePurchaseOrderStatus(pol.purchaseOrder!!) | |||||
| } | |||||
| } | |||||
| val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedStockInLine.id!!) | val lineInfo = stockInLineRepository.findStockInLineInfoByIdAndDeletedFalse(savedStockInLine.id!!) | ||||
| return MessageResponse( | return MessageResponse( | ||||
| @@ -3,48 +3,55 @@ package com.ffii.fpsms.modules.stock.service | |||||
| import com.ffii.core.support.AbstractBaseEntityService | import com.ffii.core.support.AbstractBaseEntityService | ||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | import com.ffii.fpsms.modules.master.web.models.MessageResponse | ||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder | |||||
| import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository | import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository | ||||
| import com.ffii.fpsms.modules.stock.entity.StockIn | |||||
| import com.ffii.fpsms.modules.stock.entity.StockInLine | |||||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.StockInRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.* | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveStockInRequest | import com.ffii.fpsms.modules.stock.web.model.SaveStockInRequest | ||||
| import com.ffii.fpsms.modules.stock.web.model.StockInStatus | import com.ffii.fpsms.modules.stock.web.model.StockInStatus | ||||
| import org.springframework.stereotype.Service | import org.springframework.stereotype.Service | ||||
| import org.springframework.transaction.annotation.Transactional | import org.springframework.transaction.annotation.Transactional | ||||
| import java.io.IOException | import java.io.IOException | ||||
| import kotlin.jvm.optionals.getOrNull | |||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | |||||
| import java.util.* | |||||
| @Service | @Service | ||||
| open class StockInService( | open class StockInService( | ||||
| private val jdbcDao: JdbcDao, | private val jdbcDao: JdbcDao, | ||||
| private val stockInRepository: StockInRepository, | private val stockInRepository: StockInRepository, | ||||
| private val purchaseOrderRepository: PurchaseOrderRepository, | private val purchaseOrderRepository: PurchaseOrderRepository, | ||||
| ): AbstractBaseEntityService<StockIn, Long, StockInRepository>(jdbcDao, stockInRepository) { | |||||
| private val stockTakeRepository: StockTakeRepository, | |||||
| ): AbstractBaseEntityService<StockIn, Long, StockInRepository>(jdbcDao, stockInRepository) { | |||||
| @Throws(IOException::class) | @Throws(IOException::class) | ||||
| @Transactional | @Transactional | ||||
| open fun create(request: SaveStockInRequest): MessageResponse { | open fun create(request: SaveStockInRequest): MessageResponse { | ||||
| val stockIn = StockIn() | val stockIn = StockIn() | ||||
| val purchaseOrder = if (request.purchaseOrderId != null) | |||||
| purchaseOrderRepository.findByIdAndDeletedFalse(request.purchaseOrderId).orElseThrow() | |||||
| else return MessageResponse( | |||||
| id = null, | |||||
| code = null, | |||||
| name = null, | |||||
| type = "Found Null", | |||||
| message = "request.purchaseOrderId is null", | |||||
| errorPosition = "Stock In" | |||||
| ) | |||||
| stockIn.apply { | stockIn.apply { | ||||
| code = purchaseOrder.code | |||||
| supplier = purchaseOrder.supplier | |||||
| this.purchaseOrder = purchaseOrder | |||||
| // shop = purchaseOrder.shop | |||||
| orderDate = purchaseOrder.orderDate | |||||
| estimatedCompleteDate = purchaseOrder.estimatedArrivalDate?.toLocalDate() | |||||
| completeDate = purchaseOrder.completeDate | |||||
| status = StockInStatus.PENDING.status | status = StockInStatus.PENDING.status | ||||
| orderDate = LocalDateTime.now() | |||||
| code = LocalDateTime.now().toString().take(19) | |||||
| } | |||||
| if (request.purchaseOrderId != null) { | |||||
| val purchaseOrder : PurchaseOrder = purchaseOrderRepository.findByIdAndDeletedFalse(request.purchaseOrderId).orElseThrow(); | |||||
| stockIn.apply { | |||||
| code = purchaseOrder.code | |||||
| supplier = purchaseOrder.supplier | |||||
| this.purchaseOrder = purchaseOrder | |||||
| // shop = purchaseOrder.shop | |||||
| orderDate = purchaseOrder.orderDate | |||||
| estimatedCompleteDate = purchaseOrder.estimatedArrivalDate?.toLocalDate() | |||||
| completeDate = purchaseOrder.completeDate | |||||
| } | |||||
| } | } | ||||
| if (request.stockTakeId != null) { | |||||
| val stockTake = request.stockTakeId.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) } | |||||
| stockIn.apply { | |||||
| this.stockTake = stockTake | |||||
| } | |||||
| } | |||||
| val savedStockIn = saveAndFlush(stockIn) | val savedStockIn = saveAndFlush(stockIn) | ||||
| return MessageResponse( | return MessageResponse( | ||||
| id = savedStockIn.id, | id = savedStockIn.id, | ||||
| @@ -0,0 +1,40 @@ | |||||
| package com.ffii.fpsms.modules.stock.service | |||||
| import com.ffii.fpsms.modules.master.entity.UomConversionRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.StockTakeLine | |||||
| import com.ffii.fpsms.modules.stock.entity.StockTakeLineRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.StockTakeRepository | |||||
| import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatus | |||||
| import com.ffii.fpsms.modules.stock.web.model.SaveStockTakeLineRequest | |||||
| import org.springframework.stereotype.Service | |||||
| @Service | |||||
| open class StockTakeLineService( | |||||
| val stockTakeLineRepository: StockTakeLineRepository, | |||||
| val stockTakeRepository: StockTakeRepository, | |||||
| val inventoryLotLineRepository: InventoryLotLineRepository, | |||||
| val uomConversionRepository: UomConversionRepository, | |||||
| val inventoryLotLineService: InventoryLotLineService, | |||||
| ) { | |||||
| fun saveStockTakeLine(request: SaveStockTakeLineRequest): StockTakeLine { | |||||
| val stockTake = request.stockTakeId?.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) } ?: throw Exception("Cant Find Stock Take"); | |||||
| val inventoryLotLine = request.inventoryLotLineId?.let { inventoryLotLineRepository.findByIdAndDeletedIsFalse(it) }; | |||||
| val uom = request.uomId?.let { uomConversionRepository.findByIdAndDeletedFalse(it) }; | |||||
| val status = request.status?.let { _status -> StockTakeLineStatus.entries.find { it.value == _status} } | |||||
| val stockTakeLine = request.id?.let { stockTakeLineRepository.findByIdAndDeletedIsFalse(it) } ?: StockTakeLine(); | |||||
| // will skip null | |||||
| stockTake.let { stockTakeLine.stockTake = it } | |||||
| inventoryLotLine.let { stockTakeLine.inventoryLotLine = it } | |||||
| uom.let { stockTakeLine.uom = it } | |||||
| request.initialQty?.let { stockTakeLine.initialQty = it } | |||||
| request.finalQty?.let { stockTakeLine.finalQty = it } | |||||
| request.completeDate?.let { stockTakeLine.completeDate = it } | |||||
| status?.let { stockTakeLine.status = it } | |||||
| request.remarks?.let { stockTakeLine.remarks = it } | |||||
| return stockTakeLineRepository.save(stockTakeLine); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,233 @@ | |||||
| package com.ffii.fpsms.modules.stock.service | |||||
| import com.ffii.core.utils.JwtTokenUtil | |||||
| import com.ffii.fpsms.modules.common.CodeGenerator | |||||
| import com.ffii.fpsms.modules.master.entity.WarehouseRepository | |||||
| import com.ffii.fpsms.modules.master.service.ItemUomService | |||||
| import com.ffii.fpsms.modules.master.service.ItemsService | |||||
| import com.ffii.fpsms.modules.master.service.WarehouseService | |||||
| import com.ffii.fpsms.modules.master.web.models.SaveWarehouseRequest | |||||
| import com.ffii.fpsms.modules.stock.entity.InventoryLotLineRepository | |||||
| import com.ffii.fpsms.modules.stock.entity.StockTake | |||||
| import com.ffii.fpsms.modules.stock.entity.StockTakeRepository | |||||
| import com.ffii.fpsms.modules.stock.enums.StockTakeLineStatus | |||||
| import com.ffii.fpsms.modules.stock.enums.StockTakeStatus | |||||
| import com.ffii.fpsms.modules.stock.web.model.* | |||||
| import org.apache.poi.ss.usermodel.Cell | |||||
| import org.apache.poi.ss.usermodel.CellType | |||||
| import org.apache.poi.ss.usermodel.Sheet | |||||
| import org.apache.poi.ss.usermodel.Workbook | |||||
| import org.slf4j.Logger | |||||
| import org.slf4j.LoggerFactory | |||||
| import org.springframework.stereotype.Service | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | |||||
| @Service | |||||
| class StockTakeService( | |||||
| val stockTakeRepository: StockTakeRepository, | |||||
| val warehouseRepository: WarehouseRepository, | |||||
| val warehouseService: WarehouseService, | |||||
| val stockInService: StockInService, | |||||
| val stockInLineService: StockInLineService, | |||||
| val itemsService: ItemsService, | |||||
| val itemUomService: ItemUomService, | |||||
| val stockTakeLineService: StockTakeLineService, | |||||
| val inventoryLotLineRepository: InventoryLotLineRepository, | |||||
| ) { | |||||
| val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) | |||||
| fun assignStockTakeNo(): String { | |||||
| val prefix = "ST" | |||||
| val midfix = CodeGenerator.DEFAULT_MIDFIX | |||||
| val latestCode = stockTakeRepository.findLatestCodeByPrefix("${prefix}-${midfix}") | |||||
| return CodeGenerator.generateOrderNo(prefix = prefix, latestCode = latestCode) | |||||
| } | |||||
| fun saveStockTake(request: SaveStockTakeRequest): StockTake { | |||||
| val stockTake = request.id?.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) } ?: StockTake(); | |||||
| val status = request.status?.let { _status -> StockTakeStatus.entries.find { it.value == _status } } | |||||
| request.code?.let { stockTake.code = it } | |||||
| if (stockTake.code == null) { | |||||
| stockTake.code = assignStockTakeNo() | |||||
| } | |||||
| request.planStart?.let { stockTake.planStart = it } | |||||
| request.planEnd?.let { stockTake.planEnd = it } | |||||
| request.actualStart?.let { stockTake.actualStart = it } | |||||
| request.actualEnd?.let { stockTake.actualEnd = it } | |||||
| status?.let { stockTake.status = it } | |||||
| request.remarks?.let { stockTake.remarks = it } | |||||
| return stockTakeRepository.save(stockTake); | |||||
| } | |||||
| // ---------------------------------------------- Import Excel ---------------------------------------------- // | |||||
| fun getCellStringValue(cell: Cell): String { | |||||
| return cell.let { | |||||
| when (it.cellType) { | |||||
| CellType.STRING -> it.stringCellValue | |||||
| CellType.NUMERIC -> it.numericCellValue | |||||
| else -> "" | |||||
| } | |||||
| }.toString() | |||||
| } | |||||
| fun importExcel(workbook: Workbook?): String { | |||||
| logger.info("--------- Start - Import Stock Take Excel -------"); | |||||
| if (workbook == null) { | |||||
| logger.error("No Excel Import"); | |||||
| return "Import Excel failure"; | |||||
| } | |||||
| val sheet: Sheet = workbook.getSheetAt(0); | |||||
| // Columns | |||||
| val COLUMN_ITEM_CODE_INDEX = 6; | |||||
| val COLUMN_FLOOR_INDEX = 11; | |||||
| val COLUMN_PLACE_INDEX = 12; | |||||
| val COLUMN_WAREHOSE_INDEX = 13; | |||||
| val COLUMN_ZONE_INDEX = 14; | |||||
| val COLUMN_SLOT_INDEX = 15; | |||||
| val COLUMN_QTY_INDEX = 18; | |||||
| val START_ROW_INDEX = 2; | |||||
| // Start Import | |||||
| val startTime = LocalDateTime.now(); | |||||
| val stCode = assignStockTakeNo() | |||||
| val saveStockTakeReq = SaveStockTakeRequest( | |||||
| code = stCode, | |||||
| planStart = startTime, | |||||
| planEnd = startTime, | |||||
| actualStart = startTime, | |||||
| actualEnd = null, | |||||
| status = StockTakeStatus.PENDING.value, | |||||
| remarks = null | |||||
| ) | |||||
| val savedStockTake = saveStockTake(saveStockTakeReq); | |||||
| val saveStockInReq = SaveStockInRequest( | |||||
| code = stCode, | |||||
| stockTakeId = savedStockTake.id | |||||
| ) | |||||
| val savedStockIn = stockInService.create(saveStockInReq) | |||||
| for (i in START_ROW_INDEX ..< sheet.lastRowNum) { | |||||
| val row = sheet.getRow(i) | |||||
| // Warehouse | |||||
| val warehouse = try { | |||||
| val floor = getCellStringValue(row.getCell(COLUMN_FLOOR_INDEX)) | |||||
| val place = getCellStringValue(row.getCell(COLUMN_PLACE_INDEX)) | |||||
| val code = getCellStringValue(row.getCell(COLUMN_WAREHOSE_INDEX)) | |||||
| val zone = getCellStringValue(row.getCell(COLUMN_ZONE_INDEX)) | |||||
| val slot = getCellStringValue(row.getCell(COLUMN_SLOT_INDEX)) | |||||
| // logger.info("Warehouse code - zone - slot: ${row.getCell(COLUMN_WAREHOSE_INDEX).cellType} - ${row.getCell(COLUMN_ZONE_INDEX).cellType} - ${row.getCell(COLUMN_SLOT_INDEX).cellType}") | |||||
| val defaultCapacity = BigDecimal(10000) | |||||
| val warehouseCode = "$code-$zone-$slot" | |||||
| val warehouseName = "$floor-$place" | |||||
| val existingWarehouse = warehouseService.findByCode(warehouseCode) | |||||
| if (existingWarehouse != null) { | |||||
| existingWarehouse | |||||
| } else { | |||||
| val warehouseRequest = SaveWarehouseRequest( | |||||
| code = warehouseCode, | |||||
| name = warehouseName, | |||||
| description = warehouseName, | |||||
| capacity = defaultCapacity | |||||
| ) | |||||
| warehouseService.saveWarehouse(warehouseRequest) | |||||
| } | |||||
| } catch (e: Exception) { | |||||
| logger.error("Import Error (Warehouse Error): ${e.message}") | |||||
| null | |||||
| } ?: continue | |||||
| // Item | |||||
| val item = try { | |||||
| // logger.info("Item Type: ${row.getCell(COLUMN_ITEM_CODE_INDEX).cellType}") | |||||
| val itemCode = row.getCell(COLUMN_ITEM_CODE_INDEX).stringCellValue; | |||||
| itemsService.findByCode(itemCode) | |||||
| } catch (e: Exception) { | |||||
| logger.error("Import Error (Item Code Error): ${e.message}") | |||||
| null | |||||
| } ?: continue | |||||
| // Stock Take Line (First Create) | |||||
| val qty = try { | |||||
| // logger.info("Qty Type: ${row.getCell(COLUMN_QTY_INDEX).cellType}") | |||||
| row.getCell(COLUMN_QTY_INDEX).numericCellValue.toBigDecimal() | |||||
| } catch (e: Exception) { | |||||
| logger.error("Import Error (Qty Error): ${e.message}") | |||||
| null | |||||
| } ?: continue | |||||
| val uom = itemUomService.findStockUnitByItemId(item.id!!)?.uom | |||||
| val saveStockTakeLineReq = SaveStockTakeLineRequest( | |||||
| stockTakeId = savedStockTake.id, | |||||
| initialQty = qty, | |||||
| finalQty = qty, | |||||
| uomId = uom?.id, | |||||
| status = StockTakeLineStatus.PENDING.value, | |||||
| remarks = null, | |||||
| ) | |||||
| val savedStockTakeLine = stockTakeLineService.saveStockTakeLine(saveStockTakeLineReq) | |||||
| // Stock In Line | |||||
| val expiryDate = LocalDateTime.now().plusMonths(6) // TODO: Add expiry date from excel | |||||
| val saveStockInLineReq = SaveStockInLineRequest( | |||||
| stockInId = savedStockIn.id, | |||||
| itemId = item.id!!, | |||||
| acceptedQty = qty, | |||||
| acceptQty = qty, | |||||
| expiryDate = expiryDate.toLocalDate(), | |||||
| warehouseId = warehouse.id, | |||||
| stockTakeLineId = savedStockTakeLine.id, | |||||
| qcAccept = true, | |||||
| status = StockInLineStatus.PENDING.status | |||||
| ) | |||||
| val savedStockInLine = stockInLineService.create(saveStockInLineReq) | |||||
| saveStockInLineReq.apply { | |||||
| id = savedStockInLine.id | |||||
| } | |||||
| stockInLineService.update(saveStockInLineReq) | |||||
| val inventoryLotLines = SaveInventoryLotLineForSil( | |||||
| qty = qty, | |||||
| warehouseId = warehouse.id | |||||
| ) | |||||
| saveStockInLineReq.apply { | |||||
| status = StockInLineStatus.RECEIVED.status | |||||
| this.inventoryLotLines = if (qty > BigDecimal.ZERO) mutableListOf(inventoryLotLines) else null | |||||
| } | |||||
| val finalStockInLine = stockInLineService.update(saveStockInLineReq) | |||||
| // Stock Take Line (Second Completed) | |||||
| val inventoryLotLine = inventoryLotLineRepository.findByInventoryLotStockInLineIdAndWarehouseId(inventoryLotStockInLineId = finalStockInLine.id!!, warehouseId = warehouse.id!!) | |||||
| saveStockTakeLineReq.apply { | |||||
| id = savedStockTakeLine.id | |||||
| status = StockTakeLineStatus.COMPLETED.value | |||||
| completeDate = LocalDateTime.now() | |||||
| inventoryLotLineId = inventoryLotLine?.id | |||||
| } | |||||
| stockTakeLineService.saveStockTakeLine(saveStockTakeLineReq) | |||||
| logger.info("[Stock Take]: Saved item '${item.name}' to warehouse '${warehouse.code}'") | |||||
| } | |||||
| // End of Import | |||||
| val endTime = LocalDateTime.now(); | |||||
| saveStockTakeReq.apply { | |||||
| id = savedStockTake.id | |||||
| actualEnd = endTime | |||||
| status = StockTakeStatus.COMPLETED.value | |||||
| } | |||||
| saveStockTake(saveStockTakeReq) | |||||
| logger.info("--------- End - Import Stock Take Excel -------") | |||||
| return "Import Excel success"; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| package com.ffii.fpsms.modules.stock.web | |||||
| import com.ffii.fpsms.modules.stock.service.StockTakeService | |||||
| import jakarta.servlet.http.HttpServletRequest | |||||
| import org.apache.poi.ss.usermodel.Workbook | |||||
| import org.apache.poi.util.IOUtils | |||||
| import org.apache.poi.xssf.streaming.SXSSFWorkbook | |||||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook | |||||
| import org.springframework.http.ResponseEntity | |||||
| import org.springframework.web.bind.ServletRequestBindingException | |||||
| import org.springframework.web.bind.annotation.PostMapping | |||||
| import org.springframework.web.bind.annotation.RequestMapping | |||||
| import org.springframework.web.bind.annotation.RestController | |||||
| import org.springframework.web.multipart.MultipartHttpServletRequest | |||||
| @RequestMapping("/stockTake") | |||||
| @RestController | |||||
| class StockTakeController( | |||||
| val stockTakeService: StockTakeService, | |||||
| ) { | |||||
| @PostMapping("/import") | |||||
| @Throws(ServletRequestBindingException::class) | |||||
| fun importExcel(request: HttpServletRequest): ResponseEntity<*> { | |||||
| var workbook: Workbook? = null; | |||||
| try { | |||||
| println("Start") | |||||
| val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList") | |||||
| workbook = XSSFWorkbook(multipartFile?.inputStream) | |||||
| // Use SXSSFWorkbook instead of XSSFWorkbook | |||||
| // IOUtils.setByteArrayMaxOverride(300_000_000) | |||||
| // workbook = SXSSFWorkbook(XSSFWorkbook(multipartFile?.inputStream)) | |||||
| // Optional: Configure SXSSF to limit memory usage | |||||
| // workbook.setCompressTempFiles(true) | |||||
| } catch (e: Exception) { | |||||
| println("Excel Wrong") | |||||
| println(e) | |||||
| } | |||||
| return ResponseEntity.ok(stockTakeService.importExcel(workbook)) | |||||
| } | |||||
| } | |||||
| @@ -33,32 +33,36 @@ data class SaveStockInRequest( | |||||
| val completeDate: LocalDateTime? = null, | val completeDate: LocalDateTime? = null, | ||||
| val status: String? = null, | val status: String? = null, | ||||
| val stockOutId: Long? = null, | val stockOutId: Long? = null, | ||||
| val stockTakeId: Long? = null, | |||||
| // val m18 | // val m18 | ||||
| ) | ) | ||||
| data class SaveStockInLineRequest( | data class SaveStockInLineRequest( | ||||
| var id: Long?, | |||||
| var purchaseOrderId: Long, | |||||
| var purchaseOrderLineId: Long, | |||||
| var id: Long? = null, | |||||
| var stockInId: Long? = null, | |||||
| var stockTakeId: Long? = null, | |||||
| var stockTakeLineId: Long? = null, | |||||
| var itemId: Long, | var itemId: Long, | ||||
| var purchaseOrderId: Long? = null, | |||||
| var purchaseOrderLineId: Long? = null, | |||||
| var acceptedQty: BigDecimal, | var acceptedQty: BigDecimal, | ||||
| var acceptQty: BigDecimal?, | var acceptQty: BigDecimal?, | ||||
| var acceptedWeight: BigDecimal?, | |||||
| var acceptedWeight: BigDecimal? = null, | |||||
| var status: String?, | var status: String?, | ||||
| var expiryDate: LocalDate?, | var expiryDate: LocalDate?, | ||||
| var productLotNo: String?, | |||||
| var dnNo: String?, | |||||
| var invoiceNo: String?, | |||||
| var remarks: String?, | |||||
| var dnDate: LocalDate?, | |||||
| var receiptDate: LocalDate?, | |||||
| var productionDate: LocalDate?, | |||||
| var qcAccept: Boolean?, | |||||
| var qcResult: List<SaveQcResultRequest>?, | |||||
| var escalationLog: SaveEscalationLogRequest?, | |||||
| var warehouseId: Long?, | |||||
| var rejectQty: BigDecimal?, | |||||
| var inventoryLotLines: List<SaveInventoryLotLineForSil>? | |||||
| var productLotNo: String? = null, | |||||
| var dnNo: String? = null, | |||||
| var invoiceNo: String? = null, | |||||
| var remarks: String? = null, | |||||
| // var dnDate: LocalDate? = null, | |||||
| var receiptDate: LocalDate? = null, | |||||
| var productionDate: LocalDate? = null, | |||||
| var qcAccept: Boolean? = null, | |||||
| var qcResult: List<SaveQcResultRequest>? = null, | |||||
| var escalationLog: SaveEscalationLogRequest? = null, | |||||
| var warehouseId: Long? = null, | |||||
| var rejectQty: BigDecimal? = null, | |||||
| var inventoryLotLines: List<SaveInventoryLotLineForSil>? = null | |||||
| ) | ) | ||||
| data class SaveInventoryLotLineForSil ( | data class SaveInventoryLotLineForSil ( | ||||
| @@ -0,0 +1,16 @@ | |||||
| package com.ffii.fpsms.modules.stock.web.model | |||||
| import java.math.BigDecimal | |||||
| import java.time.LocalDateTime | |||||
| data class SaveStockTakeLineRequest( | |||||
| var id: Long? = null, | |||||
| val stockTakeId: Long?, | |||||
| var inventoryLotLineId: Long? = null, | |||||
| val initialQty: BigDecimal?, | |||||
| val finalQty: BigDecimal?, | |||||
| val uomId: Long?, | |||||
| var completeDate: LocalDateTime? = null, | |||||
| var status: String?, | |||||
| val remarks: String?, | |||||
| ) | |||||
| @@ -0,0 +1,14 @@ | |||||
| package com.ffii.fpsms.modules.stock.web.model | |||||
| import java.time.LocalDateTime | |||||
| data class SaveStockTakeRequest( | |||||
| var id: Long? = null, | |||||
| val code: String?, | |||||
| val planStart: LocalDateTime?, | |||||
| val planEnd: LocalDateTime?, | |||||
| val actualStart: LocalDateTime?, | |||||
| var actualEnd: LocalDateTime?, | |||||
| var status: String?, | |||||
| val remarks: String?, | |||||
| ) | |||||
| @@ -0,0 +1,24 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update_stock_in | |||||
| ALTER TABLE `stock_in` | |||||
| ADD COLUMN `stockTakeId` INT NULL DEFAULT NULL AFTER `stockOutId`, | |||||
| ADD INDEX `FK_STOCK_IN_ON_STOCKTAKEID` (`stockTakeId` ASC) VISIBLE; | |||||
| ; | |||||
| ALTER TABLE `stock_in` | |||||
| ADD CONSTRAINT `FK_STOCK_IN_ON_STOCKTAKEID` | |||||
| FOREIGN KEY (`stockTakeId`) | |||||
| REFERENCES `stock_take` (`id`) | |||||
| ON DELETE RESTRICT | |||||
| ON UPDATE RESTRICT; | |||||
| ALTER TABLE `stock_in_line` | |||||
| ADD COLUMN `stockTakeLineId` INT NULL DEFAULT NULL AFTER `purchaseOrderLineId`, | |||||
| ADD INDEX `FK_STOCK_IN_LINE_ON_STOCKTAKELINEID` (`stockTakeLineId` ASC) VISIBLE; | |||||
| ; | |||||
| ALTER TABLE `stock_in_line` | |||||
| ADD CONSTRAINT `FK_STOCK_IN_LINE_ON_STOCKTAKELINEID` | |||||
| FOREIGN KEY (`stockTakeLineId`) | |||||
| REFERENCES `stock_take_line` (`id`) | |||||
| ON DELETE RESTRICT | |||||
| ON UPDATE RESTRICT; | |||||
| @@ -0,0 +1,11 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update_stock_take_line | |||||
| ALTER TABLE `stock_take_line` | |||||
| DROP FOREIGN KEY `FK_STOCK_TAKE_LINE_ON_INVENTORYLOTLINEID`; | |||||
| ALTER TABLE `stock_take_line` | |||||
| CHANGE COLUMN `inventoryLotLineId` `inventoryLotLineId` INT NULL ; | |||||
| ALTER TABLE `stock_take_line` | |||||
| ADD CONSTRAINT `FK_STOCK_TAKE_LINE_ON_INVENTORYLOTLINEID` | |||||
| FOREIGN KEY (`inventoryLotLineId`) | |||||
| REFERENCES `inventory_lot_line` (`id`); | |||||
| @@ -0,0 +1,5 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset enson:altertable_enson | |||||
| ALTER TABLE `fpsmsdb`.`do_pick_order` | |||||
| ADD COLUMN `hide` TINYINT NULL DEFAULT '0' AFTER `deleted`; | |||||