ソースを参照

[Stock Take & Stock In & Bom & Job Order] import stock take excel & update jo

master
cyril.tsui 2ヶ月前
コミット
5e8ba2b5bc
39個のファイルの変更705行の追加90行の削除
  1. +30
    -0
      src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt
  2. +5
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt
  3. +1
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt
  4. +21
    -4
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
  5. +8
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt
  6. +10
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnum.kt
  7. +18
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnumConverter.kt
  8. +2
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt
  9. +5
    -3
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  10. +2
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterialRepository.kt
  11. +1
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessMaterialRepository.kt
  12. +2
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt
  13. +5
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt
  14. +12
    -9
      src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt
  15. +4
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
  16. +23
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt
  17. +11
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/models/SaveWarehouseRequest.kt
  18. +6
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  19. +4
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockIn.kt
  20. +4
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt
  21. +9
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockInRepository.kt
  22. +5
    -2
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt
  23. +5
    -3
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLine.kt
  24. +2
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLineRepository.kt
  25. +8
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRepository.kt
  26. +7
    -0
      src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnum.kt
  27. +18
    -0
      src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnumConverter.kt
  28. +6
    -0
      src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnum.kt
  29. +18
    -0
      src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnumConverter.kt
  30. +46
    -29
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  31. +21
    -19
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInService.kt
  32. +40
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeLineService.kt
  33. +218
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt
  34. +43
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeController.kt
  35. +20
    -16
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt
  36. +16
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeLineRequest.kt
  37. +14
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeRequest.kt
  38. +24
    -0
      src/main/resources/db/changelog/changes/20250918_01_cyril/01_update_stock_in_line.sql
  39. +11
    -0
      src/main/resources/db/changelog/changes/20250918_01_cyril/02_update_stock_take_line.sql

+ 30
- 0
src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt ファイルの表示

@@ -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("-")
}
}

+ 5
- 2
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt ファイルの表示

@@ -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)


+ 1
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt ファイルの表示

@@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository

@Repository
interface JobOrderBomMaterialRepository : AbstractRepository<JobOrderBomMaterial, Long> {
fun findByJobOrderIdAndItemId(jobOrderId: Long, itemId: Long): JobOrderBomMaterial?;
}

+ 21
- 4
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt ファイルの表示

@@ -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
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 = """


+ 8
- 1
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt ファイルの表示

@@ -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?,
)

+ 10
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnum.kt ファイルの表示

@@ -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")
}

+ 18
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnumConverter.kt ファイルの表示

@@ -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 }
}
}
}

+ 2
- 2
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt ファイルの表示

@@ -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
}


+ 5
- 3
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt ファイルの表示

@@ -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)
)
}
}

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterialRepository.kt ファイルの表示

@@ -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?
}

+ 1
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessMaterialRepository.kt ファイルの表示

@@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository

@Repository
interface BomProcessMaterialRepository : AbstractRepository<BomProcessMaterial, Long> {
fun findByBomProcessIdAndBomMaterialId(bomProcessId: Long, bomMaterialId: Long): BomProcessMaterial?;
}

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt ファイルの表示

@@ -16,4 +16,6 @@ interface BomRepository : AbstractRepository<Bom, Long> {
fun findByItemIdAndDeletedIsFalse(itemId: Serializable): Bom?

fun findBomComboByDeletedIsFalse(): List<BomCombo>

fun findByCodeAndDeletedIsFalse(code: String): Bom?
}

+ 5
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt ファイルの表示

@@ -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?;
}

+ 12
- 9
src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt ファイルの表示

@@ -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")


+ 4
- 0
src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt ファイルの表示

@@ -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)
}


+ 23
- 0
src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt ファイルの表示

@@ -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);
}
}

+ 11
- 0
src/main/java/com/ffii/fpsms/modules/master/web/models/SaveWarehouseRequest.kt ファイルの表示

@@ -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,
)

+ 6
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt ファイルの表示

@@ -12,6 +12,9 @@ import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus

@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")
@@ -34,6 +37,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>


+ 4
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockIn.kt ファイルの表示

@@ -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



+ 4
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt ファイルの表示

@@ -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)


+ 9
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockInRepository.kt ファイルの表示

@@ -1,9 +1,18 @@
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 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?
}

+ 5
- 2
src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt ファイルの表示

@@ -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)


+ 5
- 3
src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLine.kt ファイルの表示

@@ -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
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLineRepository.kt ファイルの表示

@@ -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?;
}

+ 8
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRepository.kt ファイルの表示

@@ -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?
}

+ 7
- 0
src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnum.kt ファイルの表示

@@ -0,0 +1,7 @@
package com.ffii.fpsms.modules.stock.enums

enum class StockTakeStatus(val value: String) {
PENDING("pending"),
STOCKTAKING("stockTaking"),
COMPLETED("completed"),
}

+ 18
- 0
src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnumConverter.kt ファイルの表示

@@ -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 }
}
}
}

+ 6
- 0
src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnum.kt ファイルの表示

@@ -0,0 +1,6 @@
package com.ffii.fpsms.modules.stock.enums

enum class StockTakeLineStatus(val value: String) {
PENDING("pending"),
COMPLETED("completed"),
}

+ 18
- 0
src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnumConverter.kt ファイルの表示

@@ -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 }
}
}
}

+ 46
- 29
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt ファイルの表示

@@ -65,6 +65,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 {
@@ -78,30 +79,36 @@ open class StockInLineService(
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 {
val purchaseOrderLine = request.purchaseOrderLineId?.let { polRepository.findById(it).getOrNull() }
val stockTakeLine = request.stockTakeLineId?.let { stockTakeLineRepository.findById(it).getOrNull() }
var stockIn = request.stockInId?.let { stockInRepository.findByIdAndDeletedIsFalse(it) }
?: request.purchaseOrderId?.let { stockInRepository.findByPurchaseOrderIdAndDeletedFalse(it) }
?: request.stockTakeId?.let { stockInRepository.findByStockTakeIdAndDeletedFalse(it) }
purchaseOrderLine?.apply {
status = PurchaseOrderLineStatus.RECEIVING
}
val pol = polRepository.saveAndFlush(purchaseOrderLine)
val pol = purchaseOrderLine?.let { polRepository.saveAndFlush(it) }
if (stockIn == null) {
stockIn = stockInService.create(SaveStockInRequest(purchaseOrderId = request.purchaseOrderId)).entity as StockIn
// update po status to receiving
val po = purchaseOrderRepository.findById(request.purchaseOrderId).orElseThrow()
po.apply {
status = PurchaseOrderStatus.RECEIVING
val po = request.purchaseOrderId?.let { purchaseOrderRepository.findById(it).getOrNull() }
if (po != null) {
po.apply {
status = PurchaseOrderStatus.RECEIVING
}
purchaseOrderRepository.save(po)
}
purchaseOrderRepository.save(po)
}
val allStockInLine = stockInLineRepository.findAllStockInLineInfoByStockInIdAndDeletedFalse(stockIn.id!!)
if (pol.qty!! < request.acceptedQty) {
if (pol != null && pol.qty!! < request.acceptedQty) {
throw BadRequestException()
}
stockInLine.apply {
this.item = item
itemNo = item.code
this.purchaseOrder = purchaseOrderLine.purchaseOrder
this.purchaseOrder = purchaseOrderLine?.purchaseOrder
this.purchaseOrderLine = purchaseOrderLine
this.stockTakeLine = stockTakeLine
this.stockIn = stockIn
acceptedQty = request.acceptedQty
dnNo = request.dnNo
@@ -156,7 +163,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)
@@ -269,9 +276,9 @@ open class StockInLineService(
@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 unfinishedLines = request.purchaseOrderId?.let { polRepository
.findAllByPurchaseOrderIdAndStatusNotAndDeletedIsFalse(purchaseOrderId = it, status = PurchaseOrderLineStatus.COMPLETED) }
if (unfinishedLines != null && unfinishedLines.isEmpty()) {
val po = purchaseOrderRepository.findById(request.purchaseOrderId).orElseThrow()
po.apply {
status = PurchaseOrderStatus.COMPLETED
@@ -287,42 +294,52 @@ open class StockInLineService(
fun updatePurchaseOrderLineStatus(request: SaveStockInLineRequest) {
println(request.status)
if (request.status == StockInLineStatus.RECEIVING.status) {
val unQcedLines = stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(purchaseOrderLineId = request.purchaseOrderLineId)
.filter {
val unQcedLines = request.purchaseOrderLineId?.let { stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(purchaseOrderLineId = it) }
?.filter {
it.status != StockInLineStatus.RECEIVING.status
&& it.status != StockInLineStatus.RECEIVED.status
&& it.status != StockInLineStatus.COMPLETE.status
&& it.status != StockInLineStatus.REJECT.status
}
if (unQcedLines.isEmpty()) {
if (unQcedLines != null && 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
val purchaseOrderLine = request.purchaseOrderLineId?.let { polRepository.findById(it).orElseThrow() }
if (purchaseOrderLine != null) {
purchaseOrderLine.apply {
status = PurchaseOrderLineStatus.RECEIVING
}
polRepository.saveAndFlush(purchaseOrderLine)
}
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 {
val unfinishedLines = request.purchaseOrderLineId?.let {
stockInLineRepository.findStockInLineInfoByPurchaseOrderLineIdAndDeletedFalse(
purchaseOrderLineId = it
)
}
?.filter {
it.status != StockInLineStatus.COMPLETE.status
&& it.status != StockInLineStatus.REJECT.status
}
println("unfinishedLines")
println(unfinishedLines)
if (unfinishedLines.isEmpty()) {
if (unfinishedLines != null && 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
val purchaseOrderLine = request.purchaseOrderLineId?.let {
polRepository.findById(it).orElseThrow()
}
if (purchaseOrderLine != null) {
purchaseOrderLine.apply {
status = PurchaseOrderLineStatus.COMPLETED
}
polRepository.saveAndFlush(purchaseOrderLine)
}
polRepository.saveAndFlush(purchaseOrderLine)
} else {
// still have unfinished lines
}
@@ -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


+ 21
- 19
src/main/java/com/ffii/fpsms/modules/stock/service/StockInService.kt ファイルの表示

@@ -4,45 +4,47 @@ 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.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

@Service
open class StockInService(
private val jdbcDao: JdbcDao,
private val stockInRepository: StockInRepository,
private val purchaseOrderRepository: PurchaseOrderRepository,
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"
)
// 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"
// )
val purchaseOrder = request.purchaseOrderId?.let { purchaseOrderRepository.findByIdAndDeletedFalse(request.purchaseOrderId).getOrNull() }
val stockTake = request.stockTakeId?.let { stockTakeRepository.findByIdAndDeletedIsFalse(it) }
stockIn.apply {
code = purchaseOrder.code
supplier = purchaseOrder.supplier
code = request.code ?: purchaseOrder?.code
supplier = purchaseOrder?.supplier
this.purchaseOrder = purchaseOrder
this.stockTake = stockTake
// shop = purchaseOrder.shop
orderDate = purchaseOrder.orderDate
estimatedCompleteDate = purchaseOrder.estimatedArrivalDate?.toLocalDate()
completeDate = purchaseOrder.completeDate
orderDate = purchaseOrder?.orderDate
estimatedCompleteDate = purchaseOrder?.estimatedArrivalDate?.toLocalDate()
completeDate = purchaseOrder?.completeDate
status = StockInStatus.PENDING.status
}
val savedStockIn = saveAndFlush(stockIn)


+ 40
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeLineService.kt ファイルの表示

@@ -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);
}
}

+ 218
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt ファイルの表示

@@ -0,0 +1,218 @@
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.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 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_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 code = row.getCell(COLUMN_WAREHOSE_INDEX).stringCellValue
val zone = row.getCell(COLUMN_ZONE_INDEX).stringCellValue
val slot = row.getCell(COLUMN_SLOT_INDEX).stringCellValue
// 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 existingWarehouse = warehouseService.findByCode(warehouseCode)

if (existingWarehouse != null) {
existingWarehouse
} else {
val warehouseRequest = SaveWarehouseRequest(
code = warehouseCode,
name = warehouseCode,
description = warehouseCode,
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";
}
}

+ 43
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeController.kt ファイルの表示

@@ -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))
}
}

+ 20
- 16
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt ファイルの表示

@@ -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 purchaseOrderId: Long? = null,
var purchaseOrderLineId: Long? = null,
var itemId: Long,
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 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?,
var rejectQty: BigDecimal?,
var inventoryLotLines: List<SaveInventoryLotLineForSil>?
var rejectQty: BigDecimal? = null,
var inventoryLotLines: List<SaveInventoryLotLineForSil>? = null,
var stockTakeLineId: Long? = null,
var stockTakeId: Long? = null,
var stockInId: Long? = null,
)

data class SaveInventoryLotLineForSil (


+ 16
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeLineRequest.kt ファイルの表示

@@ -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?,
)

+ 14
- 0
src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeRequest.kt ファイルの表示

@@ -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?,
)

+ 24
- 0
src/main/resources/db/changelog/changes/20250918_01_cyril/01_update_stock_in_line.sql ファイルの表示

@@ -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;

+ 11
- 0
src/main/resources/db/changelog/changes/20250918_01_cyril/02_update_stock_take_line.sql ファイルの表示

@@ -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`);

読み込み中…
キャンセル
保存