Bladeren bron

no message

master
[email protected] 3 weken geleden
bovenliggende
commit
55f2bc3a32
2 gewijzigde bestanden met toevoegingen van 219 en 11 verwijderingen
  1. +217
    -10
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt
  2. +2
    -1
      src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt

+ 217
- 10
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt Bestand weergeven

@@ -47,12 +47,14 @@ import kotlin.collections.component2
import kotlin.jvm.optionals.getOrNull
import kotlin.math.ceil
import kotlin.comparisons.maxOf
import java.util.Locale

// === POI IMPORTS FOR EXCEL EXPORT WITH PRINT SETUP ===
import org.apache.poi.ss.usermodel.*
import org.apache.poi.ss.usermodel.IndexedColors
import org.apache.poi.ss.usermodel.FillPatternType
import org.apache.poi.ss.usermodel.VerticalAlignment
import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.apache.poi.xssf.usermodel.XSSFPrintSetup
import java.io.ByteArrayOutputStream
@@ -627,16 +629,16 @@ open class ProductionScheduleService(
i.avgQtyLastMonth,
(i.onHandQty -500),
(i.onHandQty -500) * 1.0 / i.avgQtyLastMonth as daysLeft,
i.avgQtyLastMonth * 2 - stockQty as needQty,
i.avgQtyLastMonth * 2.6 - stockQty as needQty,
i.stockQty,
CASE
WHEN stockQty * 1.0 / i.avgQtyLastMonth <= 1.9 THEN
CEIL((i.avgQtyLastMonth * 1.9 - stockQty) / i.outputQty)
CEIL((i.avgQtyLastMonth * 2.6 - 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)
CEIL((i.avgQtyLastMonth * 2.6 - stockQty) / i.outputQty)
ELSE 0
END AS batchNeed,
25 + 25 + markDark + markFloat + markDense + markAS + markTimeSequence + markComplexity as priority,
@@ -805,19 +807,39 @@ open class ProductionScheduleService(

saveDetailedScheduleOutput(sortedOutputList, accProdCount, fgCount, produceAt)

//do for 7 days to predict
for (i in 1..6) {
//do for 9 days to predict
for (i in 1..8) {
val targetDate = produceAt.plusDays(i.toLong())
val isSat = targetDate.dayOfWeek == DayOfWeek.SATURDAY
val isFri = targetDate.dayOfWeek == DayOfWeek.FRIDAY
val isSunday = targetDate.dayOfWeek == DayOfWeek.SUNDAY
val isFriSat = isFri || isSat

logger.info("##targetDate:" + targetDate + " isFri:"+ isFri + " isSat:" + isSat + " isSunday:" + isSunday)


fgCount = 0
accProdCount = 0.0
sortedOutputList.forEach { record ->
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;
var safetyStockDay = 2.0
var redLine = 1.9

if(isFriSat){
//record.daysLeft = record.daysLeft
safetyStockDay = 2.6
}

if(record.daysLeft < redLine){
record.needQty = (record.avgQtyLastMonth * safetyStockDay) - record.stockQty;

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

@@ -1362,8 +1384,10 @@ open class ProductionScheduleService(
}

fun exportProdScheduleToExcel(
fromDate: LocalDate,
lines: List<Map<String, Any>>,
lineMats: List<Map<String, Any>>
lineMats: List<Map<String, Any>>,
lineDailys: List<Map<String, Any>>
): ByteArray {
val workbook = XSSFWorkbook()

@@ -1531,6 +1555,110 @@ open class ProductionScheduleService(
matSheet.setMargin(Sheet.TopMargin, 0.7)
matSheet.setMargin(Sheet.BottomMargin, 0.7)

// === DAILY MATERIAL NEEDS SHEET - CLEAN & READABLE DATES ===
val dailySheet = workbook.createSheet("Daily Material Needs")

// Generate nicely formatted date headers: "10 Jan" on first row, day of week on second
val dateFormatterDayMonth = DateTimeFormatter.ofPattern("d MMM", Locale.ENGLISH)
val dateFormatterDayName = DateTimeFormatter.ofPattern("EEEE", Locale.ENGLISH) // Monday, Tuesday...

val dateInfo = (1..9).map { offset ->
val date = fromDate.plusDays(offset.toLong())
val dayMonth = date.format(dateFormatterDayMonth) // e.g., "10 Jan"
val dayName = date.format(dateFormatterDayName) // e.g., "Monday"
Pair(dayMonth, dayName)
}

// Headers: Two rows for dates
val headerRow1 = dailySheet.createRow(0)
val headerRow2 = dailySheet.createRow(1)

// Base columns
val baseHeaders = listOf("Mat Code", "Mat Name", "UoM")

baseHeaders.forEachIndexed { i, title ->
val cell1 = headerRow1.createCell(i)
cell1.setCellValue(title)
cell1.cellStyle = headerStyle

val cell2 = headerRow2.createCell(i)
cell2.cellStyle = headerStyle // Empty but merged visually
}

// Date columns: two rows
dateInfo.forEachIndexed { index, (dayMonth, dayName) ->
val colIndex = baseHeaders.size + index

// First row: "10 Jan"
val cellDay = headerRow1.createCell(colIndex)
cellDay.setCellValue(dayMonth)
cellDay.cellStyle = headerStyle

// Second row: "Monday"
val cellWeekDay = headerRow2.createCell(colIndex)
cellWeekDay.setCellValue(dayName)
cellWeekDay.cellStyle = headerStyle
}

// Merge the base header cells vertically (optional, for cleaner look)
for (i in baseHeaders.indices) {
dailySheet.addMergedRegion(CellRangeAddress(0, 1, i, i))
}

// Now write data starting from row 2 (index 2)
lineDailys.forEachIndexed { index, rowData ->
val row = dailySheet.createRow(index + 2) // Start after the two header rows
row.heightInPoints = 30f

val baseValues = listOf<Any>(
rowData["matCode"]?.toString() ?: "",
rowData["matName"]?.toString() ?: "",
rowData["uom"]?.toString() ?: ""
)

val dailyQty = (1..9).map { offset ->
asDouble(rowData["day$offset"])
}

val allValues = baseValues + dailyQty

allValues.forEachIndexed { i, value ->
val cell = row.createCell(i)
when (value) {
is String -> cell.setCellValue(value)
is Number -> cell.setCellValue(value.toDouble())
else -> cell.setCellValue(0.0)
}
cell.cellStyle = wrapStyle
}
}

// === COLUMN WIDTHS ===
dailySheet.setColumnWidth(0, 16 * 256) // Mat Code
dailySheet.setColumnWidth(1, 35 * 256) // Mat Name
dailySheet.setColumnWidth(2, 10 * 256) // UoM

// Date columns: slightly wider for readability
for (i in baseHeaders.size until baseHeaders.size + 10) {
dailySheet.setColumnWidth(i, 14 * 256)
}

// Auto-size base columns (optional improvement)
for (i in 0..2) {
dailySheet.autoSizeColumn(i)
}

// === PRINT SETUP - PORTRAIT ===
val dailyPrintSetup = dailySheet.printSetup
dailyPrintSetup.paperSize = XSSFPrintSetup.A4_PAPERSIZE
dailyPrintSetup.landscape = false
dailyPrintSetup.fitWidth = 1.toShort()
dailyPrintSetup.fitHeight = 0.toShort()
dailySheet.fitToPage = true
dailySheet.horizontallyCenter = true
dailySheet.setMargin(Sheet.TopMargin, 0.7)
dailySheet.setMargin(Sheet.BottomMargin, 0.7)

// Finalize
val out = ByteArrayOutputStream()
workbook.use { it.write(out) }
@@ -1620,6 +1748,85 @@ open class ProductionScheduleService(
}

open fun searchExportDailyMaterial(fromDate: LocalDate): List<Map<String, Any>> {

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

val sql = """
WITH daily_needs AS (
SELECT
itm.id AS materialId,
itm.code AS matCode,
itm.name AS matName,
bm.uomName AS uom,
iv.onHandQty,
iv.unavailableQty,
COALESCE((
SELECT SUM(pol.qty)
FROM purchase_order_line pol
JOIN purchase_order po ON pol.purchaseOrderId = po.id
WHERE pol.itemId = itm.id
AND DATE(po.estimatedArrivalDate) >= CURDATE()
AND po.completeDate IS NULL
), 0) AS purchasedQty,
DATE(ps.produceAt) AS produceDate,
SUM(bm.qty * psl.batchNeed) 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
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)
AND ps.id = (
SELECT ps2.id
FROM production_schedule ps2
WHERE DATE(ps2.produceAt) = DATE(ps.produceAt)
ORDER BY ps2.id DESC
LIMIT 1
)
AND bm.itemId IS NOT NULL
GROUP BY
itm.id,
DATE(ps.produceAt)
)

SELECT
matCode,
matName,
uom,
onHandQty AS currentStock,
purchasedQty AS incomingPO,
(onHandQty + purchasedQty) AS grossAvailable,
-- Dynamic columns for next 7 days
COALESCE(MAX(CASE WHEN produceDate = DATE_ADD(CURDATE(), INTERVAL 1 DAY) THEN qtyNeeded END), 0) AS day1,
COALESCE(MAX(CASE WHEN produceDate = DATE_ADD(CURDATE(), INTERVAL 2 DAY) THEN qtyNeeded END), 0) AS day2,
COALESCE(MAX(CASE WHEN produceDate = DATE_ADD(CURDATE(), INTERVAL 3 DAY) THEN qtyNeeded END), 0) AS day3,
COALESCE(MAX(CASE WHEN produceDate = DATE_ADD(CURDATE(), INTERVAL 4 DAY) THEN qtyNeeded END), 0) AS day4,
COALESCE(MAX(CASE WHEN produceDate = DATE_ADD(CURDATE(), INTERVAL 5 DAY) THEN qtyNeeded END), 0) AS day5,
COALESCE(MAX(CASE WHEN produceDate = DATE_ADD(CURDATE(), INTERVAL 6 DAY) THEN qtyNeeded END), 0) AS day6,
COALESCE(MAX(CASE WHEN produceDate = DATE_ADD(CURDATE(), INTERVAL 7 DAY) THEN qtyNeeded END), 0) AS day7,
-- Total and net
COALESCE(SUM(qtyNeeded), 0) AS totalNeed7Days,
(onHandQty + purchasedQty - COALESCE(SUM(qtyNeeded), 0)) AS netAfter7Days

FROM daily_needs
GROUP BY
materialId, matCode, matName, uom, onHandQty, purchasedQty
ORDER BY netAfter7Days ASC, totalNeed7Days DESC;
""";

return jdbcDao.queryForList(sql, args);
}

@Transactional
open fun clearTodayAndFutureProdSchedule() {
val deleteLinesSql = """


+ 2
- 1
src/main/java/com/ffii/fpsms/modules/master/web/ProductionScheduleController.kt Bestand weergeven

@@ -240,7 +240,8 @@ class ProductionScheduleController(
fun exportProdSchedule(): ResponseEntity<ByteArray> {
val data = productionScheduleService.searchExportProdSchedule(LocalDate.now())
val dataMat = productionScheduleService.searchExportProdScheduleMaterial(LocalDate.now())
val excelContent = productionScheduleService.exportProdScheduleToExcel(data, dataMat)
val dataDaily = productionScheduleService.searchExportDailyMaterial(LocalDate.now())
val excelContent = productionScheduleService.exportProdScheduleToExcel(LocalDate.now(), data, dataMat, dataDaily)
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=production_schedule.xlsx")


Laden…
Annuleren
Opslaan