# Conflicts: # src/main/java/com/ffii/fpsms/modules/pickOrder/entity/TruckRepository.ktmaster
| @@ -165,7 +165,15 @@ open class M18MasterDataService( | |||
| maxQty = null, | |||
| m18Id = id, | |||
| m18LastModifyDate = commonUtils.timestampToLocalDateTime(pro.lastModifyDate), | |||
| qcCategoryId = null | |||
| qcCategoryId = null, | |||
| store_id = null, | |||
| warehouse = null, | |||
| area = null, | |||
| slot = null, | |||
| LocationCode = null, | |||
| isEgg = null, | |||
| isFee = null, | |||
| isBag = null | |||
| ) | |||
| val savedItem = itemsService.saveItem(saveItemRequest) | |||
| @@ -260,7 +268,15 @@ open class M18MasterDataService( | |||
| maxQty = null, | |||
| m18Id = item.id, | |||
| m18LastModifyDate = commonUtils.timestampToLocalDateTime(pro.lastModifyDate), | |||
| qcCategoryId = null | |||
| qcCategoryId = null, | |||
| store_id = null, | |||
| warehouse = null, | |||
| area = null, | |||
| slot = null, | |||
| LocationCode = null, | |||
| isEgg = null, | |||
| isFee = null, | |||
| isBag = null | |||
| ) | |||
| val savedItem = itemsService.saveItem(saveItemRequest) | |||
| @@ -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?; | |||
| } | |||
| @@ -288,6 +288,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"); | |||
| @@ -6,6 +6,7 @@ import jakarta.persistence.Entity | |||
| import jakarta.persistence.Table | |||
| import jakarta.validation.constraints.NotNull | |||
| import jakarta.validation.constraints.Size | |||
| import java.time.LocalDateTime | |||
| @Table(name = "equipment_detail") | |||
| @Entity | |||
| @@ -15,6 +16,19 @@ open class EquipmentDetail : BaseEntity<Long>() { | |||
| @Column(name = "equipmentCode", nullable = true, length = 255) | |||
| open var equipmentCode: String? = null | |||
| @Column(name = "repairAndMaintenanceStatus", nullable = true) | |||
| open var repairAndMaintenanceStatus: Boolean? = null | |||
| @Column(name = "latestRepairAndMaintenanceDate", nullable = true) | |||
| open var latestRepairAndMaintenanceDate: LocalDateTime? = null | |||
| @Column(name = "lastRepairAndMaintenanceDate", nullable = true) | |||
| open var lastRepairAndMaintenanceDate: LocalDateTime? = null | |||
| @Size(max = 255) | |||
| @Column(name = "repairAndMaintenanceRemarks", nullable = true, length = 255) | |||
| open var repairAndMaintenanceRemarks: String? = null | |||
| @Size(max = 30) | |||
| @NotNull | |||
| @Column(name = "code", nullable = false, length = 30) | |||
| @@ -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,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 | |||
| @@ -200,6 +204,7 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||
| select | |||
| prod.id, | |||
| prod.scheduleAt, | |||
| prod.produceAt, | |||
| prod.totalFGType, | |||
| prod.totalEstProdCount, | |||
| prod.daysLeft, | |||
| @@ -237,6 +242,7 @@ interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, | |||
| select | |||
| ps.id, | |||
| ps.scheduleAt, | |||
| ps.produceAt, | |||
| ps.totalFGType, | |||
| ps.totalEstProdCount, | |||
| psl.approverId is not null as approved, | |||
| @@ -33,7 +33,7 @@ interface ShopRepository : AbstractRepository<Shop, Long> { | |||
| @Query( | |||
| nativeQuery = true, | |||
| value = """ | |||
| SELECT s.id, s.code, s.name, s.contactNo, s.contactEmail, s.contactName, s.addr1, s.addr2, s.addr3, s.type, t.TruckLanceCode, t.LoadingSequence, t.districtReference,t.Store_id, t.remark | |||
| SELECT s.id, s.code, s.name, s.contactNo, s.contactEmail, s.contactName, s.addr1, s.addr2, s.addr3, s.type, t.TruckLanceCode, t.DepartureTime as departureTime, t.LoadingSequence, t.districtReference, t.Store_id as Store_id, t.remark | |||
| FROM shop s LEFT JOIN truck t ON s.id = t.shopId | |||
| WHERE s.type = 'shop' | |||
| AND s.deleted = false; | |||
| @@ -18,6 +18,7 @@ interface ProdScheduleInfo { | |||
| // Detailed Production Schedule With Line | |||
| interface DetailedProdScheduleWithLineWithJsonString { | |||
| val id: Long? | |||
| val produceAt: LocalDateTime? | |||
| val scheduleAt: LocalDateTime? | |||
| val totalEstProdCount: BigDecimal? | |||
| val totalFGType: Long? | |||
| @@ -27,6 +28,7 @@ interface DetailedProdScheduleWithLineWithJsonString { | |||
| data class DetailedProdScheduleWithLine ( | |||
| val id: Long?, | |||
| val produceAt: LocalDateTime?, | |||
| val scheduleAt: LocalDateTime?, | |||
| val totalEstProdCount: BigDecimal?, | |||
| val totalFGType: Long?, | |||
| @@ -19,4 +19,5 @@ interface ShopAndTruck { | |||
| val districtReference: Long? | |||
| val Store_id: String? | |||
| val remark: String? | |||
| val truckId: Long? | |||
| } | |||
| @@ -7,6 +7,9 @@ import com.ffii.fpsms.modules.master.entity.EquipmentDetailRepository | |||
| import org.springframework.stereotype.Service | |||
| import org.springframework.transaction.annotation.Transactional | |||
| import com.ffii.fpsms.modules.master.web.models.NewEquipmentDetailRequest | |||
| import com.ffii.fpsms.modules.master.web.models.UpdateMaintenanceRequest | |||
| import java.time.LocalDateTime | |||
| @Service | |||
| open class EquipmentDetailService( | |||
| private val jdbcDao: JdbcDao, | |||
| @@ -18,21 +21,54 @@ open class EquipmentDetailService( | |||
| } | |||
| open fun getEquipmentDetailsByPage(args: Map<String, Any>): List<Map<String, Any>> { | |||
| println("Search args: $args") | |||
| val sql = StringBuilder( | |||
| "SELECT e.id, e.code, e.name, e.description FROM equipment_detail e WHERE e.deleted = FALSE" | |||
| """ | |||
| SELECT | |||
| e.id AS id, | |||
| e.code AS code, | |||
| e.name AS name, | |||
| e.description AS description, | |||
| e.equipmentCode AS equipmentCode, | |||
| e.repairAndMaintenanceStatus AS repairAndMaintenanceStatus, | |||
| e.latestRepairAndMaintenanceDate AS latestRepairAndMaintenanceDate, | |||
| e.lastRepairAndMaintenanceDate AS lastRepairAndMaintenanceDate, | |||
| e.repairAndMaintenanceRemarks AS repairAndMaintenanceRemarks | |||
| FROM equipment_detail e | |||
| WHERE e.deleted = FALSE | |||
| """ | |||
| ) | |||
| if (args.containsKey("code")) { | |||
| sql.append(" AND e.code like :code ") | |||
| // Handle combined search for code and equipmentCode (OR logic) | |||
| val searchTerm = args["equipmentCode"] as? String | |||
| val codeTerm = args["code"] as? String | |||
| if (searchTerm != null && codeTerm != null && searchTerm == codeTerm) { | |||
| // When both are provided with the same value, search using OR | |||
| sql.append(" AND (LOWER(e.code) LIKE LOWER(:equipmentCode) OR LOWER(e.equipmentCode) LIKE LOWER(:equipmentCode)) ") | |||
| } else { | |||
| // Otherwise, use individual conditions | |||
| if (codeTerm != null && (searchTerm == null || searchTerm != codeTerm)) { | |||
| sql.append(" AND LOWER(e.code) LIKE LOWER(:code) ") | |||
| } | |||
| if (searchTerm != null && (codeTerm == null || searchTerm != codeTerm)) { | |||
| sql.append(" AND LOWER(e.equipmentCode) LIKE LOWER(:equipmentCode) ") | |||
| } | |||
| } | |||
| if (args.containsKey("id")) { | |||
| sql.append(" AND e.id like :id ") | |||
| } | |||
| if (args.containsKey("name")) { | |||
| sql.append(" AND e.name like :name ") | |||
| sql.append(" AND LOWER(e.name) LIKE LOWER(:name) ") | |||
| } | |||
| if (args.containsKey("description")) { | |||
| sql.append(" AND e.description like :description ") | |||
| } | |||
| if (args.containsKey("repairAndMaintenanceStatus")) { | |||
| sql.append(" AND e.repairAndMaintenanceStatus = :repairAndMaintenanceStatus ") | |||
| } | |||
| return jdbcDao.queryForList(sql.toString(), args) | |||
| } | |||
| @@ -51,6 +87,42 @@ open class EquipmentDetailService( | |||
| open fun findByDescription(description: String): EquipmentDetail? { | |||
| return equipmentDetailRepository.findByDescriptionAndDeletedIsFalse(description) | |||
| } | |||
| @Transactional | |||
| open fun updateMaintenanceAndRepair( | |||
| id: Long, | |||
| request: UpdateMaintenanceRequest | |||
| ): EquipmentDetail { | |||
| val equipmentDetail = findById(id) | |||
| ?: throw IllegalArgumentException("Equipment detail not found with id: $id") | |||
| // Store the previous status and latest date before updating | |||
| val previousStatus = equipmentDetail.repairAndMaintenanceStatus | |||
| val previousLatestDate = equipmentDetail.latestRepairAndMaintenanceDate | |||
| // Update status and remarks | |||
| equipmentDetail.repairAndMaintenanceStatus = request.repairAndMaintenanceStatus | |||
| equipmentDetail.repairAndMaintenanceRemarks = request.repairAndMaintenanceRemarks | |||
| // Handle date updates based on status change | |||
| when { | |||
| // Changing from "是" (true) to "否" (false) | |||
| previousStatus == true && request.repairAndMaintenanceStatus == false -> { | |||
| // Save current latestRepairAndMaintenanceDate to lastRepairAndMaintenanceDate | |||
| equipmentDetail.lastRepairAndMaintenanceDate = previousLatestDate | |||
| // Update latestRepairAndMaintenanceDate to current time | |||
| equipmentDetail.latestRepairAndMaintenanceDate = LocalDateTime.now() | |||
| } | |||
| // Changing from "否" (false) to "是" (true) | |||
| // Keep dates unchanged - no action needed | |||
| previousStatus == false && request.repairAndMaintenanceStatus == true -> { | |||
| // Dates remain unchanged | |||
| } | |||
| // Other cases (null to true/false, or same status) - no date changes | |||
| } | |||
| return equipmentDetailRepository.saveAndFlush(equipmentDetail) | |||
| } | |||
| /* | |||
| @Transactional | |||
| open fun saveEquipmentDetail(request: NewEquipmentDetailRequest): EquipmentDetail { | |||
| @@ -18,26 +18,40 @@ open class EquipmentService( | |||
| } | |||
| open fun getEquipmentsByPage(args: Map<String, Any>): List<Map<String, Any>> { | |||
| val sql = StringBuilder( | |||
| "SELECT e.id, e.code, e.name, e.description, e.equipmentTypeId FROM equipment e WHERE e.deleted = FALSE" | |||
| ) | |||
| if (args.containsKey("code")) { | |||
| sql.append(" AND e.code like :code ") | |||
| } | |||
| if (args.containsKey("id")) { | |||
| sql.append(" AND e.id like :id ") | |||
| } | |||
| if (args.containsKey("name")) { | |||
| sql.append(" AND e.name like :name ") | |||
| } | |||
| if (args.containsKey("description")) { | |||
| sql.append(" AND e.description like :description ") | |||
| } | |||
| if (args.containsKey("equipmentTypeId")) { | |||
| sql.append(" AND e.equipmentTypeId like :equipmentTypeId ") | |||
| } | |||
| return jdbcDao.queryForList(sql.toString(), args) | |||
| val sql = StringBuilder( | |||
| """ | |||
| SELECT | |||
| e.id AS id, | |||
| e.code AS code, | |||
| e.name AS name, | |||
| e.description AS description, | |||
| e.equipmentTypeId AS equipmentTypeId, | |||
| ed.repairAndMaintenanceStatus AS repairAndMaintenanceStatus, | |||
| ed.latestRepairAndMaintenanceDate AS latestRepairAndMaintenanceDate, | |||
| ed.lastRepairAndMaintenanceDate AS lastRepairAndMaintenanceDate, | |||
| ed.repairAndMaintenanceRemarks AS repairAndMaintenanceRemarks | |||
| FROM equipment e | |||
| LEFT JOIN equipment_detail ed ON e.code = ed.equipmentCode | |||
| WHERE e.deleted = FALSE | |||
| """ | |||
| ) | |||
| if (args.containsKey("code")) { | |||
| sql.append(" AND e.code like :code ") | |||
| } | |||
| if (args.containsKey("id")) { | |||
| sql.append(" AND e.id like :id ") | |||
| } | |||
| if (args.containsKey("name")) { | |||
| sql.append(" AND e.name like :name ") | |||
| } | |||
| if (args.containsKey("description")) { | |||
| sql.append(" AND e.description like :description ") | |||
| } | |||
| if (args.containsKey("equipmentTypeId")) { | |||
| sql.append(" AND e.equipmentTypeId like :equipmentTypeId ") | |||
| } | |||
| return jdbcDao.queryForList(sql.toString(), args) | |||
| } | |||
| open fun findById(id: Long): Equipment? { | |||
| return equipmentRepository.findByIdAndDeletedFalse(id) | |||
| @@ -392,7 +392,10 @@ open class ItemsService( | |||
| "i.id, " + | |||
| "i.code, " + | |||
| "i.name, " + | |||
| "i.description " + | |||
| "i.description, " + | |||
| "i.type, " + | |||
| "i.`LocationCode` as LocationCode, " + | |||
| "i.`qcCategoryId` as qcCategoryId " + | |||
| "FROM items i " + | |||
| "WHERE i.deleted = FALSE" | |||
| ); | |||
| @@ -512,6 +515,14 @@ open class ItemsService( | |||
| this.qcCategory = qcCategory | |||
| m18Id = request.m18Id ?: this.m18Id | |||
| m18LastModifyDate = request.m18LastModifyDate ?: this.m18LastModifyDate | |||
| store_id = request.store_id | |||
| warehouse = request.warehouse | |||
| area = request.area | |||
| slot = request.slot | |||
| LocationCode = request.LocationCode | |||
| isEgg = request.isEgg ?: false | |||
| isFee = request.isFee ?: false | |||
| isBag = request.isBag ?: false | |||
| } | |||
| logger.info("saving item: $item") | |||
| val savedItem = itemsRepository.saveAndFlush(item) | |||
| @@ -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 | |||
| ) | |||
| @@ -329,6 +341,7 @@ open class ProductionScheduleService( | |||
| return DetailedProdScheduleWithLine( | |||
| id = sqlResult.id, | |||
| produceAt = sqlResult.produceAt, | |||
| scheduleAt = sqlResult.scheduleAt, | |||
| totalEstProdCount = sqlResult.totalEstProdCount, | |||
| totalFGType = sqlResult.totalFGType, | |||
| @@ -397,57 +410,63 @@ 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) | |||
| logger.info("jobOrderDetailByItemId ok itemId:$itemId") | |||
| } catch (e: NoSuchElementException) { | |||
| //only do with no JO is working | |||
| logger.info("NoSuchElementException itemId:$itemId") | |||
| 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 | |||
| prodScheduleLine.apply { | |||
| // Use bom.outputQty, ensuring it's treated as Double for prodQty | |||
| prodQty = bom.outputQty?.toDouble() | |||
| ?: throw IllegalStateException("BOM output quantity is null for Item ID $itemId.") | |||
| approverId = approver?.id | |||
| } | |||
| productionScheduleLineRepository.save(prodScheduleLine) | |||
| // 5. Logging (optional but kept) | |||
| logger.info("prodScheduleLine.prodQty: ${prodScheduleLine.prodQty}") | |||
| logger.info("bom?.outputQty: ${bom.outputQty} ${bom.outputQtyUom}") | |||
| logger.info("[releaseProdSchedule] prodScheduleLine.needNoOfJobOrder:" + prodScheduleLine.needNoOfJobOrder) | |||
| //repeat(prodScheduleLine.needNoOfJobOrder) { | |||
| // 6. Create Job Order | |||
| val joRequest = CreateJobOrderRequest( | |||
| bomId = bom.id, // bom is guaranteed non-null here | |||
| reqQty = bom.outputQty?.multiply(BigDecimal.valueOf(prodScheduleLine.batchNeed.toLong())), | |||
| approverId = approver?.id, | |||
| // CRUCIAL FIX: Use the line ID, not the parent schedule ID | |||
| prodScheduleLineId = prodScheduleLine.id!! | |||
| ) | |||
| // Assuming createJobOrder returns the created Job Order (jo) | |||
| val jo = jobOrderService.createJobOrder(joRequest) | |||
| // 4. Update Prod Schedule Line fields | |||
| prodScheduleLine.apply { | |||
| // Use bom.outputQty, ensuring it's treated as Double for prodQty | |||
| prodQty = bom.outputQty?.toDouble() | |||
| ?: throw IllegalStateException("BOM output quantity is null for Item ID $itemId.") | |||
| approverId = approver?.id | |||
| } | |||
| val createdJobOrderId = jo.id | |||
| ?: throw IllegalStateException("Job Order creation failed: returned object ID is null.") | |||
| // 7. Create related job order data | |||
| jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(createdJobOrderId) | |||
| jobOrderProcessService.createJobOrderProcessesByJoId(createdJobOrderId) | |||
| productProcessService.createProductProcessByJobOrderId(createdJobOrderId, prodScheduleLine.itemPriority.toInt()) | |||
| //} | |||
| productionScheduleLineRepository.save(prodScheduleLine) | |||
| // 5. Logging (optional but kept) | |||
| logger.info("prodScheduleLine.prodQty: ${prodScheduleLine.prodQty}") | |||
| logger.info("bom?.outputQty: ${bom.outputQty} ${bom.outputQtyUom}") | |||
| logger.info("[releaseProdSchedule] prodScheduleLine.needNoOfJobOrder:" + prodScheduleLine.needNoOfJobOrder) | |||
| //repeat(prodScheduleLine.needNoOfJobOrder) { | |||
| // 6. Create Job Order | |||
| val joRequest = CreateJobOrderRequest( | |||
| bomId = bom.id, // bom is guaranteed non-null here | |||
| reqQty = bom.outputQty?.multiply(BigDecimal.valueOf(prodScheduleLine.batchNeed.toLong())), | |||
| approverId = approver?.id, | |||
| // CRUCIAL FIX: Use the line ID, not the parent schedule ID | |||
| prodScheduleLineId = prodScheduleLine.id!! | |||
| ) | |||
| // Assuming createJobOrder returns the created Job Order (jo) | |||
| val jo = jobOrderService.createJobOrder(joRequest) | |||
| val createdJobOrderId = jo.id | |||
| ?: throw IllegalStateException("Job Order creation failed: returned object ID is null.") | |||
| // 7. Create related job order data | |||
| jobOrderBomMaterialService.createJobOrderBomMaterialsByJoId(createdJobOrderId) | |||
| jobOrderProcessService.createJobOrderProcessesByJoId(createdJobOrderId) | |||
| productProcessService.createProductProcessByJobOrderId(createdJobOrderId, prodScheduleLine.itemPriority.toInt()) | |||
| //} | |||
| } | |||
| } | |||
| @@ -719,7 +738,11 @@ open class ProductionScheduleService( | |||
| var machineCap = 10000.0 | |||
| var needQtyList = getNeedQty() | |||
| //remove the production schedule >= today | |||
| clearTodayAndFutureProdSchedule() | |||
| println("needQtyList - " + needQtyList); | |||
| //##### The 22000, 10000 machine cap just means the max warehouse storage qty, not production qty cap | |||
| //##### The total production qty of the date is 10000 due to machine cap | |||
| //##### search all items with bom to consider need or no need production | |||
| @@ -1338,60 +1361,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 +1468,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() | |||
| } | |||
| @@ -1514,5 +1619,30 @@ open class ProductionScheduleService( | |||
| return jdbcDao.queryForList(sql, args); | |||
| } | |||
| @Transactional | |||
| open fun clearTodayAndFutureProdSchedule() { | |||
| val deleteLinesSql = """ | |||
| DELETE FROM production_schedule_line | |||
| WHERE prodScheduleId IN ( | |||
| SELECT id FROM production_schedule | |||
| WHERE DATE(produceAt) >= DATE(NOW()) | |||
| ) | |||
| """.trimIndent() | |||
| val deleteSchedulesSql = """ | |||
| DELETE FROM production_schedule | |||
| WHERE DATE(produceAt) >= DATE(NOW()) | |||
| """.trimIndent() | |||
| // Execute child delete first | |||
| jdbcDao.executeUpdate(deleteLinesSql) | |||
| // Then delete parent schedules | |||
| jdbcDao.executeUpdate(deleteSchedulesSql) | |||
| // Optional: log the action (if you have logging setup) | |||
| // logger.info("Cleared all production schedules with produceAt >= today") | |||
| } | |||
| } | |||
| @@ -10,6 +10,8 @@ import jakarta.validation.Valid | |||
| import org.springframework.web.bind.annotation.* | |||
| import java.util.Collections.emptyList | |||
| import com.ffii.fpsms.modules.master.web.models.NewEquipmentDetailRequest | |||
| import com.ffii.fpsms.modules.master.web.models.UpdateMaintenanceRequest | |||
| @RestController | |||
| @RequestMapping("/EquipmentDetail") | |||
| class EquipmentDetailController( | |||
| @@ -43,31 +45,38 @@ fun getAllEquipmentDetailByPage( | |||
| .addStringLike("code") | |||
| .addStringLike("description") | |||
| .addStringLike("id") | |||
| .addStringLike("equipmentCode") | |||
| .addBoolean("repairAndMaintenanceStatus") | |||
| .build() | |||
| val pageSize = request.getParameter("pageSize")?.toIntOrNull() ?: 10 | |||
| val pageNum = request.getParameter("pageNum")?.toIntOrNull() ?: 1 | |||
| // 方法名和变量名都要和 Service 保持一致 | |||
| val fullList = equipmentDetailService.getEquipmentDetailsByPage(criteriaArgs) ?: emptyList() | |||
| val paginatedList = PagingUtils.getPaginatedList(fullList, pageSize, pageNum) | |||
| return RecordsRes(paginatedList as List<Map<String, Any>>, fullList.size) | |||
| } | |||
| // 详情 | |||
| @GetMapping("/details/{id}") | |||
| fun getEquipmentDetail(@PathVariable id: Long): EquipmentDetail? { | |||
| return equipmentDetailService.findById(id) | |||
| } | |||
| @PutMapping("/update/{id}") | |||
| fun updateMaintenanceAndRepair( | |||
| @PathVariable id: Long, | |||
| @RequestBody request: UpdateMaintenanceRequest | |||
| ): EquipmentDetail { | |||
| return equipmentDetailService.updateMaintenanceAndRepair(id, request) | |||
| } | |||
| /* | |||
| // 新增/编辑 | |||
| @PostMapping("/save") | |||
| fun saveEquipmentDetail(@Valid @RequestBody equipmentDetail: NewEquipmentDetailRequest): EquipmentDetail { | |||
| return equipmentDetailService.saveEquipmentDetail(equipmentDetail) | |||
| } | |||
| */ | |||
| // 逻辑删除 | |||
| @DeleteMapping("/delete/{id}") | |||
| fun deleteEquipmentDetail(@PathVariable id: Long) { | |||
| equipmentDetailService.deleteEquipmentDetail(id) | |||
| @@ -0,0 +1,6 @@ | |||
| package com.ffii.fpsms.modules.master.web.models | |||
| data class UpdateMaintenanceRequest( | |||
| val repairAndMaintenanceStatus: Boolean?, | |||
| val repairAndMaintenanceRemarks: String? | |||
| ) | |||
| @@ -45,6 +45,14 @@ data class NewItemRequest( | |||
| val m18Id: Long?, | |||
| val m18LastModifyDate: LocalDateTime?, | |||
| val qcCategoryId: Long?, | |||
| val store_id: String?, | |||
| val warehouse: String?, | |||
| val area: String?, | |||
| val slot: String?, | |||
| val LocationCode: String?, | |||
| val isEgg: Boolean?, | |||
| val isFee: Boolean?, | |||
| val isBag: Boolean?, | |||
| // val type: List<NewTypeRequest>?, | |||
| // val uom: List<NewUomRequest>?, | |||
| // val weightUnit: List<NewWeightUnitRequest>?, | |||
| @@ -51,4 +51,40 @@ fun findByShopIdAndStoreIdAndDayOfWeek( | |||
| @Param("storeId") storeId: String, | |||
| @Param("dayOfWeekAbbr") dayOfWeekAbbr: String | |||
| ): List<Truck> | |||
| @Query( | |||
| nativeQuery = true, | |||
| value = """ | |||
| SELECT t.* | |||
| FROM truck t | |||
| INNER JOIN ( | |||
| SELECT TruckLanceCode, remark, MIN(id) as min_id | |||
| FROM truck | |||
| WHERE deleted = false | |||
| AND TruckLanceCode IS NOT NULL | |||
| GROUP BY TruckLanceCode, remark | |||
| ) AS unique_combos | |||
| ON t.id = unique_combos.min_id | |||
| WHERE t.deleted = false | |||
| ORDER BY t.TruckLanceCode, t.remark | |||
| """ | |||
| ) | |||
| fun findAllUniqueTruckLanceCodeAndRemarkCombinations(): List<Truck> | |||
| @Query( | |||
| nativeQuery = true, | |||
| value = """ | |||
| SELECT s.id as id, t.ShopCode as code, s.name as name, s.contactNo as contactNo, | |||
| s.contactEmail as contactEmail, s.contactName as contactName, | |||
| s.addr1 as addr1, s.addr2 as addr2, s.addr3 as addr3, s.type as type, | |||
| t.TruckLanceCode as truckLanceCode, t.DepartureTime as departureTime, | |||
| t.LoadingSequence as LoadingSequence, t.districtReference as districtReference, | |||
| t.Store_id as Store_id, t.remark as remark, t.id as truckId | |||
| FROM shop s INNER JOIN truck t ON s.id = t.shopId | |||
| WHERE t.TruckLanceCode = :truckLanceCode | |||
| AND t.deleted = false | |||
| AND s.deleted = false; | |||
| """ | |||
| ) | |||
| fun findAllFromShopAndTruckByTruckLanceCodeAndDeletedFalse(@Param("truckLanceCode") truckLanceCode: String): List<ShopAndTruck> | |||
| } | |||
| @@ -12,7 +12,9 @@ import com.ffii.fpsms.modules.pickOrder.web.models.SaveTruckRequest | |||
| import java.time.LocalTime | |||
| import java.time.format.DateTimeFormatter | |||
| import com.ffii.fpsms.modules.master.entity.ShopRepository | |||
| import com.ffii.fpsms.modules.master.entity.projections.ShopAndTruck | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.SaveTruckLane | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.UpdateLoadingSequenceRequest | |||
| import jakarta.transaction.Transactional | |||
| @@ -187,7 +189,7 @@ open class TruckService( | |||
| } | |||
| open fun findAllByShopId(shopId: Long): List<Truck> { | |||
| return truckRepository.findAllByShopId(shopId) | |||
| return truckRepository.findByShopIdAndDeletedFalse(shopId) | |||
| } | |||
| @Transactional | |||
| @@ -212,7 +214,10 @@ open class TruckService( | |||
| @Transactional | |||
| open fun deleteById(id: Long): String { | |||
| truckRepository.deleteById(id) | |||
| val deleteTruck = truckRepository.findById(id).orElseThrow().apply { | |||
| deleted = true | |||
| } | |||
| truckRepository.save(deleteTruck) | |||
| return "Truck deleted successfully with id: $id" | |||
| } | |||
| @@ -241,4 +246,22 @@ open class TruckService( | |||
| return truckRepository.save(truck) | |||
| } | |||
| open fun findAllUniqueTruckLanceCodeAndRemarkCombinations(): List<Truck> { | |||
| return truckRepository.findAllUniqueTruckLanceCodeAndRemarkCombinations() | |||
| } | |||
| open fun findAllFromShopAndTruckByTruckLanceCodeAndDeletedFalse(truckLanceCode: String): List<ShopAndTruck> { | |||
| return truckRepository.findAllFromShopAndTruckByTruckLanceCodeAndDeletedFalse(truckLanceCode) | |||
| } | |||
| @Transactional | |||
| open fun updateLoadingSequence(request: UpdateLoadingSequenceRequest): Truck { | |||
| val truck = truckRepository.findById(request.id).orElseThrow { | |||
| IllegalArgumentException("Truck not found with id: ${request.id}") | |||
| } | |||
| truck.loadingSequence = request.loadingSequence | |||
| return truckRepository.save(truck) | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ffii.fpsms.modules.pickOrder.web | |||
| import com.ffii.fpsms.modules.master.entity.projections.ShopAndTruck | |||
| import com.ffii.fpsms.modules.master.web.models.MessageResponse | |||
| import com.ffii.fpsms.modules.pickOrder.entity.Truck | |||
| import org.springframework.web.bind.ServletRequestBindingException | |||
| @@ -15,6 +16,7 @@ import com.ffii.fpsms.modules.pickOrder.service.TruckService | |||
| import com.ffii.fpsms.modules.pickOrder.entity.TruckRepository | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.SaveTruckLane | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.deleteTruckLane | |||
| import com.ffii.fpsms.modules.pickOrder.web.models.UpdateLoadingSequenceRequest | |||
| import jakarta.validation.Valid | |||
| @RestController | |||
| @@ -173,4 +175,42 @@ class TruckController( | |||
| ) | |||
| } | |||
| } | |||
| @GetMapping("/findAllUniqueTruckLanceCodeAndRemarkCombinations") | |||
| fun findAllUniqueTruckLanceCodeAndRemarkCombinations(): List<Truck> { | |||
| return truckService.findAllUniqueTruckLanceCodeAndRemarkCombinations() | |||
| } | |||
| @GetMapping("/findAllFromShopAndTruckByTruckLanceCodeAndDeletedFalse") | |||
| fun findAllFromShopAndTruckByTruckLanceCodeAndDeletedFalse(@RequestParam truckLanceCode: String): List<ShopAndTruck> { | |||
| return truckService.findAllFromShopAndTruckByTruckLanceCodeAndDeletedFalse(truckLanceCode) | |||
| } | |||
| @PostMapping("/updateLoadingSequence") | |||
| fun updateLoadingSequence(@Valid @RequestBody request: UpdateLoadingSequenceRequest): MessageResponse { | |||
| try { | |||
| val truck = truckService.updateLoadingSequence(request) | |||
| return MessageResponse( | |||
| id = truck.id, | |||
| name = truck.shopName, | |||
| code = truck.truckLanceCode, | |||
| type = "truck", | |||
| message = "Loading sequence updated successfully", | |||
| errorPosition = null, | |||
| entity = truck | |||
| ) | |||
| } catch (e: Exception) { | |||
| return MessageResponse( | |||
| id = null, | |||
| name = null, | |||
| code = null, | |||
| type = "truck", | |||
| message = "Error: ${e.message}", | |||
| errorPosition = null, | |||
| entity = null | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| @@ -24,3 +24,7 @@ data class SaveTruckLane( | |||
| data class deleteTruckLane( | |||
| val id: Long | |||
| ) | |||
| data class UpdateLoadingSequenceRequest( | |||
| val id: Long, | |||
| val loadingSequence: Int | |||
| ) | |||