浏览代码

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
master
CANCERYS\kw093 7 小时前
父节点
当前提交
8ef41c2ece
共有 24 个文件被更改,包括 741 次插入1048 次删除
  1. +34
    -0
      deployLocal.bat
  2. +41
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
  3. +18
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  4. +5
    -1
      src/main/java/com/ffii/fpsms/modules/master/entity/EquipmentDetail.kt
  5. +9
    -11
      src/main/java/com/ffii/fpsms/modules/master/entity/Items.kt
  6. +4
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/Printer.kt
  7. +18
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLine.kt
  8. +14
    -1
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt
  9. +6
    -6
      src/main/java/com/ffii/fpsms/modules/master/entity/Warehouse.kt
  10. +1
    -15
      src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt
  11. +8
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/projections/ProdScheduleInfo.kt
  12. +122
    -851
      src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
  13. +382
    -126
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  14. +0
    -17
      src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt
  15. +0
    -15
      src/main/java/com/ffii/fpsms/modules/master/web/ItemsController.kt
  16. +16
    -4
      src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt
  17. +9
    -0
      src/main/java/com/ffii/fpsms/modules/master/web/models/ReleaseProdScheduleRequest.kt
  18. +9
    -0
      src/main/resources/db/changelog/changes/20251211_01_Fai/01_add_fields_to_psl.sql
  19. +5
    -0
      src/main/resources/db/changelog/changes/20251211_01_Fai/02_add_fields_to_psl.sql
  20. +3
    -1
      src/main/resources/db/changelog/changes/20251216_01_Enson/01_add_column.sql
  21. +5
    -0
      src/main/resources/db/changelog/changes/20251219_01_KelvinY/01_add_equipmentCode_to_equipment_detail.sql
  22. +5
    -0
      src/main/resources/db/changelog/changes/20251219_01_KelvinY/02_add_type_to_printer.sql
  23. +15
    -0
      src/main/resources/db/changelog/changes/20251219_01_KelvinY/03_modify_items_table.sql
  24. +12
    -0
      src/main/resources/db/changelog/changes/20251219_01_KelvinY/04_modify_warehouse_table.sql

+ 34
- 0
deployLocal.bat 查看文件

@@ -0,0 +1,34 @@
@echo off

set "PROJECT_PATH=C:\dev\FPSMS-backend"
set "SERVICE_NAME=FP-backend"

echo.
echo [1/4] Stopping service %SERVICE_NAME% ...
net stop "%SERVICE_NAME%" >nul 2>&1

echo.
echo [2/4] Waiting 5 seconds...
timeout /t 5 >nul

echo.
echo [3/4] Building new JAR...
cd /d "%PROJECT_PATH%"
call gradlew clean bootJar --no-daemon

if %errorlevel% neq 0 (
echo.
echo Build FAILED! Service not restarted.
timeout /t 10
exit /b 1
)

echo.
echo [4/4] Starting service %SERVICE_NAME% ...
net start "%SERVICE_NAME%"

echo.
echo SUCCESS! %SERVICE_NAME% is running with the new JAR!
echo Deployment completed: %date% %time%
echo Closing in 5 seconds...
timeout /t 5 >nul

+ 41
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt 查看文件

@@ -136,5 +136,46 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> {
pageable: Pageable pageable: Pageable
): Page<JobOrderInfo> ): Page<JobOrderInfo>


<<<<<<< HEAD
fun findAllByStatusIn(statuses: List<JobOrderStatus>): List<JobOrder> fun findAllByStatusIn(statuses: List<JobOrderStatus>): List<JobOrder>
=======
@Query(
nativeQuery = true,
value = """
select
jo.id,
jo.code,
b.name,
jo.reqQty,
b.outputQtyUom as unit,
uc2.udfudesc as uom,
json_arrayagg(
json_object(
'id', jobm.id,
'code', i.code,
'name', i.name,
'lotNo', il.lotNo,
'reqQty', jobm.reqQty,
'uom', uc.udfudesc,
'status', jobm.status
)
) as pickLines,
jo.status
from job_order jo
left join bom b on b.id = jo.bomId
left join item_uom iu on b.itemId = iu.itemId and iu.salesUnit = true
left join uom_conversion uc2 on uc2.id = iu.uomId
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
where jo.prodScheduleLineId = :prodScheduleLineId
group by jo.id, uc2.udfudesc
limit 1
"""
)
fun findJobOrderByProdScheduleLineId(prodScheduleLineId: Long): JobOrderDetailWithJsonString?;
>>>>>>> origin/master
} }

+ 18
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt 查看文件

@@ -264,6 +264,24 @@ open class JobOrderService(
) )
} }


open fun jobOrderDetailByPsId(prodScheduleLineId: Long): JobOrderDetail {
val sqlResult = jobOrderRepository.findJobOrderByProdScheduleLineId(prodScheduleLineId) ?: throw NoSuchElementException("Job Order not found: $prodScheduleLineId");

val type = object : TypeToken<List<JobOrderDetailPickLine>>() {}.type
val jsonResult = sqlResult.pickLines?.let { GsonUtils.stringToJson<List<JobOrderDetailPickLine>>(it, type) }
return JobOrderDetail(
id = sqlResult.id,
code = sqlResult.code,
itemCode = sqlResult.itemCode,
name = sqlResult.name,
reqQty = sqlResult.reqQty,
uom = sqlResult.uom,
pickLines = jsonResult,
status = sqlResult.status,
shortUom = sqlResult.shortUom
)
}

open fun jobOrderDetailByCode(code: String): JobOrderDetail { open fun jobOrderDetailByCode(code: String): JobOrderDetail {
val sqlResult = jobOrderRepository.findJobOrderDetailByCode(code) ?: throw NoSuchElementException("Job Order not found: $code"); val sqlResult = jobOrderRepository.findJobOrderDetailByCode(code) ?: throw NoSuchElementException("Job Order not found: $code");




+ 5
- 1
src/main/java/com/ffii/fpsms/modules/master/entity/EquipmentDetail.kt 查看文件

@@ -10,6 +10,11 @@ import jakarta.validation.constraints.Size
@Table(name = "equipment_detail") @Table(name = "equipment_detail")
@Entity @Entity
open class EquipmentDetail : BaseEntity<Long>() { open class EquipmentDetail : BaseEntity<Long>() {

@Size(max = 255)
@Column(name = "equipmentCode", nullable = true, length = 255)
open var equipmentCode: String? = null

@Size(max = 30) @Size(max = 30)
@NotNull @NotNull
@Column(name = "code", nullable = false, length = 30) @Column(name = "code", nullable = false, length = 30)
@@ -25,7 +30,6 @@ open class EquipmentDetail : BaseEntity<Long>() {
@Column(name = "description", nullable = false, length = 500) @Column(name = "description", nullable = false, length = 500)
open var description: String? = null open var description: String? = null



@Column(name = "equipmentTypeID") @Column(name = "equipmentTypeID")
open var equipmentTypeId: Long? = null open var equipmentTypeId: Long? = null




+ 9
- 11
src/main/java/com/ffii/fpsms/modules/master/entity/Items.kt 查看文件

@@ -34,15 +34,9 @@ open class Items : BaseEntity<Long>() {
@Column(name = "countryOfOrigin") @Column(name = "countryOfOrigin")
open var countryOfOrigin: String? = null open var countryOfOrigin: String? = null


@Column(name = "inventorySheet")
open var inventorySheet: String? = null

@Column(name = "store_id", nullable = true, length = 255) @Column(name = "store_id", nullable = true, length = 255)
open var store_id: String? = null open var store_id: String? = null


@Column(name = "storeLocation", nullable = true, length = 255)
open var storeLocation: String? = null

@Column(name = "warehouse", nullable = true, length = 255) @Column(name = "warehouse", nullable = true, length = 255)
open var warehouse: String? = null open var warehouse: String? = null


@@ -52,14 +46,17 @@ open class Items : BaseEntity<Long>() {
@Column(name = "slot", nullable = true, length = 255) @Column(name = "slot", nullable = true, length = 255)
open var slot: String? = null open var slot: String? = null


@Column(name = "MTMSPickRoutingID", nullable = true)
open var MTMSPickRoutingID: Int? = null

@Column(name = "LocationCode", nullable = true, length = 255) @Column(name = "LocationCode", nullable = true, length = 255)
open var LocationCode: String? = null open var LocationCode: String? = null


@Column(name = "company", nullable = true, length = 30)
open var company: String? = null
@Column(name = "isEgg", nullable = true)
open var isEgg: Boolean? = false

@Column(name = "isFee", nullable = true)
open var isFee: Boolean? = false

@Column(name = "isBag", nullable = true)
open var isBag: Boolean? = false


@Column(name = "maxQty") @Column(name = "maxQty")
open var maxQty: Double? = null open var maxQty: Double? = null
@@ -90,4 +87,5 @@ open class Items : BaseEntity<Long>() {
@JsonManagedReference @JsonManagedReference
@JoinColumn(name = "qcCategoryId") @JoinColumn(name = "qcCategoryId")
open var qcCategory: QcCategory? = null open var qcCategory: QcCategory? = null

} }

+ 4
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/Printer.kt 查看文件

@@ -18,6 +18,10 @@ open class Printer : BaseEntity<Long>() {
@Column(name = "name", length = 500) @Column(name = "name", length = 500)
open var name: String? = null open var name: String? = null


@Size(max = 255)
@Column(name = "type", nullable = true, length = 255)
open var type: String? = null

@Size(max = 500) @Size(max = 500)
@Column(name = "description", length = 500) @Column(name = "description", length = 500)
open var description: String? = null open var description: String? = null


+ 18
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLine.kt 查看文件

@@ -45,6 +45,24 @@ open class ProductionScheduleLine : BaseEntity<Long>() {
@Column(name = "weightingRef") @Column(name = "weightingRef")
open var weightingRef: Double = 0.0 open var weightingRef: Double = 0.0


@Column(name = "outputQty")
open var outputQty: Double = 0.0

@Column(name = "stockQty")
open var stockQty: Double = 0.0

@Column(name = "daysLeft")
open var daysLeft: Double = 0.0

@Column(name = "batchNeed")
open var batchNeed: Int = 0

@Column(name = "needNoOfJobOrder")
open var needNoOfJobOrder: Int = 0

@Column(name = "avgQtyLastMonth")
open var avgQtyLastMonth: Double = 0.0

@JsonBackReference @JsonBackReference
@ManyToOne @ManyToOne
@JoinColumn(name = "prodScheduleId", nullable = false) @JoinColumn(name = "prodScheduleId", nullable = false)


+ 14
- 1
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt 查看文件

@@ -202,8 +202,15 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
prod.scheduleAt, prod.scheduleAt,
prod.totalFGType, prod.totalFGType,
prod.totalEstProdCount, prod.totalEstProdCount,
prod.daysLeft,
prod.needNoOfJobOrder,
prod.itemPriority,
prod.batchNeed,
prod.stockQty,
prod.avgQtyLastMonth,
json_arrayagg( json_arrayagg(
json_object('id', prod.pslId, 'bomMaterials', prod.bomMaterials, 'jobNo', prod.jobNo, 'code', prod.code, 'name', prod.name, 'type', prod.type, 'demandQty', prod.demandQty, 'bomOutputQty', prod.bomOutputQty, 'uomName', prod.uomName, 'prodTimeInMinute', prod.prodTimeInMinute, 'priority', prod.priority, 'approved', prod.approved, 'proportion', prod.proportion)
json_object('id', prod.pslId, 'bomMaterials', prod.bomMaterials, 'jobNo', prod.jobNo, 'code', prod.code, 'name', prod.name, 'type', prod.type, 'demandQty', prod.demandQty, 'bomOutputQty', prod.bomOutputQty, 'uomName', prod.uomName, 'prodTimeInMinute', prod.prodTimeInMinute, 'priority', prod.priority, 'approved', prod.approved, 'proportion', prod.proportion, 'daysLeft', prod.daysLeft, 'needNoOfJobOrder', prod.needNoOfJobOrder, 'batchNeed', prod.batchNeed, 'avgQtyLastMonth', prod.avgQtyLastMonth, 'stockQty', prod.stockQty)
) as prodScheduleLines ) as prodScheduleLines
from ( from (
select select
@@ -213,6 +220,12 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
ps.totalEstProdCount, ps.totalEstProdCount,
psl.approverId is not null as approved, psl.approverId is not null as approved,
psl.id as pslId, psl.id as pslId,
psl.daysLeft,
psl.needNoOfJobOrder,
psl.avgQtyLastMonth,
psl.itemPriority,
psl.batchNeed,
psl.stockQty,
pm.bomMaterials, pm.bomMaterials,
pm.bomOutputQty, pm.bomOutputQty,
uc.udfudesc as uomName, uc.udfudesc as uomName,


+ 6
- 6
src/main/java/com/ffii/fpsms/modules/master/entity/Warehouse.kt 查看文件

@@ -32,16 +32,16 @@ open class Warehouse : BaseEntity<Long>() {


@Column(name = "store_id", nullable = false, length = 30) @Column(name = "store_id", nullable = false, length = 30)
open var store_id: String? = null open var store_id: String? = null
@Column(name = "storeLocation", nullable = true, length = 30)
open var storeLocation: String? = null
@Column(name = "stockTakeTable", nullable = true, length = 30)
open var stockTakeTable: String? = null
@Column(name = "company", nullable = true, length = 30)
open var company: String? = null

@Column(name = "warehouse", nullable = true, length = 30) @Column(name = "warehouse", nullable = true, length = 30)
open var warehouse: String? = null open var warehouse: String? = null

@Column(name = "area", nullable = true, length = 30) @Column(name = "area", nullable = true, length = 30)
open var area: String? = null open var area: String? = null

@Column(name = "slot", nullable = true, length = 30) @Column(name = "slot", nullable = true, length = 30)
open var slot: String? = null open var slot: String? = null

@Column(name = "stockTakeSection", nullable = true, length = 255)
open var stockTakeSection: String? = null
} }

+ 1
- 15
src/main/java/com/ffii/fpsms/modules/master/entity/WarehouseRepository.kt 查看文件

@@ -13,19 +13,5 @@ interface WarehouseRepository : AbstractRepository<Warehouse, Long> {
fun findByIdAndDeletedIsFalse(id: Serializable): Warehouse?; fun findByIdAndDeletedIsFalse(id: Serializable): Warehouse?;


fun findByCodeAndDeletedIsFalse(code: String): Warehouse?; fun findByCodeAndDeletedIsFalse(code: String): Warehouse?;

@Query("""
SELECT w FROM Warehouse w
WHERE w.code = :code
AND w.stockTakeTable = :stockTakeTable
AND w.company = :company
AND w.storeLocation = :storeLocation
AND w.deleted = false
""")
fun findByCodeAndStockTakeTableAndCompanyAndStoreLocationAndDeletedIsFalse(
code: String,
stockTakeTable: String,
company: String,
storeLocation: String
): Warehouse?
} }

+ 8
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/projections/ProdScheduleInfo.kt 查看文件

@@ -47,6 +47,14 @@ data class DetailedProdScheduleLineInfo(
val approved: Boolean?, val approved: Boolean?,
val proportion: BigDecimal?, val proportion: BigDecimal?,
val uomName: String?, val uomName: String?,
val daysLeft: BigDecimal?,
val onHandQty: BigDecimal?,
val stockQty: BigDecimal?,
val lastMonthAvgSales: BigDecimal?,
val avgQtyLastMonth: BigDecimal?,
val needNoOfJobOrder: BigDecimal?,
val batchNeed: BigDecimal?,
val itemPriority: BigDecimal?,
) )


data class DetailedProdScheduleLineBomMaterial ( data class DetailedProdScheduleLineBomMaterial (


+ 122
- 851
src/main/java/com/ffii/fpsms/modules/master/service/ItemsService.kt
文件差异内容过多而无法显示
查看文件


+ 382
- 126
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt 查看文件

@@ -15,6 +15,7 @@ import com.ffii.fpsms.modules.master.entity.*
import com.ffii.fpsms.modules.master.entity.projections.* import com.ffii.fpsms.modules.master.entity.projections.*
import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleLineRequest import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleLineRequest
import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleRequest
import com.ffii.fpsms.modules.master.web.models.SearchProdScheduleRequest import com.ffii.fpsms.modules.master.web.models.SearchProdScheduleRequest
import com.ffii.fpsms.modules.settings.service.SettingsService import com.ffii.fpsms.modules.settings.service.SettingsService
import com.ffii.fpsms.modules.stock.entity.Inventory import com.ffii.fpsms.modules.stock.entity.Inventory
@@ -31,6 +32,7 @@ import org.springframework.data.domain.PageRequest
import org.springframework.scheduling.support.CronTrigger import org.springframework.scheduling.support.CronTrigger
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import org.springframework.jdbc.core.BeanPropertyRowMapper
import java.lang.reflect.Type import java.lang.reflect.Type
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
@@ -43,6 +45,7 @@ import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.comparisons.maxOf


@Service @Service
open class ProductionScheduleService( open class ProductionScheduleService(
@@ -360,12 +363,109 @@ open class ProductionScheduleService(
) )
} }


@Transactional(rollbackFor = [java.lang.Exception::class])
open fun releaseProdSchedule(request: ReleaseProdScheduleRequest): MessageResponse {
// 1. Fetch data safely. Assuming searchProdScheduleLine returns a List<Map<String, Any>>
val data: List<Map<String, Any>> = searchProdScheduleLine(request.id)
if (data.isEmpty()) {
logger.warn("No production schedule lines found for ID: ${request.id}")
return MessageResponse(
id = request.id.toLong(),
message = "No lines to release. Parent ID: ${request.id}",
errorPosition = "data",
name = "",
code = "",
type = ""
)
}

val approver = SecurityUtils.getUser().getOrNull() // Get approver once

for (d in data) {
// 2. Safely cast the ID (Int) and convert it to Long for repository lookup
val prodScheduleLineIdInt = d["id"] as? Int
?: throw IllegalStateException("Production Schedule Line ID is missing or not an Int in search result map.")
val prodScheduleLineId = prodScheduleLineIdInt.toLong()
val prodScheduleLine = productionScheduleLineRepository.findById(prodScheduleLineId).getOrNull()
?: throw NoSuchElementException("Production Schedule Line with ID $prodScheduleLineId not found.")

try {
jobOrderService.jobOrderDetailByPsId(prodScheduleLineId)
} catch (e: NoSuchElementException) {
// 3. Fetch BOM, handling nullability safely
val item = prodScheduleLine.item
?: throw IllegalStateException("Item object is missing for Production Schedule Line $prodScheduleLineId.")

val itemId = item.id
?: throw IllegalStateException("Item ID is missing for Production Schedule Line $prodScheduleLineId.")

val bom = bomService.findByItemId(itemId)
?: throw NoSuchElementException("BOM not found for Item ID $itemId.")

// 4. Update Prod Schedule Line fields
prodScheduleLine.apply {
// Use bom.outputQty, ensuring it's treated as Double for prodQty
prodQty = bom.outputQty?.toDouble()
?: throw IllegalStateException("BOM output quantity is null for Item ID $itemId.")
approverId = approver?.id
}
productionScheduleLineRepository.save(prodScheduleLine)

// 5. Logging (optional but kept)
logger.info("prodScheduleLine.prodQty: ${prodScheduleLine.prodQty}")
logger.info("bom?.outputQty: ${bom.outputQty} ${bom.outputQtyUom}")

logger.info("[releaseProdSchedule] prodScheduleLine.needNoOfJobOrder:" + prodScheduleLine.needNoOfJobOrder)
repeat(prodScheduleLine.needNoOfJobOrder) {
// 6. Create Job Order
val joRequest = CreateJobOrderRequest(
bomId = bom.id, // bom is guaranteed non-null here
reqQty = bom.outputQty, // bom is guaranteed non-null here
approverId = approver?.id,
// CRUCIAL FIX: Use the line ID, not the parent schedule ID
prodScheduleLineId = prodScheduleLine.id!!
)

// Assuming createJobOrder returns the created Job Order (jo)
val jo = jobOrderService.createJobOrder(joRequest)
val createdJobOrderId = jo.id
?: throw IllegalStateException("Job Order creation failed: returned object ID is null.")

// 7. Create related job order data
jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(createdJobOrderId)
jobOrderProcessService.createJobOrderProcessesByJoId(createdJobOrderId)
productProcessService.createProductProcessByJobOrderId(createdJobOrderId)
}
}

// No need to fetch latest detail inside the loop
}

return MessageResponse(
id = request.id.toLong(),
message = "Successfully created Job Orders for all production schedule lines (Parent ID: ${request.id}).",
name = "",
code = "",
type = "",
errorPosition = null
// entity = mapOf("prodScheduleLines" to latestDetail?.prodScheduleLines)
)
}

@Transactional(rollbackFor = [java.lang.Exception::class]) @Transactional(rollbackFor = [java.lang.Exception::class])
open fun releaseProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse { open fun releaseProdScheduleLine(request: ReleaseProdScheduleLineRequest): MessageResponse {
val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException() val prodScheduleLine = request.id.let { productionScheduleLineRepository.findById(it).getOrNull() } ?: throw NoSuchElementException()
val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) } val bom = prodScheduleLine.item.id?.let { bomService.findByItemId(it) }
val approver = SecurityUtils.getUser().getOrNull() val approver = SecurityUtils.getUser().getOrNull()
val proportion = BigDecimal.ONE // request.demandQty.divide(bom?.outputQty ?: BigDecimal.ONE, 5, RoundingMode.HALF_UP)
val isSameQty = request.demandQty.equals(prodScheduleLine.prodQty) val isSameQty = request.demandQty.equals(prodScheduleLine.prodQty)


// Update Prod Schedule Type // Update Prod Schedule Type
@@ -385,107 +485,71 @@ open class ProductionScheduleService(
} }
productionScheduleLineRepository.save(prodScheduleLine) productionScheduleLineRepository.save(prodScheduleLine)


// Create Job Order
val joRequest = CreateJobOrderRequest(
bomId = bom?.id,
reqQty = request.demandQty,
approverId = approver?.id,
prodScheduleLineId = request.id,
//jobType = null,
)

val jo = jobOrderService.createJobOrder(joRequest)

// Create Job Order Bom Materials
if (bom?.bomMaterials != null) {
// Job Order Bom Material
val jobmRequests = bom.bomMaterials.map { bm ->
val demandQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO
val saleUnit = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) }
println("itemId: ${bm.item?.id} | itemNo: ${bm.item?.code} | itemName: ${bm.item?.name} | saleUnit: ${saleUnit?.uom?.udfudesc}")

val jobm = CreateJobOrderBomMaterialRequest(
joId = jo.id,
itemId = bm.item?.id,
reqQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO,
uomId = saleUnit?.uom?.id
)

jobm
}

if (jobmRequests != null) {
jobOrderBomMaterialService.createJobOrderBomMaterials(jobmRequests)
}

// Inventory
/* val inventories = bom.bomMaterials.map { bm ->
val demandQty = bm.qty?.times(proportion) ?: BigDecimal.ZERO

var inventory = bm.item?.id?.let { inventoryRepository.findByItemId(it).getOrNull() }
if (inventory != null) {
inventory.apply {
this.onHoldQty = (this.onHoldQty ?: BigDecimal.ZERO).plus(demandQty)
}
} else {
if (bm.item != null) {
val itemUom = bm.item?.id?.let { itemUomService.findSalesUnitByItemId(it) }
inventory = Inventory().apply {
item = bm.item
onHandQty = BigDecimal.ZERO
unavailableQty = BigDecimal.ZERO
this.onHoldQty = demandQty
uom = itemUom?.uom
status = "unavailable"
}
}
}

inventory
}.groupBy { it?.item } // Group by item
.mapNotNull { (item, invList) ->
if (invList.isNotEmpty()) {
invList[0]?.apply {
onHoldQty = invList.sumOf { it?.onHoldQty ?: BigDecimal.ZERO }
}
} else {
null
}
}

inventoryRepository.saveAllAndFlush(inventories) */
}

// Create Job Order Process
val jopRequests = bom?.bomProcesses?.map { bp ->
CreateJobOrderProcessRequest(
joId = jo.id,
processId = bp.process?.id,
seqNo = bp.seqNo,
//compare prodQty to bom outputQty
logger.info("request.demandQty:" + request.demandQty);
logger.info("prodScheduleLine.prodQty:" + prodScheduleLine.prodQty);
logger.info("bom?.outputQty:" + bom?.outputQty + "" + bom?.outputQtyUom);

logger.info("prodScheduleLine.needNoOfJobOrder:" + prodScheduleLine.needNoOfJobOrder)
repeat(prodScheduleLine.needNoOfJobOrder) {
// Create Job Order
val joRequest = CreateJobOrderRequest(
bomId = bom?.id,
reqQty = bom?.outputQty,
approverId = approver?.id,
prodScheduleLineId = request.id
) )
}


if (jopRequests != null) {
jobOrderProcessService.createJobOrderProcesses(jopRequests)
}
val jo = jobOrderService.createJobOrder(joRequest)


productProcessService.createProductProcessByJobOrderId(jo.id!!)
logger.info("jo created:" + jo.id!!)
jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(jo.id!!)
jobOrderProcessService.createJobOrderProcessesByJoId(jo.id!!)
productProcessService.createProductProcessByJobOrderId(jo.id!!)
}
// Get Latest Data // Get Latest Data
// val bomMaterials = prodScheduleLine.id?.let { productionScheduleLineRepository.getBomMaterials(it) } // val bomMaterials = prodScheduleLine.id?.let { productionScheduleLineRepository.getBomMaterials(it) }
val latestDetail = prodScheduleLine.productionSchedule.id?.let { detailedProdScheduleDetail(it) } val latestDetail = prodScheduleLine.productionSchedule.id?.let { detailedProdScheduleDetail(it) }


return MessageResponse( return MessageResponse(
id = request.id, id = request.id,
name = null, name = null,
code = jo.code,
//code = "",
//entity = null,
type = null, type = null,
message = "Success", message = "Success",
code = "",
entity = mapOf("prodScheduleLines" to latestDetail?.prodScheduleLines), entity = mapOf("prodScheduleLines" to latestDetail?.prodScheduleLines),
errorPosition = null errorPosition = null
) )
} }


//====================細排相關 START====================// //====================細排相關 START====================//
open fun searchProdScheduleLine(prodScheduleId: Int): List<Map<String, Any>> {

val args = mapOf(
"prodScheduleId" to prodScheduleId,
)

val sql = """
SELECT
psl.*
FROM production_schedule ps
LEFT JOIN production_schedule_line psl ON psl.prodScheduleId = ps.id
WHERE ps.deleted = FALSE
AND psl.deleted = FALSE
AND psl.prodScheduleId = :prodScheduleId
""";

return jdbcDao.queryForList(sql, args);
}


open fun getDailyProductionCount(assignDate: Int, selectedDate: LocalDateTime): Int { open fun getDailyProductionCount(assignDate: Int, selectedDate: LocalDateTime): Int {


@@ -507,65 +571,164 @@ open class ProductionScheduleService(
+ " Limit 1" + " Limit 1"
); );
return jdbcDao.queryForInt(sql.toString(), args); return jdbcDao.queryForInt(sql.toString(), args);
} }


open fun generateDetailedScheduleByDay(assignDate: Int, selectedDate: LocalDateTime, produceAt: LocalDateTime) {
val detailedScheduleOutputList = ArrayList<ProductionScheduleRecord>()
open fun getNeedQty(): List<NeedQtyRecord> {
val sql = """
SELECT
i.outputQty,
i.avgQtyLastMonth,
(i.onHandQty -500),
(i.onHandQty -500) * 1.0 / i.avgQtyLastMonth as daysLeft,
i.avgQtyLastMonth * 2 - stockQty as needQty,
i.stockQty,
CASE
WHEN stockQty * 1.0 / i.avgQtyLastMonth <= 1.9 THEN
CEIL((i.avgQtyLastMonth * 1.9 - stockQty) / i.outputQty)
ELSE 0
END AS needNoOfJobOrder,
CASE
WHEN stockQty * 1.0 / i.avgQtyLastMonth <= 1.9 THEN
CEIL((i.avgQtyLastMonth * 1.9 - stockQty) / i.outputQty)
ELSE 0
END AS batchNeed,
markDark + markFloat + markDense + markAS as priority,
i.*
FROM
(SELECT
(SELECT
ROUND(AVG(d.dailyQty) * 1.5)
FROM
(SELECT
SUM(dol.qty) AS dailyQty
FROM
delivery_order_line dol
LEFT JOIN delivery_order do ON dol.deliveryOrderId = do.id
WHERE
dol.itemId = items.id
-- AND MONTH(do.estimatedArrivalDate) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))
AND do.estimatedArrivalDate >= '2025-12-01' AND do.estimatedArrivalDate < '2025-12-11'
GROUP BY do.estimatedArrivalDate) AS d) AS avgQtyLastMonth,
inventory.onHandQty - 500 AS stockQty,
bom.outputQty,
bom.outputQtyUom,
(SELECT
udfudesc
FROM
delivery_order_line
LEFT JOIN uom_conversion ON delivery_order_line.uomId = uom_conversion.id
WHERE
delivery_order_line.itemId = bom.itemId
LIMIT 1) AS doUom,
items.code,
items.name,
bom.description,
inventory.onHandQty,
bom.itemId,
bom.id AS bomId,
CASE WHEN bom.isDark = 5 THEN 11
ELSE 0 END as markDark,
CASE WHEN bom.isFloat = 3 THEN 11
ELSE 0 END as markFloat,
CASE WHEN bom.isDense = 5 THEN 11
ELSE 0 END as markDense,
CASE WHEN bom.allergicSubstances = 5 THEN 11
ELSE 0 END as markAS,
inventory.id AS inventoryId
FROM
bom
LEFT JOIN items ON bom.itemId = items.id
LEFT JOIN inventory ON items.id = inventory.itemId
WHERE
bom.itemId != 16771) AS i
WHERE 1
and i.avgQtyLastMonth is not null
and i.onHandQty is not null
-- and (i.onHandQty -500) * 1.0 / i.avgQtyLastMonth <= 1.9
-- and avgQtyLastMonth - (onHandQty - 500) > 0

""".trimIndent()

val rows: List<Map<String, Any>> = jdbcDao.queryForList(sql)

return rows.map { row ->
NeedQtyRecord().apply {
name = row["name"] as String
id = (row["itemId"] as Number).toLong()
needQty = (row["needQty"] as Number).toDouble()
outputQty = (row["outputQty"] as Number).toDouble()
stockQty = (row["stockQty"] as Number).toDouble()
avgQtyLastMonth = (row["avgQtyLastMonth"] as Number).toDouble()
needNoOfJobOrder = (row["needNoOfJobOrder"] as Number).toLong()
daysLeft = (row["daysLeft"] as Number).toDouble()
batchNeed = row["batchNeed"] as Number


// check the produce date have manual changes.
val manualChange = productionScheduleRepository.findByTypeAndProduceAtAndDeletedIsFalse("detailed", produceAt)
if (manualChange != null) {
return;
}
} }
}


//increasement available
var idleProductionCount = 22000.0 - getDailyProductionCount(assignDate, selectedDate);

println("idleProductionCount - " + idleProductionCount);
open fun generateDetailedScheduleByDay(assignDate: Int, selectedDate: LocalDateTime, produceAt: LocalDateTime) {
val detailedScheduleOutputList = ArrayList<NeedQtyRecord>()


//increasement available
//var warehouseCap = 22000.0
var machineCap = 10000.0
var needQtyList = getNeedQty()
println("needQtyList - " + needQtyList);
//##### The 22000, 10000 machine cap just means the max warehouse storage qty, not production qty cap
//##### The total production qty of the date is 10000 due to machine cap
//##### search all items with bom to consider need or no need production
val args = mapOf( val args = mapOf(
"assignDate" to assignDate, "assignDate" to assignDate,
"selectedDate" to selectedDate.format(formatter) "selectedDate" to selectedDate.format(formatter)
) )

val scheduledList: List<ProductionScheduleRecord> = getProductionScheduleRecord(args)

//用缺口決定生產順序
val productionPriorityMap: HashMap<ProductionScheduleRecord, Double> = HashMap();
//TODO: update to use SQL get shop order record for detailed scheduling (real prodQty and openBal)
for (record in scheduledList) {
val productionPriorityMap: HashMap<NeedQtyRecord, Double> = HashMap();
for (record in needQtyList) {
println("Object - " + record.toString()); println("Object - " + record.toString());
val tempRecord = record;
tempRecord.prodQty = tempRecord.prodQty * 2;
val difference =
-(tempRecord.targetMinStock + tempRecord.prodQty - tempRecord.estCloseBal) /*TODO: this should be real close bal*/;
productionPriorityMap.put(tempRecord, difference)
//val tempRecord = record;
//val difference =
// -tempRecord.outputQty
val isDark = maxOf(record.isDark.toDouble(), 0.0)
val isFloat = maxOf(record.isFloat.toDouble(), 0.0)
val isDense = maxOf(record.isDense.toDouble(), 0.0)
val allergicSubstances = maxOf(record.allergicSubstances.toDouble(), 0.0)

val priority = isDark + isFloat + isDense + allergicSubstances
record.itemPriority = priority
productionPriorityMap.put(record, priority)
} }


//##### all qty should be 包, FG qty/bom outputQty
//sort by difference //sort by difference
val sortedEntries = productionPriorityMap.entries.sortedBy { it.value } val sortedEntries = productionPriorityMap.entries.sortedBy { it.value }


var accProdCount = 0.0; var accProdCount = 0.0;
var fgCount = 0L; var fgCount = 0L;


for ((updatedScheduleRecord, totalDifference) in sortedEntries) {
for ((updatedScheduleRecord, priority) in sortedEntries) {
//match id with rough schedule record to create new record //match id with rough schedule record to create new record
val targetRecord = scheduledList.find { it.item.id == updatedScheduleRecord.item.id }
val prodDifference = updatedScheduleRecord.prodQty - (targetRecord?.prodQty ?: 0.0)

if (idleProductionCount - prodDifference >= 0) {
val targetRecord = needQtyList.find { it.id == updatedScheduleRecord.id }
//??? this should be the bom output Qty * no. of job order needed
val prodDifference = updatedScheduleRecord.outputQty - (targetRecord?.outputQty ?: 0.0)
updatedScheduleRecord.itemPriority = priority;
if (updatedScheduleRecord.batchNeed.toInt() > 0) {
//have enough quoter for adjustment //have enough quoter for adjustment
idleProductionCount -= prodDifference;
machineCap -= prodDifference;
detailedScheduleOutputList.add(updatedScheduleRecord) detailedScheduleOutputList.add(updatedScheduleRecord)
accProdCount += updatedScheduleRecord.prodQty
accProdCount += updatedScheduleRecord.outputQty
fgCount++ fgCount++
} else { } else {
println("[INFO] item " + updatedScheduleRecord.name + " have bee skipped"); println("[INFO] item " + updatedScheduleRecord.name + " have bee skipped");
} }
} }


// Sort detailedScheduleOutputList by item priority
val sortedOutputList = detailedScheduleOutputList.sortedBy { it.weightingRef }.toMutableList()
// Sort detailedScheduleOutputList by item priority: needQty
val sortedOutputList = detailedScheduleOutputList.sortedBy { it.needQty }.toMutableList()


// Update itemPriority in the sorted list // Update itemPriority in the sorted list
var tempPriority = 1L var tempPriority = 1L
@@ -575,10 +738,41 @@ open class ProductionScheduleService(
} }


saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount, produceAt) saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount, produceAt)
}


//do for 7 days to predict
for (i in 1..6) {
fgCount = 0
accProdCount = 0.0
sortedOutputList.forEach { record ->
record.stockQty = record.stockQty + (record.outputQty * record.needNoOfJobOrder.toInt()) - record.avgQtyLastMonth
//compare if less than 1.9 days
record.daysLeft = record.stockQty / record.avgQtyLastMonth

if(record.daysLeft <= 1.9){
record.needQty = (record.avgQtyLastMonth * 2) - record.stockQty;

if(record.needQty > 0.0){
record.batchNeed = ceil(record.needQty / record.outputQty)
record.needNoOfJobOrder = record.batchNeed

fgCount += 1
accProdCount += record.outputQty * record.batchNeed.toInt()
}else{
record.needNoOfJobOrder = 0
record.batchNeed = 0
}
}

logger.info(record.name + " record.batchNeed: " + record.batchNeed + " record.stockQty:" + record.stockQty + " record.daysLeft:" + record.daysLeft)

}

saveDetailedScheduleOutputPredict(sortedOutputList, accProdCount, fgCount, produceAt.plusDays(i.toLong()))
}
}
open fun saveDetailedScheduleOutput( open fun saveDetailedScheduleOutput(
sortedEntries: List<ProductionScheduleRecord>,
sortedEntries: List<NeedQtyRecord>,
accProdCount: Double, accProdCount: Double,
fgCount: Long, fgCount: Long,
produceAt: LocalDateTime, produceAt: LocalDateTime,
@@ -593,28 +787,58 @@ open class ProductionScheduleService(
tempObj.id = saveProductionScheduleToDatabase(tempObj); tempObj.id = saveProductionScheduleToDatabase(tempObj);


for (detailedScheduleRecord in sortedEntries) { for (detailedScheduleRecord in sortedEntries) {
saveDetailedScheduleLineToDatabase(tempObj.id ?: -1, detailedScheduleRecord)
if(detailedScheduleRecord.batchNeed.toInt() > 0)
saveDetailedScheduleLineToDatabase(tempObj.id ?: -1, detailedScheduleRecord)
} }
} }


private fun saveDetailedScheduleLineToDatabase(parentId: Long, detailedScheduleObj: ProductionScheduleRecord) {
open fun saveDetailedScheduleOutputPredict(
sortedEntries: List<NeedQtyRecord>,
accProdCount: Double,
fgCount: Long,
produceAt: LocalDateTime,
) {
val tempObj = ProductionSchedule()
tempObj.id = -1;
tempObj.scheduleAt = LocalDateTime.now()
tempObj.produceAt = produceAt;
tempObj.totalFGType = fgCount;
tempObj.totalEstProdCount = accProdCount;
tempObj.type = "detailed"
tempObj.id = saveProductionScheduleToDatabase(tempObj);

for (detailedScheduleRecord in sortedEntries) {
if(detailedScheduleRecord.batchNeed.toInt() > 0)
saveDetailedScheduleLineToDatabase(tempObj.id ?: -1, detailedScheduleRecord)
}
}

private fun saveDetailedScheduleLineToDatabase(parentId: Long, detailedScheduleObj: NeedQtyRecord) {
try { try {
val prodSchedule = productionScheduleRepository.findById(parentId).get() val prodSchedule = productionScheduleRepository.findById(parentId).get()
val item = detailedScheduleObj.item.id?.let { itemService.findById(it) } ?: Items()
val item = detailedScheduleObj.id?.let { itemService.findById(it) } ?: Items()
var savedItem = ProductionScheduleLine() var savedItem = ProductionScheduleLine()
print("###detailedScheduleObj.stockQty:" + detailedScheduleObj.stockQty)
savedItem.id = -1; savedItem.id = -1;
// savedItem.prodScheduleId = parentId; // savedItem.prodScheduleId = parentId;
savedItem.productionSchedule = prodSchedule; savedItem.productionSchedule = prodSchedule;
savedItem.item = item; savedItem.item = item;
savedItem.lastMonthAvgSales = detailedScheduleObj.lastMonthAvgSales ?: 0.0;
savedItem.lastMonthAvgSales = detailedScheduleObj.avgQtyLastMonth ?: 0.0;
savedItem.refScheduleId = detailedScheduleObj.id; savedItem.refScheduleId = detailedScheduleObj.id;
savedItem.approverId = null; savedItem.approverId = null;
savedItem.estCloseBal = detailedScheduleObj.estCloseBal;
savedItem.prodQty = detailedScheduleObj.prodQty
savedItem.estCloseBal = detailedScheduleObj.outputQty;
savedItem.prodQty = detailedScheduleObj.outputQty
savedItem.type = "detailed" savedItem.type = "detailed"
savedItem.assignDate = detailedScheduleObj.assignDate;
savedItem.weightingRef = detailedScheduleObj.weightingRef
savedItem.itemPriority = detailedScheduleObj.itemPriority
savedItem.assignDate = LocalDateTime.now().dayOfMonth.toLong();
savedItem.weightingRef = detailedScheduleObj.needQty
savedItem.itemPriority = detailedScheduleObj.needQty.toLong()
savedItem.outputQty = detailedScheduleObj.outputQty
savedItem.avgQtyLastMonth = detailedScheduleObj.avgQtyLastMonth
savedItem.stockQty = detailedScheduleObj.stockQty
savedItem.daysLeft = detailedScheduleObj.daysLeft
savedItem.batchNeed = detailedScheduleObj.batchNeed.toInt()
savedItem.needNoOfJobOrder = detailedScheduleObj.needNoOfJobOrder.toInt()

productionScheduleLineRepository.saveAndFlush(savedItem) productionScheduleLineRepository.saveAndFlush(savedItem)


} catch (e: Exception) { } catch (e: Exception) {
@@ -622,6 +846,38 @@ open class ProductionScheduleService(
} }
} }
//====================細排相關 END====================// //====================細排相關 END====================//
open class NeedQtyRecord {
//SQL record in general with item name
open var name: String = "" //item name
open var avgQtyLastMonth: Double = 0.0
open var needNoOfJobOrder: Number = 0
open var id: Long = 0
open var needQty: Double = 0.0
open var outputQty: Double = 0.0
open var itemPriority: Number = 0
open var isDark: Number = 0
open var isFloat: Number = 0
open var isDense: Number = 0
open var allergicSubstances: Number = 0
open var stockQty: Double = 0.0
open var daysLeft: Double = 0.0
open var batchNeed: Number = 0

override fun toString(): String {
return "NeedQtyRecord(name=${name}," +
" avgQtyLastMonth=$avgQtyLastMonth" +
" stockQty=$stockQty" +
" itemId=$id" +
" needNoOfJobOrder=$needNoOfJobOrder" +
" outputQty=$outputQty" +
" needQty=$needQty" +
" itemPriority=$itemPriority"+
" isDark=$isDark" +
" isFloat=$isFloat" +
" isDense=$isDense" +
" allergicSubstances=$allergicSubstances"
}
}


open class ProductionScheduleRecord : ProductionScheduleLine() { open class ProductionScheduleRecord : ProductionScheduleLine() {
//SQL record in general with item name //SQL record in general with item name


+ 0
- 17
src/main/java/com/ffii/fpsms/modules/master/service/WarehouseService.kt 查看文件

@@ -244,9 +244,6 @@ open class WarehouseService(
this.store_id = store_id this.store_id = store_id
this.slot = slot this.slot = slot
this.order = order this.order = order
this.storeLocation = storeLocation
this.stockTakeTable = stockTakeTable
this.company = company
} }
warehouseRepository.save(existingWarehouse) warehouseRepository.save(existingWarehouse)
updateCount++ updateCount++
@@ -262,9 +259,6 @@ open class WarehouseService(
this.store_id = store_id this.store_id = store_id
this.slot = slot this.slot = slot
this.order = order this.order = order
this.storeLocation = storeLocation
this.stockTakeTable = stockTakeTable
this.company = company
} }
warehouseRepository.save(newWarehouse) warehouseRepository.save(newWarehouse)
createCount++ createCount++
@@ -342,15 +336,4 @@ open class WarehouseService(
// calculate total sort value: floor * 10000 + letter * 100 + slot // calculate total sort value: floor * 10000 + letter * 100 + slot
return floorOrder * 10000L + letterOrder * 100L + slot return floorOrder * 10000L + letterOrder * 100L + slot
} }

open fun findByCodeAndStockTakeTableAndCompanyAndStoreLocation(
code: String,
stockTakeTable: String,
company: String,
storeLocation: String
): Warehouse? {
return warehouseRepository.findByCodeAndStockTakeTableAndCompanyAndStoreLocationAndDeletedIsFalse(
code, stockTakeTable, company, storeLocation
)
}
} }

+ 0
- 15
src/main/java/com/ffii/fpsms/modules/master/web/ItemsController.kt 查看文件

@@ -128,19 +128,4 @@ fun getItemsWithDetailsByPage(request: HttpServletRequest): RecordsRes<Map<Strin
return RecordsRes(paginatedList, fullList.size) return RecordsRes(paginatedList, fullList.size)
} }


@PostMapping("/patch/file")
fun patchItemsFromExcelFile(@RequestParam("fileName") fileName: String): ResponseEntity<*> {
return try {
if (fileName.isBlank()) {
return ResponseEntity.badRequest().body("File name cannot be empty")
}

logger.info("Request to patch items from file: $fileName")
val result = itemsService.patchItemsFromExcelFileName(fileName)
ResponseEntity.ok(result)
} catch (e: Exception) {
logger.error("Error patching from file: ${e.message}", e)
ResponseEntity.badRequest().body("Error: ${e.message}")
}
}
} }

+ 16
- 4
src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt 查看文件

@@ -13,6 +13,7 @@ import com.ffii.fpsms.modules.master.service.ProductionScheduleService.RoughSche
import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.master.web.models.MessageResponse
import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleLineRequest import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleLineRequest
import com.ffii.fpsms.modules.master.web.models.SearchProdScheduleRequest import com.ffii.fpsms.modules.master.web.models.SearchProdScheduleRequest
import com.ffii.fpsms.modules.master.web.models.ReleaseProdScheduleRequest
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid import jakarta.validation.Valid
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
@@ -65,6 +66,7 @@ class ProductionScheduleController(


@GetMapping("/detail/detailed/{id}") @GetMapping("/detail/detailed/{id}")
fun getDetailedProdScheduleDetail(@PathVariable id: Long): DetailedProdScheduleWithLine { fun getDetailedProdScheduleDetail(@PathVariable id: Long): DetailedProdScheduleWithLine {
print("getDetailedProdScheduleDetail################")
return productionScheduleService.detailedProdScheduleDetail(id) return productionScheduleService.detailedProdScheduleDetail(id)
} }


@@ -78,8 +80,14 @@ class ProductionScheduleController(
return productionScheduleService.releaseProdScheduleLine(request) return productionScheduleService.releaseProdScheduleLine(request)
} }


@PostMapping("/detail/detailed/release")
fun releaseProdSchedule(@Valid @RequestBody request: ReleaseProdScheduleRequest): MessageResponse {
return productionScheduleService.releaseProdSchedule(request)
}

@GetMapping("/getRecordByPage") @GetMapping("/getRecordByPage")
fun allProdSchedulesByPage(@ModelAttribute request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> { fun allProdSchedulesByPage(@ModelAttribute request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> {
print("allProdSchedulesByPage############")
return productionScheduleService.allProdSchedulesByPage(request); return productionScheduleService.allProdSchedulesByPage(request);
} }


@@ -97,11 +105,12 @@ class ProductionScheduleController(
fun generateDetailSchedule(request: HttpServletRequest?): Int { fun generateDetailSchedule(request: HttpServletRequest?): Int {
try { try {
// For test // For test
val genDate = request?.getParameter("genDate")?.let { LocalDate.parse(it).atStartOfDay() }
//val genDate = request?.getParameter("genDate")?.let { LocalDate.parse(it).atStartOfDay() }


val genDate = LocalDateTime.now()
val today = LocalDateTime.now() val today = LocalDateTime.now()


val latestRoughScheduleAt = productionScheduleService.getLatestScheduleAt("rough");
//val latestRoughScheduleAt = productionScheduleService.getLatestScheduleAt("rough");
// val latestRoughScheduleAt = productionScheduleService.getSecondLatestRoughScheduleAt("rough"); // val latestRoughScheduleAt = productionScheduleService.getSecondLatestRoughScheduleAt("rough");
// val latestRoughScheduleAt = productionScheduleService.getScheduledAtByDate(genDate?.toLocalDate() ?: today.toLocalDate()); // val latestRoughScheduleAt = productionScheduleService.getScheduledAtByDate(genDate?.toLocalDate() ?: today.toLocalDate());
// assume schedule period is monday to sunday // assume schedule period is monday to sunday
@@ -115,8 +124,11 @@ class ProductionScheduleController(
// } else assignDate.toInt() // } else assignDate.toInt()
//TODO: update this to receive selectedDate and assignDate from schedule //TODO: update this to receive selectedDate and assignDate from schedule
// productionScheduleService.generateDetailedScheduleByDay(1, LocalDateTime.of(2025,6,25,0,0,0)) // productionScheduleService.generateDetailedScheduleByDay(1, LocalDateTime.of(2025,6,25,0,0,0))
println("genDate: $genDate | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ")
productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt ?: LocalDateTime.now(), genDate ?: today)
//println("genDate: $genDate | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ")
//productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt ?: LocalDateTime.now(), genDate ?: today)
println("Start generate detailed Schedule")
productionScheduleService.generateDetailedScheduleByDay(assignDate, today, today)
return 200 return 200
} catch (e: Exception) { } catch (e: Exception) {
throw RuntimeException("Error generate schedule: ${e.message}", e) throw RuntimeException("Error generate schedule: ${e.message}", e)


+ 9
- 0
src/main/java/com/ffii/fpsms/modules/master/web/models/ReleaseProdScheduleRequest.kt 查看文件

@@ -0,0 +1,9 @@
package com.ffii.fpsms.modules.master.web.models

import jakarta.validation.constraints.NotNull
import java.math.BigDecimal

data class ReleaseProdScheduleRequest(
@field:NotNull(message = "Id cannot be null")
val id: Int
)

+ 9
- 0
src/main/resources/db/changelog/changes/20251211_01_Fai/01_add_fields_to_psl.sql 查看文件

@@ -0,0 +1,9 @@
-- liquibase formatted sql
-- changeset Fai:add_fields_to_psl

ALTER TABLE `fpsmsdb`.`production_schedule_line`
ADD COLUMN `outputQty` DECIMAL(16,2) NULL AFTER `weightingRef`,
ADD COLUMN `stockQty` DECIMAL(16,2) NULL AFTER `outputQty`,
ADD COLUMN `daysLeft` DECIMAL(16,2) NULL AFTER `stockQty`,
ADD COLUMN `batchNeed` INT NULL AFTER `daysLeft`,
ADD COLUMN `needNoOfJobOrder` INT NULL AFTER `batchNeed`;

+ 5
- 0
src/main/resources/db/changelog/changes/20251211_01_Fai/02_add_fields_to_psl.sql 查看文件

@@ -0,0 +1,5 @@
-- liquibase formatted sql
-- changeset Fai:add_avgQtyLastMonth_to_psl

ALTER TABLE `fpsmsdb`.`production_schedule_line`
ADD COLUMN `avgQtyLastMonth` DECIMAL(16,2) NULL;

+ 3
- 1
src/main/resources/db/changelog/changes/20251216_01_Enson/01_add_column.sql 查看文件

@@ -1,5 +1,7 @@
-- liquibase formatted sql -- liquibase formatted sql
-- changeset Enson:add_column -- changeset Enson:add_column
-- preconditions onFail:MARK_RAN
-- precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'fpsmsdb' AND table_name = 'bag'


create table `fpsmsdb`.`bag` ( create table `fpsmsdb`.`bag` (
`id` int not null auto_increment, `id` int not null auto_increment,
@@ -56,4 +58,4 @@ create table `fpsmsdb`.`jo_bag_consumption` (
`deleted` tinyint(1) NOT NULL DEFAULT '0', `deleted` tinyint(1) NOT NULL DEFAULT '0',
primary key (`id`) primary key (`id`)


) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

+ 5
- 0
src/main/resources/db/changelog/changes/20251219_01_KelvinY/01_add_equipmentCode_to_equipment_detail.sql 查看文件

@@ -0,0 +1,5 @@
-- liquibase formatted sql
-- changeset KelvinY:add_equipmentCode_to_equipment_detail

ALTER TABLE `fpsmsdb`.`equipment_detail`
ADD COLUMN `equipmentCode` VARCHAR(255) NULL;

+ 5
- 0
src/main/resources/db/changelog/changes/20251219_01_KelvinY/02_add_type_to_printer.sql 查看文件

@@ -0,0 +1,5 @@
-- liquibase formatted sql
-- changeset KelvinY:add_type_to_printer

ALTER TABLE `fpsmsdb`.`printer`
ADD COLUMN `type` VARCHAR(255) NULL;

+ 15
- 0
src/main/resources/db/changelog/changes/20251219_01_KelvinY/03_modify_items_table.sql 查看文件

@@ -0,0 +1,15 @@
-- liquibase formatted sql
-- changeset KelvinY:modify_items_table

-- Add new columns
ALTER TABLE `fpsmsdb`.`items`
ADD COLUMN `isEgg` TINYINT(1) NULL DEFAULT 0 AFTER `LocationCode`,
ADD COLUMN `isFee` TINYINT(1) NULL DEFAULT 0 AFTER `isEgg`,
ADD COLUMN `isBag` TINYINT(1) NULL DEFAULT 0 AFTER `isFee`;

-- Remove old columns
ALTER TABLE `fpsmsdb`.`items`
DROP COLUMN `storeLocation`,
DROP COLUMN `MTMSPickRoutingID`,
DROP COLUMN `inventorySheet`,
DROP COLUMN `company`;

+ 12
- 0
src/main/resources/db/changelog/changes/20251219_01_KelvinY/04_modify_warehouse_table.sql 查看文件

@@ -0,0 +1,12 @@
-- liquibase formatted sql
-- changeset KelvinY:modify_warehouse_table

-- Add new column
ALTER TABLE `fpsmsdb`.`warehouse`
ADD COLUMN `stockTakeSection` VARCHAR(255) NULL;

-- Remove old columns
ALTER TABLE `fpsmsdb`.`warehouse`
DROP COLUMN `storeLocation`,
DROP COLUMN `stockTakeTable`,
DROP COLUMN `company`;

正在加载...
取消
保存