[email protected] 5 дней назад
Родитель
Сommit
9daf33d246
3 измененных файлов: 97 добавлений и 48 удалений
  1. +1
    -1
      src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt
  2. +35
    -17
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  3. +61
    -30
      src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt

+ 1
- 1
src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt Просмотреть файл

@@ -164,7 +164,7 @@ open class SchedulerService(


//TODO: update this to receive selectedDate and assignDate from schedule //TODO: update this to receive selectedDate and assignDate from schedule
println("produceAt: $produceAt | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ") println("produceAt: $produceAt | today: $today | latestRoughScheduleAt: $latestRoughScheduleAt | assignDate: $assignDate ")
productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt.atStartOfDay(), produceAt)
productionScheduleService.generateDetailedScheduleByDay(assignDate, latestRoughScheduleAt.atStartOfDay(), produceAt, 7)
} }
} catch (e: Exception) { } catch (e: Exception) {
throw RuntimeException("Error generate schedule: ${e.message}", e) throw RuntimeException("Error generate schedule: ${e.message}", e)


+ 35
- 17
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt Просмотреть файл

@@ -735,7 +735,7 @@ open class ProductionScheduleService(
} }
} }


open fun generateDetailedScheduleByDay(assignDate: Int, selectedDate: LocalDateTime, produceAt: LocalDateTime) {
open fun generateDetailedScheduleByDay(assignDate: Int, selectedDate: LocalDateTime, produceAt: LocalDateTime, days: Int) {
val detailedScheduleOutputList = ArrayList<NeedQtyRecord>() val detailedScheduleOutputList = ArrayList<NeedQtyRecord>()


//increasement available //increasement available
@@ -744,7 +744,7 @@ open class ProductionScheduleService(
var needQtyList = getNeedQty() var needQtyList = getNeedQty()
//remove the production schedule >= today //remove the production schedule >= today
clearTodayAndFutureProdSchedule()
clearTodayAndFutureProdSchedule(produceAt)


println("needQtyList - " + needQtyList); println("needQtyList - " + needQtyList);
@@ -812,7 +812,7 @@ open class ProductionScheduleService(
saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount, produceAt) saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount, produceAt)


//do for 9 days to predict //do for 9 days to predict
for (i in 1..8) {
for (i in 1..(days + 1)) {
val targetDate = produceAt.plusDays(i.toLong()) val targetDate = produceAt.plusDays(i.toLong())
val isSat = targetDate.dayOfWeek == DayOfWeek.SATURDAY val isSat = targetDate.dayOfWeek == DayOfWeek.SATURDAY
val isFri = targetDate.dayOfWeek == DayOfWeek.FRIDAY val isFri = targetDate.dayOfWeek == DayOfWeek.FRIDAY
@@ -1495,7 +1495,8 @@ open class ProductionScheduleService(


val matHeaders = listOf( val matHeaders = listOf(
"Mat Code", "Mat Name", "Required Qty", "Total Qty Need", "Mat Code", "Mat Name", "Required Qty", "Total Qty Need",
"UoM", "Purchased Qty", "On Hand Qty", "Unavailable Qty",
"UoM", "Purchase Qty Need", "Purchase Unit",
"Purchased Qty", "On Hand Qty", "Unavailable Qty",
"Related Item Code", "Related Item Name" "Related Item Code", "Related Item Name"
) )


@@ -1512,6 +1513,7 @@ open class ProductionScheduleService(
row.heightInPoints = 35f row.heightInPoints = 35f


val totalNeed = asDouble(rowData["totalMatQtyNeed"]) val totalNeed = asDouble(rowData["totalMatQtyNeed"])
val purchaseQtyNeed = asDouble(rowData["purchaseQtyNeed"])
val purchased = asDouble(rowData["purchasedQty"]) val purchased = asDouble(rowData["purchasedQty"])
val onHand = asDouble(rowData["onHandQty"]) val onHand = asDouble(rowData["onHandQty"])
val requiredQty = (totalNeed - purchased - onHand).coerceAtLeast(0.0) val requiredQty = (totalNeed - purchased - onHand).coerceAtLeast(0.0)
@@ -1522,6 +1524,8 @@ open class ProductionScheduleService(
requiredQty, requiredQty,
totalNeed, totalNeed,
rowData["uomName"]?.toString() ?: "", rowData["uomName"]?.toString() ?: "",
purchaseQtyNeed,
rowData["purchaseUnit"]?.toString() ?: "",
purchased, purchased,
onHand, onHand,
asDouble(rowData["unavailableQty"]), asDouble(rowData["unavailableQty"]),
@@ -1546,11 +1550,13 @@ open class ProductionScheduleService(
matSheet.setColumnWidth(2, 14 * 256) // Required Qty matSheet.setColumnWidth(2, 14 * 256) // Required Qty
matSheet.setColumnWidth(3, 14 * 256) // Total Qty Need matSheet.setColumnWidth(3, 14 * 256) // Total Qty Need
matSheet.setColumnWidth(4, 10 * 256) // UoM matSheet.setColumnWidth(4, 10 * 256) // UoM
matSheet.setColumnWidth(5, 14 * 256) // Purchased Qty
matSheet.setColumnWidth(6, 14 * 256) // On Hand Qty
matSheet.setColumnWidth(7, 14 * 256) // Unavailable Qty
matSheet.setColumnWidth(8, 22 * 256) // Related Item Code
matSheet.setColumnWidth(9, 40 * 256) // Related Item Name (longest)
matSheet.setColumnWidth(5, 14 * 256) // Purchase Qty Need
matSheet.setColumnWidth(6, 10 * 256) // Purchase UoM
matSheet.setColumnWidth(7, 14 * 256) // Purchased Qty
matSheet.setColumnWidth(8, 14 * 256) // On Hand Qty
matSheet.setColumnWidth(9, 14 * 256) // Unavailable Qty
matSheet.setColumnWidth(10, 22 * 256) // Related Item Code
matSheet.setColumnWidth(11, 40 * 256) // Related Item Name (longest)


// Portrait print setup // Portrait print setup
val matPrintSetup = matSheet.printSetup val matPrintSetup = matSheet.printSetup
@@ -1735,6 +1741,14 @@ open class ProductionScheduleService(
left join purchase_order on purchase_order_line.purchaseOrderId = purchase_order.id left join purchase_order on purchase_order_line.purchaseOrderId = purchase_order.id
where purchase_order_line.itemId = itm.id and date(purchase_order.estimatedArrivalDate) >= date(now()) and purchase_order.completeDate is null) as purchasedQty, where purchase_order_line.itemId = itm.id and date(purchase_order.estimatedArrivalDate) >= date(now()) and purchase_order.completeDate is null) as purchasedQty,
bm.uomName, bm.uomName,
(select ratioD/ratioN from item_uom where purchaseUnit = 1 and itemId = it.id and item_uom.deleted = 0
) as purchaseRatio,
(select ceil(ratioD/ratioN*sum(bm.qty * psl.batchNeed)) from item_uom where purchaseUnit = 1 and itemId = it.id and item_uom.deleted = 0
) as purchaseQtyNeed,
(select code from item_uom
left join uom_conversion on item_uom.uomId = uom_conversion.id
where item_uom.purchaseUnit = 1 and item_uom.itemId = it.id and item_uom.deleted = 0
) as purchaseUnit,
min(ps.produceAt) as toProdcueFrom, min(ps.produceAt) as toProdcueFrom,
max(ps.produceAt) as toProdcueAt, max(ps.produceAt) as toProdcueAt,
psl.* psl.*
@@ -1773,8 +1787,8 @@ open class ProductionScheduleService(
itm.code AS matCode, itm.code AS matCode,
itm.name AS matName, itm.name AS matName,
bm.uomName AS uom, bm.uomName AS uom,
iv.onHandQty,
iv.unavailableQty,
ceil((iv.onHandQty * (itsm.ratioN / itsm.ratioD)) * (itum.ratioD / itum.ratioN)) as onHandQty,
ceil((iv.unavailableQty * (itsm.ratioN / itsm.ratioD)) * (itum.ratioD / itum.ratioN)) as unavailableQty,
COALESCE(( COALESCE((
SELECT SUM(pol.qty) SELECT SUM(pol.qty)
FROM purchase_order_line pol FROM purchase_order_line pol
@@ -1784,13 +1798,15 @@ open class ProductionScheduleService(
AND po.completeDate IS NULL AND po.completeDate IS NULL
), 0) AS purchasedQty, ), 0) AS purchasedQty,
DATE(ps.produceAt) AS produceDate, DATE(ps.produceAt) AS produceDate,
SUM(bm.qty * psl.batchNeed) AS qtyNeeded
ceil( SUM(bm.qty * psl.batchNeed) * (itum.ratioD / itum.ratioN) ) AS qtyNeeded
FROM production_schedule_line psl FROM production_schedule_line psl
JOIN production_schedule ps ON psl.prodScheduleId = ps.id JOIN production_schedule ps ON psl.prodScheduleId = ps.id
JOIN items it ON psl.itemId = it.id JOIN items it ON psl.itemId = it.id
JOIN bom ON it.id = bom.itemId JOIN bom ON it.id = bom.itemId
JOIN bom_material bm ON bom.id = bm.bomId JOIN bom_material bm ON bom.id = bm.bomId
JOIN items itm ON bm.itemId = itm.id JOIN items itm ON bm.itemId = itm.id
JOIN item_uom itum ON itm.id = itum.itemId and itum.purchaseUnit = 1
JOIN item_uom itsm ON itm.id = itsm.itemId and itsm.stockUnit = 1
LEFT JOIN inventory iv ON itm.id = iv.itemId LEFT JOIN inventory iv ON itm.id = iv.itemId
WHERE DATE(ps.produceAt) >= DATE_ADD(CURDATE(), INTERVAL 1 DAY) WHERE DATE(ps.produceAt) >= DATE_ADD(CURDATE(), INTERVAL 1 DAY)
AND DATE(ps.produceAt) < DATE_ADD(CURDATE(), INTERVAL 8 DAY) AND DATE(ps.produceAt) < DATE_ADD(CURDATE(), INTERVAL 8 DAY)
@@ -1841,25 +1857,27 @@ open class ProductionScheduleService(
} }


@Transactional @Transactional
open fun clearTodayAndFutureProdSchedule() {
open fun clearTodayAndFutureProdSchedule(produceAt: LocalDateTime) {
val deleteLinesSql = """ val deleteLinesSql = """
DELETE FROM production_schedule_line DELETE FROM production_schedule_line
WHERE prodScheduleId IN ( WHERE prodScheduleId IN (
SELECT id FROM production_schedule SELECT id FROM production_schedule
WHERE DATE(produceAt) >= DATE(NOW())
WHERE DATE(produceAt) >= DATE(:produceAt)
) )
""".trimIndent() """.trimIndent()


val deleteSchedulesSql = """ val deleteSchedulesSql = """
DELETE FROM production_schedule DELETE FROM production_schedule
WHERE DATE(produceAt) >= DATE(NOW())
WHERE DATE(produceAt) >= DATE(:produceAt)
""".trimIndent() """.trimIndent()


val params = mapOf("produceAt" to produceAt)

// Execute child delete first // Execute child delete first
jdbcDao.executeUpdate(deleteLinesSql)
jdbcDao.executeUpdate(deleteLinesSql, params)


// Then delete parent schedules // Then delete parent schedules
jdbcDao.executeUpdate(deleteSchedulesSql)
jdbcDao.executeUpdate(deleteSchedulesSql, params)


// Optional: log the action (if you have logging setup) // Optional: log the action (if you have logging setup)
// logger.info("Cleared all production schedules with produceAt >= today") // logger.info("Cleared all production schedules with produceAt >= today")


+ 61
- 30
src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt Просмотреть файл

@@ -27,6 +27,7 @@ import org.springframework.http.ResponseEntity
import org.springframework.http.HttpHeaders import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import java.time.format.DateTimeParseException


@RestController @RestController
@RequestMapping("/productionSchedule") @RequestMapping("/productionSchedule")
@@ -105,35 +106,53 @@ class ProductionScheduleController(
} }


@RequestMapping(value = ["/testDetailedSchedule"], method = [RequestMethod.GET]) @RequestMapping(value = ["/testDetailedSchedule"], method = [RequestMethod.GET])
fun generateDetailSchedule(request: HttpServletRequest?): Int {
fun generateDetailSchedule(
@RequestParam(name = "startDate", required = false) startDateParam: String?,
@RequestParam(name = "days", required = false, defaultValue = "7") days: Int,
request: HttpServletRequest?
): Int {
try { try {
// For test
//val genDate = request?.getParameter("genDate")?.let { LocalDate.parse(it).atStartOfDay() }
// ── Determine the generation base date ──
val genDate = startDateParam
?.let { param ->
try {
LocalDate.parse(param).atStartOfDay()
} catch (e: Exception) {
// Invalid format → fallback to today
println("Invalid startDate format: $param → using today")
LocalDateTime.now()
}
}
?: LocalDateTime.now() // default: today


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


//val latestRoughScheduleAt = productionScheduleService.getLatestScheduleAt("rough");
// val latestRoughScheduleAt = productionScheduleService.getSecondLatestRoughScheduleAt("rough");
// val latestRoughScheduleAt = productionScheduleService.getScheduledAtByDate(genDate?.toLocalDate() ?: today.toLocalDate());
// assume schedule period is monday to sunday
val assignDate = (genDate ?: today).dayOfWeek.value
// val day = 1
// val assignDate = ((genDate ?: today).dayOfWeek.value + day) % 7 + 1

// val assignDate = abs(Duration.between(latestRoughScheduleAt, today).toDays() % 7) + 1
// val finalAssignDate = if (assignDate.toInt() == 0) {
// 1
// } else assignDate.toInt()
//TODO: update this to receive selectedDate and assignDate from schedule
// 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("Start generate detailed Schedule")
productionScheduleService.generateDetailedScheduleByDay(assignDate, today, today)
// ── Your existing assignDate logic ──
// (assuming schedule period monday=1 ... sunday=7)
val assignDate = genDate.dayOfWeek.value

// If you want to allow offset / custom day assignment later, you can keep/extend this:
// val day = 1
// val assignDate = ((genDate.dayOfWeek.value + day) % 7) + 1

println("Starting detailed schedule generation")
println(" → genDate : $genDate")
println(" → today : $today")
println(" → assignDate : $assignDate")

// Core generation call – using today's timestamp as "reference time"
// (you can change the last parameter to genDate if business logic requires it)
productionScheduleService.generateDetailedScheduleByDay(
assignDate,
today, // reference time / as-of time
genDate,
days// target / base date for planning
)

return 200 return 200
} catch (e: Exception) { } catch (e: Exception) {
println("Error generating detailed schedule: ${e.message}")
e.printStackTrace()
throw RuntimeException("Error generate schedule: ${e.message}", e) throw RuntimeException("Error generate schedule: ${e.message}", e)
} }
} }
@@ -232,16 +251,28 @@ class ProductionScheduleController(
} }
} }


@PostMapping(
@GetMapping(
value = ["/export-prod-schedule"], value = ["/export-prod-schedule"],
produces = ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"] produces = ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]
) )
fun exportProdSchedule(): ResponseEntity<ByteArray> {
val data = productionScheduleService.searchExportProdSchedule(LocalDate.now())
val dataMat = productionScheduleService.searchExportProdScheduleMaterial(LocalDate.now())
val dataDaily = productionScheduleService.searchExportDailyMaterial(LocalDate.now())
val excelContent = productionScheduleService.exportProdScheduleToExcel(LocalDate.now(), data, dataMat, dataDaily)
fun exportProdSchedule(
@RequestParam(name = "fromDate", required = false) fromDateParam: String?
): ResponseEntity<ByteArray> {
// Parse fromDate or fall back to today
val fromDate: LocalDate = fromDateParam
?.let {
try {
LocalDate.parse(it)
} catch (e: DateTimeParseException) {
LocalDate.now()
}
}
?: LocalDate.now()

val data = productionScheduleService.searchExportProdSchedule(fromDate)
val dataMat = productionScheduleService.searchExportProdScheduleMaterial(fromDate)
val dataDaily = productionScheduleService.searchExportDailyMaterial(fromDate)
val excelContent = productionScheduleService.exportProdScheduleToExcel(fromDate, data, dataMat, dataDaily)
return ResponseEntity.ok() return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=production_schedule.xlsx") .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=production_schedule.xlsx")


Загрузка…
Отмена
Сохранить