| @@ -4,6 +4,7 @@ import java.time.LocalDate | |||
| import java.time.format.DateTimeFormatter | |||
| object CodeGenerator { | |||
| // Default Value for Lot No | |||
| private var dateFormat = DateTimeFormatter.ofPattern("yyMMdd") | |||
| fun generateCode(prefix: String, itemId: Long, count: Int): String { | |||
| // prefix = "ITEM" || "LOT" | |||
| @@ -13,4 +14,33 @@ object CodeGenerator { | |||
| val countStr = String.format("%04d", count) | |||
| 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 supplierName: String = "", | |||
| val dnNo: String = "", | |||
| val dnDate: String = "", | |||
| val receiptDate: String = "", | |||
| val poNo: String = "", | |||
| val supplierId: String = "", | |||
| val itemNo: String = "", | |||
| @@ -115,7 +115,7 @@ open class MailTemplateService( | |||
| val args = mapOf( | |||
| "\${supplierName}" to supplierName, | |||
| "\${dnNo}" to dnNo, | |||
| "\${dnDate}" to dnDate, | |||
| "\${receiptDate}" to receiptDate, | |||
| "\${poNo}" to poNo, | |||
| "\${supplierId}" to supplierId, | |||
| "\${itemNo}" to itemNo, | |||
| @@ -149,7 +149,7 @@ open class MailTemplateService( | |||
| val supplierName = po?.supplier?.name ?: "N/A" | |||
| val supplierEmail = (if((po?.supplier?.contactEmail).isNullOrEmpty()) "N/A" else po?.supplier?.contactEmail) ?: "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 supplierId = po?.supplier?.code ?: "N/A" // Id? | |||
| val itemNo = item?.code ?: "N/A" | |||
| @@ -193,7 +193,7 @@ open class MailTemplateService( | |||
| supplierEmail = supplierEmail, | |||
| supplierName = supplierName, | |||
| dnNo = dnNo, | |||
| dnDate = dnDate, | |||
| receiptDate = receiptDate, | |||
| poNo = poNo, | |||
| supplierId = supplierId, | |||
| itemNo = itemNo, | |||
| @@ -62,6 +62,9 @@ class DoPickOrder { | |||
| @Column(name = "deleted") | |||
| var deleted: Boolean = false | |||
| @Column(name = "hide", nullable = false) | |||
| var hide: Boolean = false | |||
| // Default constructor for Hibernate | |||
| constructor() | |||
| @@ -2,6 +2,8 @@ package com.ffii.fpsms.modules.jobOrder.entity | |||
| import com.fasterxml.jackson.annotation.JsonManagedReference | |||
| 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.ProductionScheduleLine | |||
| import com.ffii.fpsms.modules.user.entity.User | |||
| @@ -42,10 +44,11 @@ open class JobOrder : BaseEntity<Long>() { | |||
| @Column(name = "actualQty", precision = 14, scale = 2) | |||
| open var actualQty: BigDecimal? = null | |||
| @Size(max = 100) | |||
| // @Size(max = 100) | |||
| @NotNull | |||
| @Column(name = "status", nullable = false, length = 100) | |||
| open var status: String? = null | |||
| @Convert(converter = JobOrderStatusConverter::class) | |||
| open var status: JobOrderStatus? = null | |||
| @Size(max = 500) | |||
| @Column(name = "remarks", length = 500) | |||
| @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository | |||
| @Repository | |||
| interface JobOrderBomMaterialRepository : AbstractRepository<JobOrderBomMaterial, Long> { | |||
| fun findByJobOrderIdAndItemId(jobOrderId: Long, itemId: Long): JobOrderBomMaterial?; | |||
| } | |||
| @@ -20,6 +20,24 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||
| @Query( | |||
| nativeQuery = true, | |||
| 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 | |||
| jo.id, | |||
| jo.code, | |||
| @@ -32,7 +50,7 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> { | |||
| 'id', jobm.id, | |||
| 'code', i.code, | |||
| 'name', i.name, | |||
| 'lotNo', il.lotNo, | |||
| 'pickedLotNo', pln.pickedLotNo, | |||
| 'reqQty', jobm.reqQty, | |||
| 'uom', uc.udfudesc, | |||
| '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 items i on i.id = jobm.itemId | |||
| 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 | |||
| group by jo.id, uc2.udfudesc | |||
| limit 1 | |||
| """ | |||
| ) | |||
| fun findJobOrderDetailById(id: Long): JobOrderDetailWithJsonString?; | |||
| @Query( | |||
| nativeQuery = true, | |||
| value = """ | |||
| @@ -14,6 +14,8 @@ interface JobOrderInfo { | |||
| @get:Value("#{target.bom.item.itemUoms.^[salesUnit == true && deleted == false]?.uom.udfudesc}") | |||
| val uom: String; | |||
| @get:Value("#{target.status.value}") | |||
| val status: String; | |||
| } | |||
| @@ -42,8 +44,13 @@ data class JobOrderDetailPickLine( | |||
| val id: Long?, | |||
| val code: String?, | |||
| val name: String?, | |||
| val lotNo: String?, | |||
| val pickedLotNo: List<JobOrderDetailPickedLotNo>?, | |||
| val reqQty: BigDecimal?, | |||
| val uom: 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> { | |||
| val zero = BigDecimal.ZERO | |||
| 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 salesUnit = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } | |||
| @@ -35,7 +35,7 @@ open class JobOrderBomMaterialService( | |||
| reqQty = bm.qty?.times(proportion) ?: zero, | |||
| uomId = salesUnit?.uom?.id | |||
| ) | |||
| } ?: listOf() | |||
| } ?: listOf() | |||
| 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.JobOrderDetailPickLine | |||
| 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.JobOrderReleaseRequest | |||
| 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 prodScheduleLine = request.prodScheduleLineId?.let { productionScheduleLineRepository.findById(it).getOrNull() } | |||
| val code = assignJobNo() | |||
| val status = JobOrderStatus.entries.find { it.value == request.status } | |||
| jo.apply { | |||
| this.code = code | |||
| @@ -119,7 +121,7 @@ open class JobOrderService( | |||
| planStart = request.planStart ?: LocalDateTime.now() | |||
| planEnd = request.planEnd ?: LocalDateTime.now() | |||
| reqQty = request.reqQty | |||
| status = request.status | |||
| this.status = status | |||
| type = request.type | |||
| this.approver = approver | |||
| this.prodScheduleLine = prodScheduleLine | |||
| @@ -141,7 +143,7 @@ open class JobOrderService( | |||
| open fun releaseJobOrder(request: JobOrderReleaseRequest): MessageResponse { | |||
| val jo = request.id.let { jobOrderRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() | |||
| jo.apply { | |||
| status = "pending" | |||
| status = JobOrderStatus.PENDING | |||
| } | |||
| jobOrderRepository.save(jo) | |||
| @@ -168,7 +170,7 @@ open class JobOrderService( | |||
| type = null, | |||
| message = 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 findAllByBomIdAndDeletedIsFalse(bomId: Long): List<BomMaterial> | |||
| fun findByBomIdAndItemId(bomId: Long, itemId: Long): BomMaterial? | |||
| } | |||
| @@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository | |||
| @Repository | |||
| 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 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.fpsms.modules.master.entity.projections.WarehouseCombo | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface WarehouseRepository : AbstractRepository<Warehouse, Long> { | |||
| 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 { | |||
| val item = itemsRepository.findByCodeAndDeletedFalse(req.code) ?: itemsRepository.findByNameAndDeletedFalse(req.name) | |||
| 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.isFloat = req.isFloat | |||
| this.isDense = req.isDense | |||
| @@ -111,10 +112,11 @@ open class BomService( | |||
| private fun saveBomMaterial(req: ImportBomMatRequest): BomMaterial { | |||
| // 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.itemName = req.item!!.name | |||
| this.isConsumable = req.isConsumable | |||
| @@ -124,12 +126,12 @@ open class BomService( | |||
| this.uom = req.uom | |||
| this.uomName = req.uomName | |||
| this.bom = req.bom | |||
| } | |||
| return bomMaterialRepository.saveAndFlush(bomMaterial) | |||
| } | |||
| 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.equipment = req.equipment | |||
| this.description = req.description | |||
| @@ -142,7 +144,8 @@ open class BomService( | |||
| return bomProcessRepository.saveAndFlush(bomProcess) | |||
| } | |||
| 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.bomMaterial = req.bomMaterial | |||
| } | |||
| @@ -489,7 +492,7 @@ open class BomService( | |||
| // val folder = File(folderPath) | |||
| val resolver = PathMatchingResourcePatternResolver() | |||
| // 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") | |||
| println("size: ${excels.size}") | |||
| val logExcel = ClassPathResource("excelTemplate/bom_excel_issue_log.xlsx") | |||
| @@ -210,6 +210,10 @@ open class ItemsService( | |||
| return itemsRepository.findByIdAndDeletedFalse(id); | |||
| } | |||
| open fun findByCode(code: String): Items? { | |||
| return itemsRepository.findByCodeAndDeletedFalse(code); | |||
| } | |||
| open fun findByM18Id(m18Id: Long): Items? { | |||
| 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.WarehouseRepository | |||
| 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.stock.entity.InventoryLotRepository | |||
| 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.StockInService | |||
| import org.springframework.stereotype.Service | |||
| import kotlin.jvm.optionals.getOrNull | |||
| @Service | |||
| open class WarehouseService( | |||
| @@ -28,4 +30,25 @@ open class WarehouseService( | |||
| open fun findCombo(): List<WarehouseCombo> { | |||
| 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 routerRepository: RouterRepository, | |||
| private val doPickOrderRecordRepository: DoPickOrderRecordRepository, | |||
| private val doPickOrderRepository: DoPickOrderRepository | |||
| private val doPickOrderRepository: DoPickOrderRepository, | |||
| private val userRepository: UserRepository | |||
| ) : AbstractBaseEntityService<PickOrder, Long, PickOrderRepository>(jdbcDao, pickOrderRepository) { | |||
| @@ -2765,19 +2767,22 @@ if (existingRecords.isNotEmpty()) { | |||
| println("❌ Pick order not found with ID: $pickOrderId") | |||
| 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}") | |||
| if (pickOrder.type?.value != "do") { | |||
| println("❌ Pick order is not of type 'do': ${pickOrder.type?.value}") | |||
| 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}") | |||
| return emptyList() | |||
| } | |||
| val deliveryOrder = pickOrder.deliveryOrder | |||
| val shop = deliveryOrder?.shop | |||
| val supplier = deliveryOrder?.supplier | |||
| @@ -3164,17 +3169,21 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||
| println("❌ User not found: $userId") | |||
| 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 | |||
| val allAssignedPickOrders = pickOrderRepository.findAllByAssignToAndStatusIn( | |||
| user, | |||
| listOf(PickOrderStatus.PENDING, PickOrderStatus.RELEASED, PickOrderStatus.COMPLETED) | |||
| statusList | |||
| ).filter { it.deliveryOrder != null } // Only pick orders with doId | |||
| 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 | |||
| val filteredPickOrders = if (allAssignedPickOrders.isNotEmpty()) { | |||
| val filteredPickOrders = if (visiblePickOrders.isNotEmpty()) { | |||
| // Check if there are any RELEASED orders assigned to this user (active work) | |||
| val assignedReleasedOrders = allAssignedPickOrders.filter { | |||
| 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}") | |||
| // 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}") | |||
| // ✅ Transform flat results into hierarchical structure | |||
| @@ -3432,6 +3441,7 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map<String, A | |||
| "pickOrderLines" to pickOrderLinesMap as Any? | |||
| ) | |||
| } | |||
| // Fix the type issues in the getPickOrdersByDateAndStore method | |||
| open fun getPickOrdersByDateAndStore(storeId: String): Map<String, Any?> { | |||
| println("=== Debug: getPickOrdersByDateAndStore ===") | |||
| @@ -3663,4 +3673,132 @@ open fun confirmLotSubstitution(req: LotSubstitutionConfirmRequest): MessageResp | |||
| 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 { | |||
| 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 inLine = stockInLine.filter { it.purchaseOrderLineId == thisPol.id } | |||
| .filter { it.dnDate != null } | |||
| .filter { it.receiptDate != null } | |||
| val categoryCode = thisPol.item?.qcCategory?.code | |||
| val qcItems = thisPol.item?.qcCategory?.qcItemCategory?.map { | |||
| QcForPoLine( | |||
| @@ -25,8 +25,8 @@ interface EscalationLogInfo { | |||
| @get:Value("#{target.stockInLine?.dnNo}") | |||
| 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}") | |||
| val item: String? | |||
| @@ -13,6 +13,9 @@ import org.springframework.data.repository.query.Param | |||
| @Repository | |||
| interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long> { | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): InventoryLotLine; | |||
| 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") | |||
| @@ -35,6 +38,9 @@ interface InventoryLotLineRepository : AbstractRepository<InventoryLotLine, Long | |||
| fun findAllByIdIn(ids: List<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: InventoryLotLineStatus): List<InventoryLotLine> | |||
| } | |||
| @@ -40,6 +40,10 @@ open class StockIn : BaseEntity<Long>() { | |||
| @JoinColumn(name = "stockOutId") | |||
| open var stockOutId: StockOut? = null | |||
| @ManyToOne | |||
| @JoinColumn(name = "stockTakeId") | |||
| open var stockTake: StockTake? = null | |||
| @Column(name = "orderDate") | |||
| open var orderDate: LocalDateTime? = null | |||
| @@ -37,6 +37,10 @@ open class StockInLine : BaseEntity<Long>() { | |||
| @JoinColumn(name = "purchaseOrderLineId") | |||
| open var purchaseOrderLine: PurchaseOrderLine? = null | |||
| @ManyToOne | |||
| @JoinColumn(name = "stockTakeLineId") | |||
| open var stockTakeLine: StockTakeLine? = null | |||
| @NotNull | |||
| @ManyToOne | |||
| @JoinColumn(name = "stockInId", nullable = false) | |||
| @@ -1,9 +1,19 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface StockInRepository : AbstractRepository<StockIn, Long> { | |||
| fun findByIdAndDeletedFalse(id: 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 | |||
| 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.validation.constraints.NotNull | |||
| import jakarta.validation.constraints.Size | |||
| @@ -28,10 +30,11 @@ open class StockTake: BaseEntity<Long>() { | |||
| @Column(name = "actualEnd") | |||
| open var actualEnd: LocalDateTime? = null | |||
| @Size(max = 20) | |||
| // @Size(max = 20) | |||
| @NotNull | |||
| @Column(name = "status", nullable = false, length = 20) | |||
| open var status: String? = null | |||
| @Convert(converter = StockTakeStatusConverter::class) | |||
| open var status: StockTakeStatus? = null | |||
| @Size(max = 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.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.validation.constraints.NotNull | |||
| import jakarta.validation.constraints.Size | |||
| @@ -16,7 +18,6 @@ open class StockTakeLine : BaseEntity<Long>() { | |||
| @JoinColumn(name = "stockTakeId", nullable = false) | |||
| open var stockTake: StockTake? = null | |||
| @NotNull | |||
| @ManyToOne | |||
| @JoinColumn(name = "inventoryLotLineId", nullable = false) | |||
| open var inventoryLotLine: InventoryLotLine? = null | |||
| @@ -34,10 +35,11 @@ open class StockTakeLine : BaseEntity<Long>() { | |||
| @Column(name = "completeDate") | |||
| open var completeDate: LocalDateTime? = null | |||
| @Size(max = 20) | |||
| // @Size(max = 20) | |||
| @NotNull | |||
| @Column(name = "status", nullable = false, length = 20) | |||
| open var status: String? = null | |||
| @Convert(converter = StockTakeLineStatusConverter::class) | |||
| open var status: StockTakeLineStatus? = null | |||
| @Size(max = 500) | |||
| @Column(name = "remarks", length = 500) | |||
| @@ -2,7 +2,9 @@ package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface StockTakeLineRepository : AbstractRepository<StockTakeLine, Long> { | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): StockTakeLine?; | |||
| } | |||
| @@ -1,8 +1,16 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.data.jpa.repository.Query | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| 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}") | |||
| val itemType: String | |||
| val dnNo: String | |||
| val dnDate: LocalDateTime? | |||
| // val dnDate: LocalDateTime? | |||
| // val qcDecision: LocalDateTime? | |||
| @get:Value("#{target.escalationLog.^[status.value == 'pending']?.handler?.id}") | |||
| 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.WarehouseRepository | |||
| 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.enums.PurchaseOrderLineStatus | |||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus | |||
| @@ -50,7 +52,7 @@ data class QrContent(val itemId: Long, val stockInLineId: Long) | |||
| @Service | |||
| open class StockInLineService( | |||
| private val jdbcDao: JdbcDao, | |||
| private val purchaseOrderRepository: PurchaseOrderRepository, | |||
| private val poRepository: PurchaseOrderRepository, | |||
| private val polRepository: PurchaseOrderLineRepository, | |||
| private val qcItemsRepository: QcItemRepository, | |||
| private val qcResultRepository: QcResultRepository, | |||
| @@ -65,6 +67,7 @@ open class StockInLineService( | |||
| private val warehouseRepository: WarehouseRepository, | |||
| private val itemUomRespository: ItemUomRespository, | |||
| private val printerService: PrinterService, | |||
| private val stockTakeLineRepository: StockTakeLineRepository, | |||
| ): AbstractBaseEntityService<StockInLine, Long, StockInLineRepository>(jdbcDao, stockInLineRepository) { | |||
| open fun getStockInLineInfo(stockInLineId: Long): StockInLineInfo { | |||
| @@ -77,44 +80,74 @@ open class StockInLineService( | |||
| @Transactional | |||
| open fun create(request: SaveStockInLineRequest): MessageResponse { | |||
| 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) { | |||
| 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 | |||
| 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 { | |||
| this.item = item | |||
| itemNo = item.code | |||
| this.purchaseOrder = purchaseOrderLine.purchaseOrder | |||
| this.purchaseOrderLine = purchaseOrderLine | |||
| this.stockIn = stockIn | |||
| acceptedQty = request.acceptedQty | |||
| dnNo = request.dnNo | |||
| dnDate = request.dnDate?.atStartOfDay() | |||
| receiptDate = request.receiptDate?.atStartOfDay() ?: LocalDateTime.now() | |||
| productLotNo = request.productLotNo | |||
| 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( | |||
| 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", | |||
| message = "save success", | |||
| errorPosition = null, | |||
| @@ -156,7 +189,7 @@ open class StockInLineService( | |||
| itemId = 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!!) | |||
| } else { | |||
| (line.qty) | |||
| @@ -267,66 +300,50 @@ open class StockInLineService( | |||
| @Throws(IOException::class) | |||
| @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) | |||
| @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 { | |||
| // 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) | |||
| @Transactional | |||
| @@ -353,7 +370,7 @@ open class StockInLineService( | |||
| this.productionDate = request.productionDate?.atStartOfDay() ?: this.productionDate// maybe need to change the request to LocalDateTime | |||
| this.productLotNo = request.productLotNo ?: this.productLotNo | |||
| 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.demandQty = request.acceptQty | |||
| this.invoiceNo = request.invoiceNo | |||
| @@ -372,7 +389,7 @@ open class StockInLineService( | |||
| val purchaseItemUom = itemUomRespository.findByItemIdAndPurchaseUnitIsTrueAndDeletedIsFalse(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!!) | |||
| } else { | |||
| BigDecimal.ONE | |||
| @@ -416,76 +433,6 @@ open class StockInLineService( | |||
| 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) { | |||
| if (request.escalationLog != null) { | |||
| // Escalated | |||
| @@ -505,7 +452,14 @@ open class StockInLineService( | |||
| } | |||
| val savedStockInLine = saveAndFlush(stockInLine) | |||
| // 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!!) | |||
| return MessageResponse( | |||
| @@ -3,48 +3,55 @@ package com.ffii.fpsms.modules.stock.service | |||
| import com.ffii.core.support.AbstractBaseEntityService | |||
| import com.ffii.core.support.JdbcDao | |||
| 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.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.StockInStatus | |||
| import org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import java.io.IOException | |||
| import kotlin.jvm.optionals.getOrNull | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| import java.util.* | |||
| @Service | |||
| open class StockInService( | |||
| private val jdbcDao: JdbcDao, | |||
| private val stockInRepository: StockInRepository, | |||
| private val purchaseOrderRepository: PurchaseOrderRepository, | |||
| ): AbstractBaseEntityService<StockIn, Long, StockInRepository>(jdbcDao, stockInRepository) { | |||
| private val stockTakeRepository: StockTakeRepository, | |||
| ): AbstractBaseEntityService<StockIn, Long, StockInRepository>(jdbcDao, stockInRepository) { | |||
| @Throws(IOException::class) | |||
| @Transactional | |||
| open fun create(request: SaveStockInRequest): MessageResponse { | |||
| 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 { | |||
| 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 | |||
| 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) | |||
| return MessageResponse( | |||
| 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 status: String? = null, | |||
| val stockOutId: Long? = null, | |||
| val stockTakeId: Long? = null, | |||
| // val m18 | |||
| ) | |||
| 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 purchaseOrderId: Long? = null, | |||
| var purchaseOrderLineId: Long? = null, | |||
| var acceptedQty: BigDecimal, | |||
| var acceptQty: BigDecimal?, | |||
| var acceptedWeight: BigDecimal?, | |||
| var acceptedWeight: BigDecimal? = null, | |||
| var status: String?, | |||
| 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 ( | |||
| @@ -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`; | |||