# 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`); |