| @@ -1,38 +1,21 @@ | |||
| package com.ffii.fpsms.modules.bag.web | |||
| import com.ffii.core.response.RecordsRes | |||
| import com.ffii.fpsms.modules.bag.service.BagService | |||
| import jakarta.validation.Valid | |||
| import com.ffii.fpsms.modules.bag.web.model.BagConsumptionResponse | |||
| import com.ffii.fpsms.modules.bag.web.model.BagInfo | |||
| import com.ffii.fpsms.modules.bag.web.model.BagLotLineResponse | |||
| import com.ffii.fpsms.modules.bag.web.model.BagSummaryResponse | |||
| import com.ffii.fpsms.modules.bag.web.model.BagUsageRecordResponse | |||
| import com.ffii.fpsms.modules.bag.web.model.CreateJoBagConsumptionRequest | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import org.springframework.web.bind.annotation.GetMapping | |||
| import org.springframework.web.bind.annotation.ModelAttribute | |||
| import org.springframework.web.bind.annotation.PathVariable | |||
| import org.springframework.web.bind.annotation.PostMapping | |||
| import org.springframework.web.bind.annotation.PutMapping | |||
| import org.springframework.web.bind.annotation.RequestBody | |||
| import org.springframework.web.bind.annotation.RequestMapping | |||
| import org.springframework.web.bind.annotation.RestController | |||
| import com.ffii.fpsms.modules.jobOrder.service.JoPickOrderService | |||
| import com.ffii.fpsms.modules.productProcess.service.ProductProcessService | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.* | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanIssueRequest | |||
| import jakarta.servlet.http.HttpServletResponse | |||
| import net.sf.jasperreports.engine.JasperExportManager | |||
| import net.sf.jasperreports.engine.JasperPrint | |||
| import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException | |||
| import org.springframework.context.NoSuchMessageException | |||
| import java.io.OutputStream | |||
| import java.io.UnsupportedEncodingException | |||
| import java.text.ParseException | |||
| import org.springframework.web.bind.annotation.* | |||
| import org.springframework.web.bind.annotation.RequestParam | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.UpdateJoPickOrderHandledByRequest | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfo | |||
| import com.ffii.fpsms.modules.jobOrder.entity.projections.JobOrderInfoWithTypeName | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportFGStockInLabelRequest | |||
| import com.ffii.fpsms.modules.bag.web.model.* | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| @RestController | |||
| @RequestMapping("/bag") | |||
| class BagController( | |||
| @@ -43,14 +26,17 @@ class BagController( | |||
| fun getBagInfo(): List<BagInfo> { | |||
| return bagService.getAllBagInfo() | |||
| } | |||
| @PostMapping("/createJoBagConsumption") | |||
| fun createJoBagConsumption(@RequestBody request: CreateJoBagConsumptionRequest): MessageResponse { | |||
| return bagService.createJoBagConsumption(request) | |||
| } | |||
| @GetMapping("/bagUsageRecords") | |||
| fun getBagUsageRecords(): List<BagUsageRecordResponse> { | |||
| return bagService.getAllBagUsageRecords() | |||
| } | |||
| @GetMapping("/bags") | |||
| fun getBags(): List<BagSummaryResponse> = | |||
| bagService.getBagSummaries() | |||
| @@ -66,4 +52,4 @@ class BagController( | |||
| @PutMapping("/by-item/{itemId}/soft-delete") | |||
| fun softDeleteBagByItemId(@PathVariable itemId: Long): MessageResponse = | |||
| bagService.softDeleteBagByItemId(itemId) | |||
| } | |||
| } | |||
| @@ -51,6 +51,7 @@ import com.ffii.fpsms.modules.stock.entity.enum.StockInLineStatus | |||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | |||
| import java.time.LocalDate | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.MaterialPickStatusItem | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.PlasticBoxCartonQtyDashboardRecord | |||
| @Service | |||
| open class JoPickOrderService( | |||
| private val joPickOrderRepository: JoPickOrderRepository, | |||
| @@ -1688,6 +1689,9 @@ open fun getCompletedJobOrderPickOrders(completedDate: LocalDate?): List<Map<Str | |||
| "secondScanCompleted" to secondScanCompleted, | |||
| "totalItems" to joPickOrders.size, | |||
| "completedItems" to joPickOrders.count { it.matchStatus == JoPickOrderStatus.completed }, | |||
| "plasticBoxCartonQty2f" to pickOrder.plasticBoxCartonQty2f, | |||
| "plasticBoxCartonQty3f" to pickOrder.plasticBoxCartonQty3f, | |||
| "plasticBoxCartonQty4f" to pickOrder.plasticBoxCartonQty4f, | |||
| ) | |||
| } else { | |||
| println("❌ Pick order ${pickOrder.id} has no job order, skipping.") | |||
| @@ -1703,6 +1707,32 @@ open fun getCompletedJobOrderPickOrders(completedDate: LocalDate?): List<Map<Str | |||
| emptyList() | |||
| } | |||
| } | |||
| open fun getPlasticBoxCartonQtyDashboard( | |||
| from: LocalDate, | |||
| to: LocalDate, | |||
| ): List<PlasticBoxCartonQtyDashboardRecord> { | |||
| val fromDt = from.atStartOfDay() | |||
| val toExclusive = to.plusDays(1).atStartOfDay() | |||
| return pickOrderRepository | |||
| .findCompletedWithPlasticBoxCartonQtyInPlanStartRange( | |||
| PickOrderStatus.COMPLETED, | |||
| fromDt, | |||
| toExclusive, | |||
| ) | |||
| .mapNotNull { pickOrder -> | |||
| val planStart = pickOrder.jobOrder?.planStart ?: return@mapNotNull null | |||
| val statLocalDate = planStart.toLocalDate() | |||
| PlasticBoxCartonQtyDashboardRecord( | |||
| pickOrderId = pickOrder.id, | |||
| statDate = "${statLocalDate.year}-${"%02d".format(statLocalDate.monthValue)}-${"%02d".format(statLocalDate.dayOfMonth)}", | |||
| plasticBoxCartonQty2f = pickOrder.plasticBoxCartonQty2f, | |||
| plasticBoxCartonQty3f = pickOrder.plasticBoxCartonQty3f, | |||
| plasticBoxCartonQty4f = pickOrder.plasticBoxCartonQty4f, | |||
| ) | |||
| } | |||
| } | |||
| open fun getJobOrderPickOrders(date: LocalDate?, status: PickOrderStatus?): List<Map<String, Any?>> { | |||
| println("=== getJobOrderPickOrders ===") | |||
| println("date: $date, status: $status") | |||
| @@ -737,6 +737,92 @@ open class JobOrderService( | |||
| } | |||
| //Pick Record | |||
| private fun validatePickRecordFloor(floor: String?): String { | |||
| val normalizedFloor = floor?.trim()?.uppercase() | |||
| ?: throw BadRequestException("floor is required for pick record print") | |||
| if (normalizedFloor !in setOf("2F", "3F", "4F", "ALL")) { | |||
| throw BadRequestException("floor must be one of 2F, 3F, 4F, ALL") | |||
| } | |||
| return normalizedFloor | |||
| } | |||
| private fun validatePlasticBoxCartonQty(qty: Int?): Int { | |||
| val value = qty ?: throw BadRequestException("plasticBoxCartonQty is required") | |||
| if (value < 1) { | |||
| throw BadRequestException("plasticBoxCartonQty must be at least 1") | |||
| } | |||
| return value | |||
| } | |||
| private data class AllFloorsPlasticBoxCartonQty( | |||
| val qty2f: Int, | |||
| val qty3f: Int, | |||
| val qty4f: Int, | |||
| val sum: Int, | |||
| ) | |||
| private fun normalizeFloorPlasticBoxCartonQty(qty: Int?): Int { | |||
| if (qty == null) return 0 | |||
| if (qty < 0) { | |||
| throw BadRequestException("plastic box carton qty cannot be negative") | |||
| } | |||
| return qty | |||
| } | |||
| private fun resolveAllFloorsPlasticBoxCartonQty( | |||
| qty2f: Int?, | |||
| qty3f: Int?, | |||
| qty4f: Int?, | |||
| ): AllFloorsPlasticBoxCartonQty { | |||
| val q2 = normalizeFloorPlasticBoxCartonQty(qty2f) | |||
| val q3 = normalizeFloorPlasticBoxCartonQty(qty3f) | |||
| val q4 = normalizeFloorPlasticBoxCartonQty(qty4f) | |||
| return AllFloorsPlasticBoxCartonQty(q2, q3, q4, q2 + q3 + q4) | |||
| } | |||
| private fun updatePickOrderPlasticBoxCartonQty(pickOrderId: Long, floor: String, qty: Int) { | |||
| val pickOrder = pickOrderRepository.findById(pickOrderId).orElse(null) ?: return | |||
| when (floor) { | |||
| "2F" -> pickOrder.plasticBoxCartonQty2f = qty | |||
| "3F" -> pickOrder.plasticBoxCartonQty3f = qty | |||
| "4F" -> pickOrder.plasticBoxCartonQty4f = qty | |||
| } | |||
| pickOrderRepository.save(pickOrder) | |||
| } | |||
| private fun persistAllFloorsPlasticBoxCartonQty(pickOrderId: Long, all: AllFloorsPlasticBoxCartonQty) { | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, "2F", all.qty2f) | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, "3F", all.qty3f) | |||
| updatePickOrderPlasticBoxCartonQty(pickOrderId, "4F", all.qty4f) | |||
| } | |||
| open fun getPickRecordPlasticBoxCartonQty(pickOrderId: Long): PickRecordPlasticBoxCartonQtyResponse { | |||
| val pickOrder = pickOrderRepository.findById(pickOrderId).orElseThrow { | |||
| NoSuchElementException("Pick order not found with ID: $pickOrderId") | |||
| } | |||
| return PickRecordPlasticBoxCartonQtyResponse( | |||
| plasticBoxCartonQty2f = pickOrder.plasticBoxCartonQty2f, | |||
| plasticBoxCartonQty3f = pickOrder.plasticBoxCartonQty3f, | |||
| plasticBoxCartonQty4f = pickOrder.plasticBoxCartonQty4f, | |||
| ) | |||
| } | |||
| private fun resolvePlasticBoxCartonQtyForPickRecord(request: ExportPickRecordRequest): Int { | |||
| val floor = validatePickRecordFloor(request.floor) | |||
| return if (floor == "ALL") { | |||
| val all = resolveAllFloorsPlasticBoxCartonQty( | |||
| request.plasticBoxCartonQty2f, | |||
| request.plasticBoxCartonQty3f, | |||
| request.plasticBoxCartonQty4f, | |||
| ) | |||
| persistAllFloorsPlasticBoxCartonQty(request.pickOrderIds, all) | |||
| all.sum | |||
| } else { | |||
| request.plasticBoxCartonQty | |||
| ?: throw BadRequestException("plasticBoxCartonQty is required") | |||
| } | |||
| } | |||
| @Throws(IOException::class) | |||
| @Transactional | |||
| open fun exportPickRecord(request: ExportPickRecordRequest): Map<String, Any> { | |||
| @@ -821,6 +907,8 @@ open class JobOrderService( | |||
| println("unit (from BOM): $unit")*/ | |||
| params["unit"] = pickRecordInfo.firstOrNull()?.get("uomConversionDesc") as? String ?: "N/A" | |||
| val plasticBoxCartonQtyForPdf = resolvePlasticBoxCartonQtyForPickRecord(request) | |||
| params["PlasticBoxCartonQty"] = plasticBoxCartonQtyForPdf.toString() | |||
| val pickOrderCode = pickRecordInfo.firstOrNull()?.get("pickOrderCode") as? String ?: "unknown" | |||
| return mapOf( | |||
| @@ -833,13 +921,26 @@ open class JobOrderService( | |||
| @Transactional | |||
| open fun printPickRecord(request: PrintPickRecordRequest){ | |||
| val printer = printerService.findById(request.printerId) ?: throw java.util.NoSuchElementException("No such printer") | |||
| val pdf = exportPickRecord( | |||
| val floor = validatePickRecordFloor(request.floor) | |||
| val exportRequest = if (floor == "ALL") { | |||
| ExportPickRecordRequest( | |||
| pickOrderIds = request.pickOrderId, | |||
| floor = request.floor, | |||
| plasticBoxCartonQty2f = request.plasticBoxCartonQty2f, | |||
| plasticBoxCartonQty3f = request.plasticBoxCartonQty3f, | |||
| plasticBoxCartonQty4f = request.plasticBoxCartonQty4f, | |||
| ) | |||
| ) | |||
| } else { | |||
| val plasticBoxCartonQty = validatePlasticBoxCartonQty(request.plasticBoxCartonQty) | |||
| updatePickOrderPlasticBoxCartonQty(request.pickOrderId, floor, plasticBoxCartonQty) | |||
| ExportPickRecordRequest( | |||
| pickOrderIds = request.pickOrderId, | |||
| floor = request.floor, | |||
| plasticBoxCartonQty = plasticBoxCartonQty, | |||
| ) | |||
| } | |||
| val pdf = exportPickRecord(exportRequest) | |||
| val jasperPrint = pdf["report"] as JasperPrint | |||
| @@ -27,7 +27,6 @@ import com.ffii.fpsms.modules.pickOrder.enums.PickOrderStatus | |||
| import org.springframework.format.annotation.DateTimeFormat | |||
| import com.ffii.fpsms.modules.productProcess.service.ProductProcessService | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.* | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.ExportPickRecordRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.PrintPickRecordRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanSubmitRequest | |||
| import com.ffii.fpsms.modules.jobOrder.web.model.SecondScanIssueRequest | |||
| @@ -224,16 +223,9 @@ fun recordSecondScanIssue( | |||
| return joPickOrderService.getCompletedJobOrderPickOrderLotDetails(pickOrderId) | |||
| } | |||
| @PostMapping("/PickRecord") | |||
| @Throws(UnsupportedEncodingException::class, NoSuchMessageException::class, ParseException::class, Exception::class) | |||
| fun printPickRecord(@Valid @RequestBody request: ExportPickRecordRequest, response: HttpServletResponse){ | |||
| response.characterEncoding = "utf-8" | |||
| response.contentType = "application/pdf" | |||
| val out: OutputStream = response.outputStream | |||
| val pdf = jobOrderService.exportPickRecord(request) | |||
| val jasperPrint = pdf["report"] as JasperPrint | |||
| response.addHeader("filename", "${pdf["filename"]}.pdf") | |||
| out.write(JasperExportManager.exportReportToPdf(jasperPrint)) | |||
| @GetMapping("/pick-record-plastic-box-carton-qty/{pickOrderId}") | |||
| fun getPickRecordPlasticBoxCartonQty(@PathVariable pickOrderId: Long): PickRecordPlasticBoxCartonQtyResponse { | |||
| return jobOrderService.getPickRecordPlasticBoxCartonQty(pickOrderId) | |||
| } | |||
| @GetMapping("/print-PickRecord") | |||
| @@ -272,6 +264,18 @@ fun recordSecondScanIssue( | |||
| ): List<Map<String, Any?>> { | |||
| return joPickOrderService.getCompletedJobOrderPickOrders(completedDate) | |||
| } | |||
| @GetMapping("/plastic-box-carton-qty-dashboard") | |||
| fun getPlasticBoxCartonQtyDashboard( | |||
| @RequestParam(name = "from") | |||
| @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) | |||
| from: LocalDate, | |||
| @RequestParam(name = "to") | |||
| @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) | |||
| to: LocalDate, | |||
| ): List<PlasticBoxCartonQtyDashboardRecord> { | |||
| return joPickOrderService.getPlasticBoxCartonQtyDashboard(from, to) | |||
| } | |||
| @GetMapping("/job-order-pick-orders") | |||
| fun getJobOrderPickOrders( | |||
| @RequestParam(name = "date", required = false) | |||
| @@ -3,4 +3,8 @@ package com.ffii.fpsms.modules.jobOrder.web.model | |||
| data class ExportPickRecordRequest ( | |||
| val pickOrderIds: Long, | |||
| val floor: String? = null, | |||
| val plasticBoxCartonQty: Int? = null, | |||
| val plasticBoxCartonQty2f: Int? = null, | |||
| val plasticBoxCartonQty3f: Int? = null, | |||
| val plasticBoxCartonQty4f: Int? = null, | |||
| ) | |||
| @@ -0,0 +1,7 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.web.model | |||
| data class PickRecordPlasticBoxCartonQtyResponse( | |||
| val plasticBoxCartonQty2f: Int?, | |||
| val plasticBoxCartonQty3f: Int?, | |||
| val plasticBoxCartonQty4f: Int?, | |||
| ) | |||
| @@ -0,0 +1,9 @@ | |||
| package com.ffii.fpsms.modules.jobOrder.web.model | |||
| data class PlasticBoxCartonQtyDashboardRecord( | |||
| val pickOrderId: Long?, | |||
| val statDate: String, | |||
| val plasticBoxCartonQty2f: Int? = null, | |||
| val plasticBoxCartonQty3f: Int? = null, | |||
| val plasticBoxCartonQty4f: Int? = null, | |||
| ) | |||
| @@ -5,4 +5,8 @@ data class PrintPickRecordRequest( | |||
| val printerId: Long, | |||
| val printQty: Int?, | |||
| val floor: String? = null, | |||
| val plasticBoxCartonQty: Int? = null, | |||
| val plasticBoxCartonQty2f: Int? = null, | |||
| val plasticBoxCartonQty3f: Int? = null, | |||
| val plasticBoxCartonQty4f: Int? = null, | |||
| ) | |||
| @@ -72,4 +72,13 @@ open class PickOrder: BaseEntity<Long>() { | |||
| @Column(name = "submittedLines") | |||
| open var submittedLines: Int? = null | |||
| @Column(name = "plastic_box_carton_qty_2f") | |||
| open var plasticBoxCartonQty2f: Int? = null | |||
| @Column(name = "plastic_box_carton_qty_3f") | |||
| open var plasticBoxCartonQty3f: Int? = null | |||
| @Column(name = "plastic_box_carton_qty_4f") | |||
| open var plasticBoxCartonQty4f: Int? = null | |||
| } | |||
| @@ -135,6 +135,28 @@ fun findAllCompletedWithJobOrderPlanStartOnDay( | |||
| @Param("planStartToExclusive") planStartToExclusive: LocalDateTime, | |||
| ): List<PickOrder> | |||
| @Query( | |||
| """ | |||
| SELECT po FROM PickOrder po | |||
| WHERE po.status = :status | |||
| AND po.deleted = false | |||
| AND po.jobOrder IS NOT NULL | |||
| AND po.jobOrder.planStart IS NOT NULL | |||
| AND po.jobOrder.planStart >= :planStartFrom | |||
| AND po.jobOrder.planStart < :planStartToExclusive | |||
| AND ( | |||
| po.plasticBoxCartonQty2f IS NOT NULL | |||
| OR po.plasticBoxCartonQty3f IS NOT NULL | |||
| OR po.plasticBoxCartonQty4f IS NOT NULL | |||
| ) | |||
| """ | |||
| ) | |||
| fun findCompletedWithPlasticBoxCartonQtyInPlanStartRange( | |||
| @Param("status") status: PickOrderStatus, | |||
| @Param("planStartFrom") planStartFrom: LocalDateTime, | |||
| @Param("planStartToExclusive") planStartToExclusive: LocalDateTime, | |||
| ): List<PickOrder> | |||
| @Modifying(clearAutomatically = true, flushAutomatically = true) | |||
| @Query( | |||
| @@ -8,6 +8,7 @@ | |||
| <parameter name="FGName" class="java.lang.String"/> | |||
| <parameter name="RequiredQuantity" class="java.lang.String"/> | |||
| <parameter name="Floor" class="java.lang.String"/> | |||
| <parameter name="PlasticBoxCartonQty" class="java.lang.String"/> | |||
| <queryString> | |||
| <![CDATA[]]> | |||
| </queryString> | |||
| @@ -24,6 +25,25 @@ | |||
| </background> | |||
| <pageHeader> | |||
| <band height="103" splitType="Stretch"> | |||
| <staticText> | |||
| <reportElement x="5" y="5" width="70" height="18" uuid="a1b2c3d4-e5f6-7890-abcd-ef1234567890"> | |||
| <property name="com.jaspersoft.studio.unit.y" value="px"/> | |||
| <property name="com.jaspersoft.studio.unit.height" value="px"/> | |||
| </reportElement> | |||
| <textElement textAlignment="Left" verticalAlignment="Middle"> | |||
| <font fontName="微軟正黑體" size="12"/> | |||
| </textElement> | |||
| <text><![CDATA[膠茜數目:]]></text> | |||
| </staticText> | |||
| <textField> | |||
| <reportElement x="75" y="5" width="80" height="18" uuid="b2c3d4e5-f6a7-8901-bcde-f12345678901"> | |||
| <property name="com.jaspersoft.studio.unit.height" value="px"/> | |||
| </reportElement> | |||
| <textElement verticalAlignment="Middle"> | |||
| <font fontName="微軟正黑體" size="12"/> | |||
| </textElement> | |||
| <textFieldExpression><![CDATA[$P{PlasticBoxCartonQty}]]></textFieldExpression> | |||
| </textField> | |||
| <textField> | |||
| <reportElement x="180" y="5" width="153" height="23" uuid="324397fb-d07b-4748-a917-7de93ebbe012"> | |||
| <property name="com.jaspersoft.studio.unit.width" value="px"/> | |||
| @@ -0,0 +1,8 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset fpsms:alter_pick_order_plastic_box_carton_qty | |||
| ALTER TABLE `pick_order` | |||
| ADD COLUMN `plastic_box_carton_qty_2f` INT NULL DEFAULT NULL AFTER `submittedLines`, | |||
| ADD COLUMN `plastic_box_carton_qty_3f` INT NULL DEFAULT NULL AFTER `plastic_box_carton_qty_2f`, | |||
| ADD COLUMN `plastic_box_carton_qty_4f` INT NULL DEFAULT NULL AFTER `plastic_box_carton_qty_3f`, | |||
| ADD COLUMN `plastic_box_carton_qty_all` INT NULL DEFAULT NULL AFTER `plastic_box_carton_qty_4f`; | |||
| @@ -0,0 +1,5 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset fpsms:drop_pick_order_plastic_box_carton_qty_all | |||
| ALTER TABLE `pick_order` | |||
| DROP COLUMN `plastic_box_carton_qty_all`; | |||