Browse Source

adding shcedule query

master
[email protected] 1 month ago
parent
commit
19e2039a52
5 changed files with 228 additions and 64 deletions
  1. +41
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt
  2. +19
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  3. +1
    -0
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt
  4. +7
    -3
      src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt
  5. +160
    -61
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt

+ 41
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/entity/JobOrderRepository.kt View File

@@ -172,10 +172,51 @@ interface JobOrderRepository : AbstractRepository<JobOrder, Long> {
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
and jo.actualEnd is null
group by jo.id, uc2.udfudesc
limit 1
"""
)
fun findJobOrderByProdScheduleLineId(prodScheduleLineId: Long): JobOrderDetailWithJsonString?;

@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 b.itemId = :itemId
and jo.actualEnd is null
group by jo.id, uc2.udfudesc
limit 1
"""
)
fun findJobOrderByItemId(itemId: Long): JobOrderDetailWithJsonString?;

}

+ 19
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt View File

@@ -286,6 +286,25 @@ open class JobOrderService(
)
}

open fun jobOrderDetailByItemId(itemId: Long): JobOrderDetail {
val sqlResult = jobOrderRepository.findJobOrderByItemId(itemId) ?: throw NoSuchElementException("Job Order not found with itemId: $itemId");

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 {
val sqlResult = jobOrderRepository.findJobOrderDetailByCode(code) ?: throw NoSuchElementException("Job Order not found: $code");



+ 1
- 0
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt View File

@@ -39,6 +39,7 @@ interface ProductionScheduleLineRepository : AbstractRepository<ProductionSchedu
group by psl.id, bm.id, i.id, pp.proportion
""")
fun getBomMaterials(id: Long): List<DetailedProdScheduleLineBomMaterialInterface>?
@Query("""
SELECT psl FROM ProductionScheduleLine psl
JOIN psl.productionSchedule ps


+ 7
- 3
src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleRepository.kt View File

@@ -7,6 +7,7 @@ import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
import java.time.LocalDate

@Repository
interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, Long> {
@@ -117,8 +118,11 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
where rn = 1
and produceAt is not null
and scheduleAt in (select max(ps2.scheduleAt) from production_schedule ps2 group by ps2.produceAt)
-- and (:scheduleAt = '' or datediff(scheduleAt, coalesce(:scheduleAt, scheduleAt)) = 0)
and (:produceAt = '' or datediff(produceAt, coalesce(:produceAt, produceAt)) = 0)
AND (
:produceAt IS NULL
OR :produceAt = ''
OR Date(produceAt) >= :produceAt
)
and (:totalEstProdCount is null or :totalEstProdCount = '' or totalEstProdCount = :totalEstProdCount)
and (coalesce(:types) is null or type in :types)
order by id ASC;
@@ -126,7 +130,7 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule,
)
fun findProdScheduleInfoByProduceAtByPage(
// scheduleAt: String?,
produceAt: String?,
produceAt: LocalDate,
totalEstProdCount: Double?,
types: List<String>?,
pageable: Pageable


+ 160
- 61
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt View File

@@ -11,6 +11,7 @@ import com.ffii.fpsms.modules.jobOrder.service.JobOrderService
import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderBomMaterialRequest
import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderProcessRequest
import com.ffii.fpsms.modules.jobOrder.web.model.CreateJobOrderRequest
import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderDetail
import com.ffii.fpsms.modules.master.entity.*
import com.ffii.fpsms.modules.master.entity.projections.*
import com.ffii.fpsms.modules.master.web.models.MessageResponse
@@ -46,10 +47,16 @@ import kotlin.collections.component2
import kotlin.jvm.optionals.getOrNull
import kotlin.math.ceil
import kotlin.comparisons.maxOf
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.apache.poi.ss.usermodel.FillPatternType

// === 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.xssf.usermodel.XSSFWorkbook
import org.apache.poi.xssf.usermodel.XSSFPrintSetup
import java.io.ByteArrayOutputStream
import java.sql.Timestamp

@Service
open class ProductionScheduleService(
@@ -118,10 +125,15 @@ open class ProductionScheduleService(
open fun allDetailedProdSchedulesByPage(request: SearchProdScheduleRequest): RecordsRes<ProdScheduleInfo> {
val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10);

val produceAtDate: LocalDate = request.produceAt?.takeIf { it.isNotBlank() }
?.let {
LocalDate.parse(it.trim())
}
?: LocalDate.now()

val response = productionScheduleRepository.findProdScheduleInfoByProduceAtByPage(
produceAt = request.produceAt,
produceAt = produceAtDate!!,
totalEstProdCount = request.totalEstProdCount,
// types = listOf("detailed", "manual"),
types = request.types,
pageable = pageable
)
@@ -397,18 +409,22 @@ open class ProductionScheduleService(
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.")
// 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 itemId = item.id
?: throw IllegalStateException("Item ID is missing for Production Schedule Line $prodScheduleLineId.")

val bom = bomService.findByItemId(itemId)
try {
jobOrderService.jobOrderDetailByItemId(itemId)
} catch (e: NoSuchElementException) {
//only do with no JO is working
try {
jobOrderService.jobOrderDetailByItemId(itemId)
} catch (e: NoSuchElementException) {
val bom = bomService.findByItemId(itemId)
?: throw NoSuchElementException("BOM not found for Item ID $itemId.")

// 4. Update Prod Schedule Line fields
@@ -448,6 +464,7 @@ open class ProductionScheduleService(
jobOrderProcessService.createJobOrderProcessesByJoId(createdJobOrderId)
productProcessService.createProductProcessByJobOrderId(createdJobOrderId, prodScheduleLine.itemPriority.toInt())
//}
}
}

@@ -1338,60 +1355,106 @@ open class ProductionScheduleService(

}

fun exportProdScheduleToExcel(lines: List<Map<String, Any>>, lineMats: List<Map<String, Any>>): ByteArray {
fun exportProdScheduleToExcel(
lines: List<Map<String, Any>>,
lineMats: List<Map<String, Any>>
): ByteArray {
val workbook = XSSFWorkbook()
// 1. Group Production Lines by Date
val groupedData = lines.groupBy {
val produceAt = it["produceAt"]
when (produceAt) {
is LocalDateTime -> produceAt.toLocalDate().toString()
is java.sql.Timestamp -> produceAt.toLocalDateTime().toLocalDate().toString()
else -> produceAt?.toString()?.substring(0, 10) ?: "Unknown_Date"
}
}

// 2. Define Header Style
// Header style
val headerStyle = workbook.createCellStyle().apply {
fillForegroundColor = IndexedColors.GREY_25_PERCENT.index
fillPattern = FillPatternType.SOLID_FOREGROUND
wrapText = true
verticalAlignment = VerticalAlignment.CENTER
val font = workbook.createFont()
font.bold = true
setFont(font)
}

// 3. Create Production Worksheets
// Body style
val wrapStyle = workbook.createCellStyle().apply {
wrapText = true
verticalAlignment = VerticalAlignment.TOP
}

// Group production lines by date
val groupedData = lines.groupBy {
val produceAt = it["produceAt"]
when (produceAt) {
is LocalDateTime -> produceAt.toLocalDate().toString()
is Timestamp -> produceAt.toLocalDateTime().toLocalDate().toString()
is String -> produceAt.take(10)
else -> produceAt?.toString()?.substring(0, 10) ?: "Unknown_Date"
}
}

// Production sheets (one per date)
groupedData.forEach { (dateKey, dailyLines) ->
val sheetName = dateKey.replace("[/\\\\?*:\\[\\]]".toRegex(), "-")
val sheet = workbook.createSheet(sheetName)
val safeSheetName = dateKey.replace(Regex("[/\\\\?*:\\[\\]]"), "-").take(31)
val sheet = workbook.createSheet(safeSheetName)

val headers = listOf("Item Name", "Avg Qty Last Month", "Stock Qty", "Days Left", "Output Qty", "Batch Need", "Priority")
val headers = listOf(
"Item Name", "Avg Qty Last Month", "Stock Qty", "Days Left",
"Output Qty", "Batch Need", "Priority"
)

// Header row
val headerRow = sheet.createRow(0)
headers.forEachIndexed { i, title ->
val cell = headerRow.createCell(i)
cell.setCellValue(title)
cell.setCellStyle(headerStyle)
headerRow.createCell(i).apply {
setCellValue(title)
cellStyle = headerStyle
}
}

// Data rows
dailyLines.forEachIndexed { index, line ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(line["itemName"]?.toString() ?: "")
row.createCell(1).setCellValue(asDouble(line["avgQtyLastMonth"]))
row.createCell(2).setCellValue(asDouble(line["stockQty"]))
row.createCell(3).setCellValue(asDouble(line["daysLeft"]))
row.createCell(4).setCellValue(asDouble(line["outputdQty"])) // Note: Matching your snippet's "outputdQty" key
row.createCell(5).setCellValue(asDouble(line["batchNeed"]))
row.createCell(6).setCellValue(asDouble(line["itemPriority"]))
row.heightInPoints = 35f // Slightly taller for portrait readability

row.createCell(0).apply { setCellValue(line["itemName"]?.toString() ?: ""); cellStyle = wrapStyle }
row.createCell(1).apply { setCellValue(asDouble(line["avgQtyLastMonth"])); cellStyle = wrapStyle }
row.createCell(2).apply { setCellValue(asDouble(line["stockQty"])); cellStyle = wrapStyle }
row.createCell(3).apply { setCellValue(asDouble(line["daysLeft"])); cellStyle = wrapStyle }
row.createCell(4).apply { setCellValue(asDouble(line["outputdQty"] ?: line["outputQty"])); cellStyle = wrapStyle }
row.createCell(5).apply { setCellValue(asDouble(line["batchNeed"])); cellStyle = wrapStyle }
row.createCell(6).apply { setCellValue(asDouble(line["itemPriority"])); cellStyle = wrapStyle }
}

for (i in headers.indices) { sheet.autoSizeColumn(i) }
// Auto-size with wider limits for portrait
for (i in headers.indices) {
sheet.autoSizeColumn(i)
val maxWidth = when (i) {
0 -> 35 * 256 // Item Name can be longer
else -> 18 * 256
}
if (sheet.getColumnWidth(i) > maxWidth) {
sheet.setColumnWidth(i, maxWidth)
}
}

// === PORTRAIT PRINT SETUP ===
val printSetup = sheet.printSetup
printSetup.paperSize = XSSFPrintSetup.A4_PAPERSIZE
printSetup.landscape = false // ← Portrait mode
printSetup.fitWidth = 1.toShort() // Crucial: scale to fit width
printSetup.fitHeight = 0.toShort() // Allow multiple pages tall

sheet.fitToPage = true
sheet.horizontallyCenter = true
sheet.setMargin(Sheet.LeftMargin, 0.5)
sheet.setMargin(Sheet.RightMargin, 0.5)
sheet.setMargin(Sheet.TopMargin, 0.7)
sheet.setMargin(Sheet.BottomMargin, 0.7)
}

// 4. Create Material Summary Worksheet
// === MATERIAL SUMMARY SHEET - PORTRAIT OPTIMIZED ===
val matSheet = workbook.createSheet("Material Summary")

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

@@ -1399,37 +1462,73 @@ open class ProductionScheduleService(
matHeaders.forEachIndexed { i, title ->
matHeaderRow.createCell(i).apply {
setCellValue(title)
setCellStyle(headerStyle)
cellStyle = headerStyle
}
}

lineMats.forEachIndexed { index, rowData ->
val row = matSheet.createRow(index + 1)
row.heightInPoints = 35f

val totalNeed = asDouble(rowData["totalMatQtyNeed"])
val purchased = asDouble(rowData["purchasedQty"])
val onHand = asDouble(rowData["onHandQty"])
// Calculation: Required Qty = totalMatQtyNeed - purchasedQty - onHandQty (minimum 0)
val requiredQty = (totalNeed - purchased - onHand).coerceAtLeast(0.0)

row.createCell(0).setCellValue(rowData["matCode"]?.toString() ?: "")
row.createCell(1).setCellValue(rowData["matName"]?.toString() ?: "")
row.createCell(2).setCellValue(requiredQty)
row.createCell(3).setCellValue(totalNeed)
row.createCell(4).setCellValue(rowData["uomName"]?.toString() ?: "")
row.createCell(5).setCellValue(purchased)
row.createCell(6).setCellValue(onHand)
row.createCell(7).setCellValue(asDouble(rowData["unavailableQty"]))
row.createCell(8).setCellValue(rowData["itemCode"]?.toString() ?: "")
row.createCell(9).setCellValue(rowData["itemName"]?.toString() ?: "")
}
val values = listOf<Any>(
rowData["matCode"]?.toString() ?: "",
rowData["matName"]?.toString() ?: "",
requiredQty,
totalNeed,
rowData["uomName"]?.toString() ?: "",
purchased,
onHand,
asDouble(rowData["unavailableQty"]),
rowData["itemCode"]?.toString() ?: "",
rowData["itemName"]?.toString() ?: ""
)

for (i in matHeaders.indices) { matSheet.autoSizeColumn(i) }
values.forEachIndexed { i, value ->
val cell = row.createCell(i)
when (value) {
is String -> cell.setCellValue(value)
is Number -> cell.setCellValue(value.toDouble())
else -> cell.setCellValue("")
}
cell.cellStyle = wrapStyle
}
}

// 5. Finalize and Return
// Manual column widths optimized for PORTRAIT A4
matSheet.setColumnWidth(0, 16 * 256) // Mat Code
matSheet.setColumnWidth(1, 32 * 256) // Mat Name
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)

// Portrait print setup
val matPrintSetup = matSheet.printSetup
matPrintSetup.paperSize = XSSFPrintSetup.A4_PAPERSIZE
matPrintSetup.landscape = false // ← Portrait
matPrintSetup.fitWidth = 1.toShort()
matPrintSetup.fitHeight = 0.toShort()

matSheet.fitToPage = true
matSheet.horizontallyCenter = true
matSheet.setMargin(Sheet.LeftMargin, 0.5)
matSheet.setMargin(Sheet.RightMargin, 0.5)
matSheet.setMargin(Sheet.TopMargin, 0.7)
matSheet.setMargin(Sheet.BottomMargin, 0.7)

// Finalize
val out = ByteArrayOutputStream()
workbook.use { it.write(out) }
workbook.close()
return out.toByteArray()
}



Loading…
Cancel
Save