kelvin.yau 2 个月前
父节点
当前提交
a73ff9d69e
共有 47 个文件被更改,包括 971 次插入235 次删除
  1. +30
    -0
      src/main/java/com/ffii/fpsms/modules/common/CodeGenerator.kt
  2. +4
    -4
      src/main/java/com/ffii/fpsms/modules/common/mail/service/MailTemplateService.kt
  3. +3
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrder.kt
  4. +5
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrder.kt
  5. +1
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderBomMaterialRepository.kt
  6. +21
    -4
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
  7. +8
    -1
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/projections/JobOrderInfo.kt
  8. +10
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnum.kt
  9. +18
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/enums/JobOrderEnumConverter.kt
  10. +2
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderBomMaterialService.kt
  11. +5
    -3
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  12. +2
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/BomMaterialRepository.kt
  13. +1
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/BomProcessMaterialRepository.kt
  14. +2
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/BomRepository.kt
  15. +5
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt
  16. +12
    -9
      src/main/java/com/ffii/fpsms/modules/master/service/BomService.kt
  17. +4
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
  18. +23
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt
  19. +11
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/models/SaveWarehouseRequest.kt
  20. +152
    -14
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  21. +17
    -0
      src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt
  22. +1
    -1
      src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt
  23. +2
    -2
      src/main/java/com/ffii/fpsms/modules/stock/entity/EscalationLogInfo.kt
  24. +6
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt
  25. +4
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockIn.kt
  26. +4
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockInLine.kt
  27. +10
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockInRepository.kt
  28. +5
    -2
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTake.kt
  29. +5
    -3
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLine.kt
  30. +2
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeLineRepository.kt
  31. +8
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/StockTakeRepository.kt
  32. +1
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt
  33. +7
    -0
      src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnum.kt
  34. +18
    -0
      src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeEnumConverter.kt
  35. +6
    -0
      src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnum.kt
  36. +18
    -0
      src/main/java/com/ffii/fpsms/modules/stock/enums/StockTakeLineEnumConverter.kt
  37. +102
    -148
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt
  38. +29
    -22
      src/main/java/com/ffii/fpsms/modules/stock/service/StockInService.kt
  39. +40
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeLineService.kt
  40. +233
    -0
      src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt
  41. +43
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/StockTakeController.kt
  42. +21
    -17
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockInRequest.kt
  43. +16
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeLineRequest.kt
  44. +14
    -0
      src/main/java/com/ffii/fpsms/modules/stock/web/model/SaveStockTakeRequest.kt
  45. +24
    -0
      src/main/resources/db/changelog/changes/20250918_01_cyril/01_update_stock_in_line.sql
  46. +11
    -0
      src/main/resources/db/changelog/changes/20250918_01_cyril/02_update_stock_take_line.sql
  47. +5
    -0
      src/main/resources/db/changelog/changes/202510926_01_enson/01_altertable_enson.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("-")
}
}

+ 4
- 4
src/main/java/com/ffii/fpsms/modules/common/mail/service/MailTemplateService.kt 查看文件

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


+ 3
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrder.kt 查看文件

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


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


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

+ 152
- 14
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt 查看文件

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

+ 17
- 0
src/main/java/com/ffii/fpsms/modules/pickOrder/web/PickOrderController.kt 查看文件

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

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/purchaseOrder/service/PurchaseOrderService.kt 查看文件

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


+ 2
- 2
src/main/java/com/ffii/fpsms/modules/stock/entity/EscalationLogInfo.kt 查看文件

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


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt 查看文件

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

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


+ 10
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/StockInRepository.kt 查看文件

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

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

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/StockInLineInfo.kt 查看文件

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


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

+ 102
- 148
src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt 查看文件

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


+ 29
- 22
src/main/java/com/ffii/fpsms/modules/stock/service/StockInService.kt 查看文件

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


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

+ 233
- 0
src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeService.kt 查看文件

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

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

+ 21
- 17
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 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 (


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

+ 5
- 0
src/main/resources/db/changelog/changes/202510926_01_enson/01_altertable_enson.sql 查看文件

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

正在加载...
取消
保存