# Conflicts: # src/main/java/com/ffii/fpsms/modules/user/web/UserController.javamaster
| @@ -9,4 +9,6 @@ interface MailTemplateRepository : AbstractRepository<MailTemplate, Long> { | |||
| fun findAllByDeletedIsFalse(): List<MailTemplate> | |||
| fun findByIdAndDeletedIsFalse(id: Serializable): MailTemplate? | |||
| fun findByCodeAndDeletedIsFalse(code: String): MailTemplate? | |||
| } | |||
| @@ -2,13 +2,29 @@ package com.ffii.fpsms.modules.common.mail.service | |||
| import com.ffii.fpsms.modules.common.mail.entity.MailTemplate | |||
| import com.ffii.fpsms.modules.common.mail.entity.MailTemplateRepository | |||
| import com.ffii.fpsms.modules.common.mail.web.models.DownloadMailTemplateResponse | |||
| import com.ffii.fpsms.modules.common.mail.web.models.MailTemplateRequest | |||
| import com.ffii.fpsms.modules.qc.service.QcResultService | |||
| import com.ffii.fpsms.modules.stock.entity.StockInLineRepository | |||
| import com.ffii.fpsms.modules.stock.service.InventoryLotService | |||
| import com.itextpdf.html2pdf.ConverterProperties | |||
| import com.itextpdf.html2pdf.HtmlConverter | |||
| import com.itextpdf.layout.font.FontProvider | |||
| import org.jsoup.Jsoup | |||
| import org.springframework.stereotype.Service | |||
| import java.io.ByteArrayOutputStream | |||
| import java.math.BigDecimal | |||
| import java.time.format.DateTimeFormatter | |||
| import kotlin.jvm.optionals.getOrNull | |||
| @Service | |||
| open class MailTemplateService( | |||
| private val mailTemplateRepository: MailTemplateRepository, | |||
| private val stockInLineRepository: StockInLineRepository, | |||
| private val inventoryLotService: InventoryLotService, | |||
| private val qcResultService: QcResultService | |||
| ) { | |||
| val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") | |||
| fun allMailTemplates(): List<MailTemplate> { | |||
| return mailTemplateRepository.findAllByDeletedIsFalse(); | |||
| } | |||
| @@ -17,6 +33,10 @@ open class MailTemplateService( | |||
| return mailTemplateRepository.findByIdAndDeletedIsFalse(id); | |||
| } | |||
| fun findByCode(code: String): MailTemplate? { | |||
| return mailTemplateRepository.findByCodeAndDeletedIsFalse(code); | |||
| } | |||
| fun saveMailTemplate(request: MailTemplateRequest): MailTemplate { | |||
| val mailTemplate = request.id?.let { findById(it) } ?: MailTemplate() | |||
| @@ -59,4 +79,102 @@ open class MailTemplateService( | |||
| mailTemplateRepository.saveAll(mailTemplates) | |||
| } | |||
| fun getMailTemplateForStockInLine(stockInLineId: Long): DownloadMailTemplateResponse { | |||
| val emailTemplate = findByCode("RM-01") ?: throw NoSuchElementException("No RP-01 Email Template") | |||
| val stockInLine = stockInLineRepository.findById(stockInLineId).getOrNull() ?: throw NoSuchElementException("Cant find this stock in line.") | |||
| // Value | |||
| val zero = BigDecimal.ZERO | |||
| val po = stockInLine.purchaseOrder | |||
| val pol = stockInLine.purchaseOrderLine | |||
| val item = stockInLine.item | |||
| val supplierName = po?.supplier?.name ?: "N/A" | |||
| val supplierEmail = if((po?.supplier?.contactEmail).isNullOrEmpty()) "N/A" else po?.supplier?.contactEmail | |||
| val dnDate = formatter.format(stockInLine.dnDate) ?: "N/A" | |||
| val poNo = po?.code ?: "N/A" | |||
| val supplierId = po?.supplier?.code ?: "N/A" // Id? | |||
| val itemNo = item?.code ?: "N/A" | |||
| val itemQty = (pol?.qty ?: zero).toString() | |||
| val uom = pol?.uom?.udfudesc ?: "N/A" | |||
| val planDnDate = po?.estimatedArrivalDate?.let { formatter.format(it) } ?: "N/A" | |||
| val unitPrice = (pol?.up ?: zero).toString() | |||
| val receivedCompany = po?.shop?.name ?: "N/A" | |||
| val lotNo = stockInLine.productLotNo ?: "N/A" | |||
| var qcDate = "N/A" | |||
| val qcResult = qcResultService.getAllQcResultInfoByStockInLineId(stockInLineId).let { qcResults -> | |||
| val filteredResult = qcResults | |||
| .groupBy { Pair(it.stockInLineId, it.qcItemId) } | |||
| .mapValues { (_, group) -> | |||
| group.maxByOrNull { it.recordDate } | |||
| } | |||
| .values | |||
| .filterNotNull() | |||
| .filter { !it.qcPassed } | |||
| if (filteredResult.isNotEmpty()) { | |||
| qcDate = formatter.format(filteredResult.maxOf { it.recordDate }) | |||
| val tempDoc = Jsoup.parse("") | |||
| val element = tempDoc.appendElement("ul") | |||
| for (result in filteredResult) { | |||
| element.appendElement("li") | |||
| .text("${result.name} - ${result.description}") | |||
| } | |||
| tempDoc.outerHtml() | |||
| } else { | |||
| "N/A" | |||
| } | |||
| } ?: "N/A" | |||
| val acceptedQty = (stockInLine.acceptedQty ?: zero).minus(stockInLine.demandQty ?: zero).toString() | |||
| val rejectedQty = (stockInLine.demandQty ?: zero).toString() // reject? | |||
| val nonDelieveredQty = (zero).toString() | |||
| // HTML | |||
| val to = supplierEmail | |||
| val toHtmlStr = Jsoup.parse("").appendElement("p").text("To: $to").outerHtml() | |||
| val subject = (emailTemplate.subjectCht ?: "N/A") | |||
| // .replace("{supplierName}", supplierName) | |||
| .replace("{poNo}", poNo) | |||
| .replace("{itemNo}", itemNo) | |||
| val subjectHtmlStr = Jsoup.parse("").appendElement("p").text("Subject: $subject").outerHtml() | |||
| val content = (emailTemplate.contentCht ?: "N/A") | |||
| .replace("{supplierName}", supplierName) | |||
| .replace("{dnDate}", dnDate) | |||
| .replace("{poNo}", poNo) | |||
| .replace("{supplierId}", supplierId) | |||
| .replace("{itemNo}", itemNo) | |||
| .replace("{itemQty}", itemQty) | |||
| .replace("{uom}", uom) | |||
| .replace("{planDnDate}", planDnDate) | |||
| .replace("{unitPrice}", unitPrice) | |||
| .replace("{receivedCompany}", receivedCompany) | |||
| .replace("{lotNo}", lotNo) | |||
| .replace("{qcResult}", qcResult) | |||
| .replace("{acceptedQty}", acceptedQty) | |||
| .replace("{nonDelieveredQty}", nonDelieveredQty) | |||
| .replace("{rejectedQty}", rejectedQty) | |||
| .replace("{qcDate}", qcDate) | |||
| val contentHtmlStr = Jsoup.parse("").appendElement("p").append("Content: $content").outerHtml() | |||
| // Result | |||
| val resultHtmlStr = toHtmlStr + subjectHtmlStr + contentHtmlStr | |||
| // println(resultHtmlStr) | |||
| val resultPdf = ByteArrayOutputStream() | |||
| val fp = FontProvider(); | |||
| fp.addStandardPdfFonts() | |||
| fp.addFont("/fonts/msjh_0.ttf") | |||
| val converterProperties = ConverterProperties() | |||
| converterProperties.apply { | |||
| fontProvider = fp | |||
| } | |||
| HtmlConverter.convertToPdf(resultHtmlStr, resultPdf, converterProperties) | |||
| return DownloadMailTemplateResponse( | |||
| file = resultPdf.toByteArray(), | |||
| fileName = subject ?: "N/A" | |||
| ); | |||
| } | |||
| } | |||
| @@ -3,10 +3,15 @@ package com.ffii.fpsms.modules.common.mail.web | |||
| import com.ffii.fpsms.modules.common.mail.entity.MailTemplate | |||
| import com.ffii.fpsms.modules.common.mail.web.models.MailTemplateRequest | |||
| import com.ffii.fpsms.modules.common.mail.service.MailTemplateService | |||
| import org.springframework.http.HttpHeaders | |||
| import org.springframework.http.MediaType | |||
| import org.springframework.http.ResponseEntity | |||
| import org.springframework.web.bind.annotation.GetMapping | |||
| import org.springframework.web.bind.annotation.PathVariable | |||
| import org.springframework.web.bind.annotation.PostMapping | |||
| import org.springframework.web.bind.annotation.RequestMapping | |||
| import org.springframework.web.bind.annotation.RestController | |||
| import java.net.URLEncoder | |||
| @RequestMapping("/mailTemplates") | |||
| @RestController | |||
| @@ -22,4 +27,18 @@ class MailTemplateController( | |||
| fun saveMailTemplate(request: MailTemplateRequest): MailTemplate { | |||
| return mailTemplateService.saveMailTemplate(request); | |||
| } | |||
| @GetMapping("/getMailTemplateForStockInLine/{stockInLineId}") | |||
| fun getMailTemplateForStockInLine(@PathVariable stockInLineId: Long): ResponseEntity<ByteArray> { | |||
| val response = mailTemplateService.getMailTemplateForStockInLine(stockInLineId) | |||
| val headers = HttpHeaders().apply { | |||
| contentType = MediaType.APPLICATION_PDF | |||
| // add("Content-Disposition", "attachment; filename=${response.fileName}") | |||
| add("filename", "${response.fileName}.pdf") | |||
| } | |||
| return ResponseEntity.ok() | |||
| .headers(headers) | |||
| .body(response.file) | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| package com.ffii.fpsms.modules.common.mail.web.models | |||
| data class DownloadMailTemplateResponse( | |||
| val file: ByteArray, | |||
| val fileName: String, | |||
| ) { | |||
| override fun equals(other: Any?): Boolean { | |||
| if (this === other) return true | |||
| if (javaClass != other?.javaClass) return false | |||
| other as DownloadMailTemplateResponse | |||
| if (!file.contentEquals(other.file)) return false | |||
| if (fileName != other.fileName) return false | |||
| return true | |||
| } | |||
| override fun hashCode(): Int { | |||
| var result = file.contentHashCode() | |||
| result = 31 * result + fileName.hashCode() | |||
| return result | |||
| } | |||
| } | |||
| @@ -37,4 +37,8 @@ open class QcResult: BaseEntity<Long>() { | |||
| @Column(name = "remarks") | |||
| open var remarks: String? = null | |||
| @NotNull | |||
| @Column(name = "qcPassed") | |||
| open var qcPassed: Boolean = true | |||
| } | |||
| @@ -1,18 +1,25 @@ | |||
| package com.ffii.fpsms.modules.qc.entity.projection | |||
| import org.springframework.beans.factory.annotation.Value | |||
| import java.time.LocalDateTime | |||
| interface QcResultInfo { | |||
| val id: Long | |||
| @get:Value("#{target.qcItem.id}") | |||
| val qcItemId: Long | |||
| @get:Value("#{target.qcItem.name}") | |||
| val name: String | |||
| @get:Value("#{target.qcItem.code}") | |||
| val code: String | |||
| @get:Value("#{target.qcItem?.id}") | |||
| val qcItemId: Long? | |||
| @get:Value("#{target.qcItem?.code}") | |||
| val code: String? | |||
| @get:Value("#{target.qcItem?.name}") | |||
| val name: String? | |||
| @get:Value("#{target.qcItem?.description}") | |||
| val description: String? | |||
| val remarks: String? | |||
| @get:Value("#{target.stockInLine?.id}") | |||
| val stockInLineId: Long? | |||
| @get:Value("#{target.stockOutLine?.id}") | |||
| val stockOutLineId: Long? | |||
| val failQty: Double | |||
| val failQty: Double? | |||
| val qcPassed: Boolean | |||
| @get:Value("#{target.modified}") | |||
| val recordDate: LocalDateTime | |||
| } | |||
| @@ -42,6 +42,7 @@ open class QcResultService( | |||
| this.failQty = request.failQty | |||
| this.type = request.type | |||
| this.remarks = request.remarks | |||
| this.qcPassed = request.qcPassed | |||
| } | |||
| val savedQcResult = saveAndFlush(qcResult) | |||
| return MessageResponse( | |||
| @@ -7,6 +7,7 @@ data class SaveQcResultRequest( | |||
| val stockInLineId: Long?, | |||
| val stockOutLineId: Long?, | |||
| val failQty: Double, | |||
| val qcPassed: Boolean, | |||
| val type: String?, | |||
| val remarks: String?, | |||
| ) | |||
| @@ -1,6 +1,8 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.entity.BaseEntity | |||
| import com.ffii.fpsms.modules.stock.enums.EscalationLogStatus | |||
| import com.ffii.fpsms.modules.stock.enums.EscalationLogStatusConverter | |||
| import com.ffii.fpsms.modules.user.entity.User | |||
| import jakarta.persistence.* | |||
| import jakarta.validation.constraints.NotNull | |||
| @@ -8,15 +10,14 @@ import jakarta.validation.constraints.Size | |||
| import java.time.LocalDateTime | |||
| @Entity | |||
| @Table(name = "supervision_approval_log") | |||
| open class SupervisionApprovalLog : BaseEntity<Long>() { | |||
| @Table(name = "escalation_log") | |||
| open class EscalationLog : BaseEntity<Long>() { | |||
| @NotNull | |||
| @ManyToOne | |||
| @JoinColumn(name = "personInCharge", nullable = false, referencedColumnName = "id") | |||
| open var personInCharge: User? = null | |||
| @JoinColumn(name = "handlerId", nullable = false, referencedColumnName = "id") | |||
| open var handler: User? = null | |||
| @Size(max = 100) | |||
| @NotNull | |||
| @Column(name = "type", nullable = false, length = 100) | |||
| open var type: String? = null | |||
| @@ -31,13 +32,19 @@ open class SupervisionApprovalLog : BaseEntity<Long>() { | |||
| @Column(name = "recordDate") | |||
| open var recordDate: LocalDateTime? = null | |||
| @Size(max = 100) | |||
| @NotNull | |||
| @Convert(converter = EscalationLogStatusConverter::class) | |||
| @Column(name = "status", nullable = false, length = 100) | |||
| open var status: String? = null | |||
| open var status: EscalationLogStatus? = null | |||
| @Size(max = 500) | |||
| @NotNull | |||
| @Column(name = "reason", nullable = false, length = 500) | |||
| open var reason: String? = null | |||
| @Column(name = "qcTotalCount") | |||
| open var qcTotalCount: Int? = null | |||
| @Column(name = "qcFailCount") | |||
| open var qcFailCount: Int? = null | |||
| } | |||
| @@ -0,0 +1,71 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import org.springframework.beans.factory.annotation.Value | |||
| import java.math.BigDecimal | |||
| import java.time.LocalDateTime | |||
| interface EscalationLogInfo { | |||
| val id: Long? | |||
| @get:Value("#{target.stockInLine?.stockIn?.purchaseOrder?.id}") | |||
| val poId: Long? | |||
| @get:Value("#{target.stockInLine?.purchaseOrderLine?.id}") | |||
| val polId: Long? | |||
| @get:Value("#{target.stockInLine?.stockIn?.code}") | |||
| val poCode: String? | |||
| @get:Value("#{target.stockInLine?.id}") | |||
| val stockInLineId: Long? | |||
| @get:Value("#{target.stockOutLine?.id}") | |||
| val stockOutLineId: Long? | |||
| @get:Value("#{target.stockInLine?.dnNo}") | |||
| val dnNo: String? | |||
| @get:Value("#{target.stockInLine?.dnDate}") | |||
| val dnDate: LocalDateTime? | |||
| @get:Value("#{target.stockInLine?.item?.code}") | |||
| val itemCode: String? | |||
| @get:Value("#{target.stockInLine?.item?.name}") | |||
| val itemName: String? | |||
| @get:Value("#{target.stockInLine?.demandQty}") | |||
| val demandQty: BigDecimal? | |||
| @get:Value("#{target.stockInLine?.acceptedQty}") | |||
| val acceptedQty: BigDecimal? | |||
| @get:Value("#{target.stockInLine?.item?.itemUoms.^[purchaseUnit == true && deleted == false]?.uom.code}") | |||
| val purchaseUomCode: String? | |||
| @get:Value("#{target.stockInLine?.item?.itemUoms.^[purchaseUnit == true && deleted == false]?.uom.udfudesc}") | |||
| val purchaseUomDesc: String? | |||
| @get:Value("#{target.stockInLine?.item?.itemUoms.^[stockUnit == true && deleted == false]?.uom.code}") | |||
| val stockUomCode: String? | |||
| @get:Value("#{target.stockInLine?.item?.itemUoms.^[stockUnit == true && deleted == false]?.uom.udfudesc}") | |||
| val stockUomDesc: String? | |||
| @get:Value("#{target.handler?.id}") | |||
| val handlerId: Long? | |||
| @get:Value("#{target.handler?.department} - #{target.handler?.title} - #{target.handler?.name}") | |||
| val handler: String? | |||
| @get:Value("#{target.handler?.name}") | |||
| val handlerName: String? | |||
| @get:Value("#{target.handler?.title}") | |||
| val handlerTitle: String? | |||
| @get:Value("#{target.handler?.department}") | |||
| val handlerDepartment: String? | |||
| // @get:Value("#{target.type}") | |||
| // val escalationLevel: String | |||
| val qcTotalCount: Int?; | |||
| val qcFailCount: Int?; | |||
| val reason: String?; | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface EscalationLogRepository: AbstractRepository<EscalationLog, Long> { | |||
| // fun findAllByHandler(handler: Long): List<SupervisionApprovalLog> | |||
| fun findAllInfoByDeletedFalse(): List<EscalationLogInfo> | |||
| fun findAllInfoByDeletedFalseAndStockInLineIdIn(stockInLineIds: List<Serializable>): List<EscalationLogInfo> | |||
| fun findAllInfoByDeletedFalseAndHandlerIdIs(handlerId: Serializable): List<EscalationLogInfo> | |||
| } | |||
| @@ -2,7 +2,9 @@ package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import org.springframework.stereotype.Repository | |||
| import java.io.Serializable | |||
| @Repository | |||
| interface InventoryLotRepository: AbstractRepository<InventoryLot, Long> { | |||
| fun findByStockInLineIdAndDeletedFalse(stockInLineId: Serializable): InventoryLot? | |||
| } | |||
| @@ -1,20 +0,0 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import org.springframework.beans.factory.annotation.Value | |||
| interface SupervisionApprovalLogInfo { | |||
| val id: Long | |||
| @get:Value("#{target.stockInLine?.stockIn.purchaseOrder.id}") | |||
| val poId: Long | |||
| @get:Value("#{target.stockInLine?.purchaseOrderLine.id}") | |||
| val polId: Long | |||
| @get:Value("#{target.stockInLine?.stockIn.code}") | |||
| val poCode: String | |||
| @get:Value("#{target.stockInLine?.id}") | |||
| val stockInLineId: Long | |||
| @get:Value("#{target.stockInLine?.item.name}") | |||
| val itemName: String | |||
| @get:Value("#{target.type}") | |||
| val escalationLevel: String | |||
| val reason: String | |||
| } | |||
| @@ -1,11 +0,0 @@ | |||
| package com.ffii.fpsms.modules.stock.entity | |||
| import com.ffii.core.support.AbstractRepository | |||
| import com.ffii.fpsms.modules.user.entity.User | |||
| import org.springframework.stereotype.Repository | |||
| @Repository | |||
| interface SupervisionApprovalLogRepository: AbstractRepository<SupervisionApprovalLog, Long> { | |||
| // fun findAllByPersonInCharge(personInCharge: Long): List<SupervisionApprovalLog> | |||
| fun findAllInfoByDeletedFalse(): List<SupervisionApprovalLogInfo> | |||
| } | |||
| @@ -14,17 +14,21 @@ interface InventoryInfo{ | |||
| @get:Value("#{target.item.type}") | |||
| val itemType: String? | |||
| // @get:Value("#{target.qty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{target.onHandQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{target.onHandQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{target.onHandQty}") | |||
| val onHandQty: BigDecimal? | |||
| @get:Value("#{target.onHoldQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{target.onHoldQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{target.onHoldQty}") | |||
| val onHoldQty: BigDecimal? | |||
| @get:Value("#{target.unavailableQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{target.unavailableQty / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{target.unavailableQty}") | |||
| val unavailableQty: BigDecimal? | |||
| @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty) / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty) / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty)}") | |||
| val availableQty: BigDecimal? | |||
| @get:Value("#{target.item.itemUoms.^[purchaseUnit == true && deleted == false]?.uom.code}") | |||
| @get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom.code}") | |||
| val uomCode: String? | |||
| @get:Value("#{target.item.itemUoms.^[purchaseUnit == true && deleted == false]?.uom.udfudesc}") | |||
| @get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom.udfudesc}") | |||
| val uomUdfudesc: String? | |||
| // @get:Value("#{target.qty * target.uom.gramPerSmallestUnit}") | |||
| // val germPerSmallestUnit: BigDecimal? | |||
| @@ -26,13 +26,17 @@ interface InventoryLotLineInfo { | |||
| @get:Value("#{target.inventoryLot.item}") | |||
| val item: InventoryLotLineItemInfo? | |||
| val warehouse: InventoryLotLineWarehouseInfo? | |||
| @get:Value("#{((target.inQty ?: 0) - (target.outQty ?: 0) - (target.holdQty ?: 0)) / (target.inventoryLot.item.itemUoms.^[salesUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[salesUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{((target.inQty ?: 0) - (target.outQty ?: 0) - (target.holdQty ?: 0)) / (target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{(target.inQty ?: 0) - (target.outQty ?: 0) - (target.holdQty ?: 0)}") | |||
| var availableQty: BigDecimal? | |||
| @get:Value("#{(target.inQty ?: 0)/ (target.inventoryLot.item.itemUoms.^[salesUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[salesUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{(target.inQty ?: 0)/ (target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{target.inQty ?: 0}") | |||
| var inQty: BigDecimal? | |||
| @get:Value("#{(target.outQty ?: 0)/ (target.inventoryLot.item.itemUoms.^[salesUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[salesUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{(target.outQty ?: 0)/ (target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{(target.outQty ?: 0)}") | |||
| var outQty: BigDecimal? | |||
| @get:Value("#{(target.holdQty ?: 0)/ (target.inventoryLot.item.itemUoms.^[salesUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[salesUnit == true && deleted == false]?.ratioD)}") | |||
| // @get:Value("#{(target.holdQty ?: 0)/ (target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.inventoryLot.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}") | |||
| @get:Value("#{(target.holdQty ?: 0)}") | |||
| var holdQty: BigDecimal? | |||
| @get:Value("#{(target.inQty ?: 0) - (target.outQty ?: 0) - (target.holdQty ?: 0)}") | |||
| val qtyPerSmallestUnit: BigDecimal? | |||
| @@ -0,0 +1,7 @@ | |||
| package com.ffii.fpsms.modules.stock.enums | |||
| enum class EscalationLogStatus(val value: String) { | |||
| PENDING("pending"), | |||
| ACCEPTED("accepted"), | |||
| REJECTED("rejected"), | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| package com.ffii.fpsms.modules.stock.enums | |||
| import jakarta.persistence.AttributeConverter | |||
| import jakarta.persistence.Converter | |||
| // Escalation Log Status | |||
| @Converter(autoApply = true) | |||
| class EscalationLogStatusConverter : AttributeConverter<EscalationLogStatus, String>{ | |||
| override fun convertToDatabaseColumn(status: EscalationLogStatus?): String? { | |||
| return status?.value | |||
| } | |||
| override fun convertToEntityAttribute(value: String?): EscalationLogStatus? { | |||
| return value?.let { v -> | |||
| EscalationLogStatus.entries.find { it.value == v } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,88 @@ | |||
| package com.ffii.fpsms.modules.stock.service | |||
| import com.ffii.core.support.AbstractBaseEntityService | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.common.SecurityUtils | |||
| import com.ffii.fpsms.modules.stock.entity.* | |||
| import com.ffii.fpsms.modules.stock.enums.EscalationLogStatus | |||
| import com.ffii.fpsms.modules.stock.web.model.SaveEscalationLogRequest | |||
| import com.ffii.fpsms.modules.stock.web.model.SaveEscalationLogResponse | |||
| import com.ffii.fpsms.modules.user.service.UserService | |||
| import org.springframework.stereotype.Service | |||
| import kotlin.jvm.optionals.getOrNull | |||
| @Service | |||
| open class EscalationLogService( | |||
| private val jdbcDao: JdbcDao, | |||
| private val escalationLogRepository: EscalationLogRepository, | |||
| private val userService: UserService, | |||
| private val stockInLineService: StockInLineService, | |||
| private val stockOutLineService: StockOutLineService, | |||
| private val stockInLineRepository: StockInLineRepository, | |||
| private val stockOutLIneRepository: StockOutLIneRepository, | |||
| ): AbstractBaseEntityService<EscalationLog, Long, EscalationLogRepository>(jdbcDao, escalationLogRepository) { | |||
| open fun getAllLogs(): List<EscalationLog> { | |||
| return escalationLogRepository.findAll() | |||
| } | |||
| open fun getLogsByStockInLines(stockInLineIds: List<Long>?): List<EscalationLogInfo> { | |||
| val logs = stockInLineIds?.let { escalationLogRepository.findAllInfoByDeletedFalseAndStockInLineIdIn(it) } ?: listOf() | |||
| return logs | |||
| } | |||
| open fun getLogsByStockInLine(stockInLineId: Long?): List<EscalationLogInfo> { | |||
| val logs = stockInLineId?.let { getLogsByStockInLines(listOf(it)) } ?: listOf() | |||
| return logs | |||
| } | |||
| open fun getLogsByUser(): List<EscalationLogInfo> { | |||
| val user = SecurityUtils.getUser().orElseThrow() | |||
| val logs = user.id?.let { escalationLogRepository.findAllInfoByDeletedFalseAndHandlerIdIs(it) } ?: listOf() | |||
| return logs | |||
| } | |||
| open fun saveEscalationLog(request: SaveEscalationLogRequest): SaveEscalationLogResponse{ | |||
| val escalationLog = request.id?.let { escalationLogRepository.findById(it).getOrNull() } ?: EscalationLog() | |||
| val handler = request.handlerId.let { userService.getUserById(it) } | |||
| if (handler == null && escalationLog.handler == null) { | |||
| throw NoSuchElementException("Person In Charge is null."); | |||
| } | |||
| val stockInLine = request.stockInLineId?.let { stockInLineRepository.findById(it).getOrNull() } | |||
| val stockOutLine = request.stockOutLineId?.let { stockOutLIneRepository.findById(it).getOrNull() } | |||
| if (stockInLine == null && stockOutLine == null) { | |||
| throw NoSuchElementException("Both stock in line and stock out line are null."); | |||
| } | |||
| val status = request.status.let { status -> EscalationLogStatus.entries.find{ it.value == status} } | |||
| escalationLog.apply { | |||
| this.handler = handler | |||
| type = request.type | |||
| this.stockInLine = stockInLine | |||
| this.stockOutLine = stockOutLine | |||
| recordDate = request.recordDate | |||
| this.status = status | |||
| reason = request.reason | |||
| qcTotalCount = request.qcTotalCount | |||
| qcFailCount = request.qcFailCount | |||
| } | |||
| val response = escalationLogRepository.save(escalationLog).let { | |||
| SaveEscalationLogResponse( | |||
| id = it.id, | |||
| stockInLineId = it.stockInLine?.id, | |||
| stockOutLineId = it.stockOutLine?.id, | |||
| handlerId = it.handler?.id, | |||
| handlerName = it.handler?.name, | |||
| handlerTitle = it.handler?.title, | |||
| handlerDepartment = it.handler?.department, | |||
| recordDate = it.recordDate, | |||
| status = it.status?.value, | |||
| reason = it.reason, | |||
| ) | |||
| } | |||
| return response | |||
| } | |||
| } | |||
| @@ -20,6 +20,10 @@ open class InventoryLotService( | |||
| private val inventoryLotRepository: InventoryLotRepository, | |||
| ): AbstractBaseEntityService<InventoryLot, Long, InventoryLotRepository>(jdbcDao, inventoryLotRepository) { | |||
| open fun findByStockInLineId(stockInLineId: Long): InventoryLot? { | |||
| return inventoryLotRepository.findByStockInLineIdAndDeletedFalse(stockInLineId) | |||
| } | |||
| @Throws(IOException::class) | |||
| @Transactional | |||
| open fun createOrUpdate(request: SaveInventoryRequest): MessageResponse { | |||
| @@ -171,6 +171,7 @@ open class StockInLineService( | |||
| this.item = item | |||
| this.stockInLine = stockInLine | |||
| this.failQty = it.failQty | |||
| this.qcPassed = it.qcPassed | |||
| this.type = "qc" // default as qc for now | |||
| this.remarks = it.remarks | |||
| } | |||
| @@ -1,38 +0,0 @@ | |||
| package com.ffii.fpsms.modules.stock.service | |||
| import com.ffii.core.support.AbstractBaseEntityService | |||
| import com.ffii.core.support.JdbcDao | |||
| import com.ffii.fpsms.modules.common.SecurityUtils | |||
| import com.ffii.fpsms.modules.stock.entity.SupervisionApprovalLog | |||
| import com.ffii.fpsms.modules.stock.entity.SupervisionApprovalLogInfo | |||
| import com.ffii.fpsms.modules.stock.entity.SupervisionApprovalLogRepository | |||
| import org.springframework.stereotype.Service | |||
| @Service | |||
| open class SupervisionApprovalLogService( | |||
| private val jdbcDao: JdbcDao, | |||
| private val supervisionApprovalLogRepository: SupervisionApprovalLogRepository | |||
| ): AbstractBaseEntityService<SupervisionApprovalLog, Long, SupervisionApprovalLogRepository>(jdbcDao, supervisionApprovalLogRepository) { | |||
| open fun getAllLogs(): List<SupervisionApprovalLog> { | |||
| return supervisionApprovalLogRepository.findAll() | |||
| } | |||
| open fun getStockInLog(): List<SupervisionApprovalLog> { | |||
| val stockInLog = getAllLogs().filter { | |||
| it.stockInLine != null | |||
| } | |||
| return stockInLog | |||
| } | |||
| open fun getLogByUser(): List<SupervisionApprovalLogInfo> { | |||
| val user = SecurityUtils.getUser().orElseThrow() | |||
| val filterLog = supervisionApprovalLogRepository | |||
| .findAllInfoByDeletedFalse() | |||
| // .findAll() | |||
| // .findAllByPersonInCharge(user.id!!).filter { | |||
| // it.stockInLine != null | |||
| // } | |||
| println("filterLog") | |||
| println(filterLog) | |||
| return filterLog | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| package com.ffii.fpsms.modules.stock.web | |||
| import com.ffii.fpsms.modules.stock.entity.EscalationLogInfo | |||
| import com.ffii.fpsms.modules.stock.service.EscalationLogService | |||
| import com.ffii.fpsms.modules.stock.web.model.SaveEscalationLogRequest | |||
| import com.ffii.fpsms.modules.stock.web.model.SaveEscalationLogResponse | |||
| import com.ffii.fpsms.modules.stock.web.model.SearchEscalationLogRequest | |||
| import jakarta.validation.Valid | |||
| import org.springframework.web.bind.annotation.GetMapping | |||
| import org.springframework.web.bind.annotation.ModelAttribute | |||
| import org.springframework.web.bind.annotation.PostMapping | |||
| import org.springframework.web.bind.annotation.RequestBody | |||
| import org.springframework.web.bind.annotation.RequestMapping | |||
| import org.springframework.web.bind.annotation.RestController | |||
| @RestController | |||
| @RequestMapping("/escalationLog") | |||
| class EscalationLogController( | |||
| private val escalationLogService: EscalationLogService | |||
| ) { | |||
| @GetMapping("/stockInLines") | |||
| fun getLogsByStockInLines(@ModelAttribute request: SearchEscalationLogRequest): List<EscalationLogInfo> { | |||
| return escalationLogService.getLogsByStockInLines(request.stockInLineIds); | |||
| } | |||
| @GetMapping("/stockInLine") | |||
| fun getLogsByStockInLine(@ModelAttribute request: SearchEscalationLogRequest): List<EscalationLogInfo> { | |||
| return escalationLogService.getLogsByStockInLine(request.stockInLineId); | |||
| } | |||
| @GetMapping("/user") | |||
| fun getLogsByUser(): List<EscalationLogInfo> { | |||
| return escalationLogService.getLogsByUser(); | |||
| } | |||
| @PostMapping("/save") | |||
| fun saveEscalationLog(@Valid @RequestBody request: SaveEscalationLogRequest): SaveEscalationLogResponse { | |||
| return escalationLogService.saveEscalationLog(request); | |||
| } | |||
| } | |||
| @@ -1,19 +0,0 @@ | |||
| package com.ffii.fpsms.modules.stock.web | |||
| import com.ffii.fpsms.modules.stock.entity.SupervisionApprovalLog | |||
| import com.ffii.fpsms.modules.stock.entity.SupervisionApprovalLogInfo | |||
| import com.ffii.fpsms.modules.stock.service.SupervisionApprovalLogService | |||
| import org.springframework.web.bind.annotation.GetMapping | |||
| import org.springframework.web.bind.annotation.RequestMapping | |||
| import org.springframework.web.bind.annotation.RestController | |||
| @RestController | |||
| @RequestMapping("/supervisionApprovalLog") | |||
| class SupervisionApprovalLogController( | |||
| private val supervisionApprovalLogService: SupervisionApprovalLogService | |||
| ) { | |||
| @GetMapping("/stock-in") | |||
| fun getStockInQcLog(): List<SupervisionApprovalLogInfo> { | |||
| return supervisionApprovalLogService.getLogByUser() | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| package com.ffii.fpsms.modules.stock.web.model | |||
| import jakarta.validation.constraints.NotBlank | |||
| import jakarta.validation.constraints.NotNull | |||
| import java.time.LocalDateTime | |||
| data class SaveEscalationLogRequest( | |||
| val id: Long?, | |||
| @field:NotNull(message = "Person In Charge cannot be empty") | |||
| val handlerId: Long, | |||
| val type: String?, | |||
| val stockInLineId: Long?, | |||
| val stockOutLineId: Long?, | |||
| val recordDate: LocalDateTime?, | |||
| @field:NotBlank(message = "Status cannot be empty") | |||
| val status: String, | |||
| val reason: String?, | |||
| val qcTotalCount: Int?, | |||
| val qcFailCount: Int?, | |||
| ) | |||
| @@ -0,0 +1,16 @@ | |||
| package com.ffii.fpsms.modules.stock.web.model | |||
| import java.time.LocalDateTime | |||
| data class SaveEscalationLogResponse( | |||
| val id: Long?, | |||
| val stockInLineId: Long?, | |||
| val stockOutLineId: Long?, | |||
| val handlerId: Long?, | |||
| val handlerName: String?, | |||
| val handlerTitle: String?, | |||
| val handlerDepartment: String?, | |||
| val recordDate: LocalDateTime?, | |||
| val status: String?, | |||
| val reason: String?, | |||
| ) | |||
| @@ -0,0 +1,7 @@ | |||
| package com.ffii.fpsms.modules.stock.web.model | |||
| data class SearchEscalationLogRequest( | |||
| val stockInLineIds: List<Long>?, | |||
| val stockInLineId: Long?, | |||
| val userId: Long?, | |||
| ) | |||
| @@ -3,6 +3,7 @@ package com.ffii.fpsms.modules.user.entity; | |||
| import java.util.List; | |||
| import java.util.Optional; | |||
| import com.ffii.fpsms.modules.user.entity.projections.UserCombo; | |||
| import org.springframework.data.repository.query.Param; | |||
| import com.ffii.core.support.AbstractRepository; | |||
| @@ -12,4 +13,6 @@ public interface UserRepository extends AbstractRepository<User, Long> { | |||
| List<User> findByName(@Param("name") String name); | |||
| Optional<User> findByUsernameAndDeletedFalse(String username); | |||
| List<UserCombo> findUserComboByTitleNotNullAndDepartmentNotNull(); | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| package com.ffii.fpsms.modules.user.entity.projections; | |||
| import org.springframework.beans.factory.annotation.Value; | |||
| public interface UserCombo { | |||
| Long getId(); | |||
| @Value("#{target.id}") | |||
| Long getValue(); | |||
| @Value("#{target.department} - #{target.title} - #{target.name}") | |||
| String getLabel(); | |||
| String getName(); | |||
| String getDepartment(); | |||
| String getTitle(); | |||
| } | |||
| @@ -8,6 +8,7 @@ import java.util.Optional; | |||
| import java.util.Set; | |||
| import java.util.stream.Collectors; | |||
| import com.ffii.fpsms.modules.user.entity.projections.UserCombo; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.springframework.beans.BeanUtils; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| @@ -269,4 +270,8 @@ public class UserService extends AbstractBaseEntityService<User, Long, UserRepos | |||
| instance = save(instance); | |||
| return randomPassword; | |||
| } | |||
| public List<UserCombo> getEscalationCombo() { | |||
| return userRepository.findUserComboByTitleNotNullAndDepartmentNotNull(); | |||
| } | |||
| } | |||
| @@ -5,6 +5,8 @@ import java.util.HashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import com.ffii.fpsms.modules.user.entity.projections.UserCombo; | |||
| import com.ffii.fpsms.modules.user.service.pojo.UserRecord; | |||
| import org.apache.commons.logging.Log; | |||
| import org.apache.commons.logging.LogFactory; | |||
| import org.springframework.http.HttpStatus; | |||
| @@ -38,7 +40,6 @@ import com.ffii.fpsms.modules.user.req.NewUserReq; | |||
| import com.ffii.fpsms.modules.user.req.SearchUserReq; | |||
| import com.ffii.fpsms.modules.user.req.UpdateUserReq; | |||
| import com.ffii.fpsms.modules.user.service.UserService; | |||
| import com.ffii.fpsms.modules.user.service.pojo.UserRecord; | |||
| import com.ffii.fpsms.modules.user.service.res.LoadUserRes; | |||
| import jakarta.validation.Valid; | |||
| @@ -71,6 +72,11 @@ public class UserController{ | |||
| return ResponseEntity.ok(userService.search(req)); | |||
| } | |||
| @GetMapping("/escalation-combo") | |||
| public ResponseEntity<List<UserCombo>> escalationCombo() { | |||
| return ResponseEntity.ok(userService.getEscalationCombo()); | |||
| } | |||
| @GetMapping("/name-list") | |||
| public ResponseEntity<List<Map<String, Object>>> namelist() { | |||
| SearchUserReq req = new SearchUserReq(); | |||
| @@ -0,0 +1,6 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset cyril:update_supervision_approval_log | |||
| ALTER TABLE `supervision_approval_log` | |||
| ADD COLUMN `qcFailCount` INT NULL AFTER `reason`, | |||
| ADD COLUMN `qcTotalCount` INT NULL AFTER `qcFailCount`; | |||
| @@ -0,0 +1,5 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset cyril:update_supervision_approval_log | |||
| ALTER TABLE `supervision_approval_log` | |||
| CHANGE COLUMN `type` `type` VARCHAR(100) NULL; | |||
| @@ -0,0 +1,5 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset cyril:rename_supervision_approval_log | |||
| ALTER TABLE `supervision_approval_log` | |||
| RENAME TO `escalation_log` ; | |||
| @@ -0,0 +1,4 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset kelvin:update qc result table | |||
| ALTER TABLE `qc_result` | |||
| ADD COLUMN `qcPassed` TINYINT(1) NOT NULL DEFAULT 1 AFTER `remarks`; | |||
| @@ -0,0 +1,11 @@ | |||
| -- liquibase formatted sql | |||
| -- changeset cyril:rename_escalation_log | |||
| ALTER TABLE `escalation_log` | |||
| DROP FOREIGN KEY `FK_SUPERVISION_APPROVAL_LOG_ON_PERSONINCHARGE`; | |||
| ALTER TABLE `escalation_log` | |||
| CHANGE COLUMN `personInCharge` `handlerId` INT NOT NULL ; | |||
| ALTER TABLE `escalation_log` | |||
| ADD CONSTRAINT `FK_SUPERVISION_APPROVAL_LOG_ON_PERSONINCHARGE` | |||
| FOREIGN KEY (`handlerId`) | |||
| REFERENCES `user` (`id`); | |||