diff --git a/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt b/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt index 3e31a35..5d7e58f 100644 --- a/src/main/java/com/ffii/fpsms/modules/common/scheduler/service/SchedulerService.kt +++ b/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 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) { throw RuntimeException("Error generate schedule: ${e.message}", e) diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt index 84cdf7d..98549a6 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt +++ b/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() //increasement available @@ -744,7 +744,7 @@ open class ProductionScheduleService( var needQtyList = getNeedQty() //remove the production schedule >= today - clearTodayAndFutureProdSchedule() + clearTodayAndFutureProdSchedule(produceAt) println("needQtyList - " + needQtyList); @@ -812,7 +812,7 @@ open class ProductionScheduleService( saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount, produceAt) //do for 9 days to predict - for (i in 1..8) { + for (i in 1..(days + 1)) { val targetDate = produceAt.plusDays(i.toLong()) val isSat = targetDate.dayOfWeek == DayOfWeek.SATURDAY val isFri = targetDate.dayOfWeek == DayOfWeek.FRIDAY @@ -1495,7 +1495,8 @@ open class ProductionScheduleService( val matHeaders = listOf( "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" ) @@ -1512,6 +1513,7 @@ open class ProductionScheduleService( row.heightInPoints = 35f val totalNeed = asDouble(rowData["totalMatQtyNeed"]) + val purchaseQtyNeed = asDouble(rowData["purchaseQtyNeed"]) val purchased = asDouble(rowData["purchasedQty"]) val onHand = asDouble(rowData["onHandQty"]) val requiredQty = (totalNeed - purchased - onHand).coerceAtLeast(0.0) @@ -1522,6 +1524,8 @@ open class ProductionScheduleService( requiredQty, totalNeed, rowData["uomName"]?.toString() ?: "", + purchaseQtyNeed, + rowData["purchaseUnit"]?.toString() ?: "", purchased, onHand, asDouble(rowData["unavailableQty"]), @@ -1546,11 +1550,13 @@ open class ProductionScheduleService( matSheet.setColumnWidth(2, 14 * 256) // Required Qty matSheet.setColumnWidth(3, 14 * 256) // Total Qty Need 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 val matPrintSetup = matSheet.printSetup @@ -1735,6 +1741,14 @@ open class ProductionScheduleService( 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, 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, max(ps.produceAt) as toProdcueAt, psl.* @@ -1773,8 +1787,8 @@ open class ProductionScheduleService( itm.code AS matCode, itm.name AS matName, 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(( SELECT SUM(pol.qty) FROM purchase_order_line pol @@ -1784,13 +1798,15 @@ open class ProductionScheduleService( AND po.completeDate IS NULL ), 0) AS purchasedQty, 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 JOIN production_schedule ps ON psl.prodScheduleId = ps.id JOIN items it ON psl.itemId = it.id JOIN bom ON it.id = bom.itemId JOIN bom_material bm ON bom.id = bm.bomId 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 WHERE DATE(ps.produceAt) >= DATE_ADD(CURDATE(), INTERVAL 1 DAY) AND DATE(ps.produceAt) < DATE_ADD(CURDATE(), INTERVAL 8 DAY) @@ -1841,25 +1857,27 @@ open class ProductionScheduleService( } @Transactional - open fun clearTodayAndFutureProdSchedule() { + open fun clearTodayAndFutureProdSchedule(produceAt: LocalDateTime) { val deleteLinesSql = """ DELETE FROM production_schedule_line WHERE prodScheduleId IN ( SELECT id FROM production_schedule - WHERE DATE(produceAt) >= DATE(NOW()) + WHERE DATE(produceAt) >= DATE(:produceAt) ) """.trimIndent() val deleteSchedulesSql = """ DELETE FROM production_schedule - WHERE DATE(produceAt) >= DATE(NOW()) + WHERE DATE(produceAt) >= DATE(:produceAt) """.trimIndent() + val params = mapOf("produceAt" to produceAt) + // Execute child delete first - jdbcDao.executeUpdate(deleteLinesSql) + jdbcDao.executeUpdate(deleteLinesSql, params) // Then delete parent schedules - jdbcDao.executeUpdate(deleteSchedulesSql) + jdbcDao.executeUpdate(deleteSchedulesSql, params) // Optional: log the action (if you have logging setup) // logger.info("Cleared all production schedules with produceAt >= today") diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt index 590378f..0c90bcc 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt +++ b/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.MediaType import org.springframework.web.bind.annotation.GetMapping +import java.time.format.DateTimeParseException @RestController @RequestMapping("/productionSchedule") @@ -105,35 +106,53 @@ class ProductionScheduleController( } @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 { - // 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 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 } catch (e: Exception) { + println("Error generating detailed schedule: ${e.message}") + e.printStackTrace() throw RuntimeException("Error generate schedule: ${e.message}", e) } } @@ -232,16 +251,28 @@ class ProductionScheduleController( } } - @PostMapping( + @GetMapping( value = ["/export-prod-schedule"], produces = ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"] ) - - fun exportProdSchedule(): ResponseEntity { - 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 { + // 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() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=production_schedule.xlsx")