From 0b756812360125b584934817d5f5f9afa82f9ad8 Mon Sep 17 00:00:00 2001 From: arsalan Date: Fri, 19 Jan 2024 18:27:44 +0530 Subject: [PATCH 1/4] add filters, excel --- .../com/restapi/controllers/Entities.kt | 8 +++- .../kotlin/com/restapi/controllers/Excel.kt | 24 +++++++++++- .../kotlin/com/restapi/controllers/Filters.kt | 37 +++++++++++++------ src/main/kotlin/com/restapi/domain/models.kt | 2 + 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/restapi/controllers/Entities.kt b/src/main/kotlin/com/restapi/controllers/Entities.kt index 8654c34..9276b00 100644 --- a/src/main/kotlin/com/restapi/controllers/Entities.kt +++ b/src/main/kotlin/com/restapi/controllers/Entities.kt @@ -373,7 +373,7 @@ object Entities { false } } - +data class Filters(val common :CommonFilters, val custom :CustomFilters) object PurchaseOrder { fun get(ctx :Context){ val id = ctx.pathParam("id") @@ -381,6 +381,12 @@ object PurchaseOrder { ctx.json(po) } + fun getAll(ctx :Context){ + val filters = ctx.bodyAsClass() + val poFilters :POFilters? = filters.custom as? POFilters + val pos = searchPos(filters.common, poFilters) + ctx.json(pos) + } fun create(ctx :Context){ val po = ctx.bodyAsClass() database.save(po) diff --git a/src/main/kotlin/com/restapi/controllers/Excel.kt b/src/main/kotlin/com/restapi/controllers/Excel.kt index 84e8f45..4d45758 100644 --- a/src/main/kotlin/com/restapi/controllers/Excel.kt +++ b/src/main/kotlin/com/restapi/controllers/Excel.kt @@ -1,9 +1,29 @@ package com.restapi.controllers -import org.apache.poi +import org.apache.poi.hssf.usermodel.HSSFSheet +import org.apache.poi.hssf.usermodel.HSSFWorkbook +import com.restapi.domain.Session.database + + import java.io.FileOutputStream enum class DataType { QUOTE, PO, VENDOR } -fun CreateExcel(cols :List, excelFor :DataType) { +fun createHeaderRow(cols :List, sh :HSSFSheet) { + sh.createRow(0).apply { + cols.forEachIndexed{index, value -> + createCell(index).setCellValue(value) + } + } +} +fun ExportToExcel(cols :List, data :List>) { val wb = HSSFWorkbook() + val sh = wb.createSheet() + createHeaderRow(cols, sh) + for((rowCount, row) in data.withIndex()) { + sh.createRow(rowCount).apply{ + for((colCount, cell) in row.withIndex()) { + createCell(colCount).setCellValue(cell) + } + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/restapi/controllers/Filters.kt b/src/main/kotlin/com/restapi/controllers/Filters.kt index 1dce9c8..411344e 100644 --- a/src/main/kotlin/com/restapi/controllers/Filters.kt +++ b/src/main/kotlin/com/restapi/controllers/Filters.kt @@ -23,6 +23,7 @@ data class CommonFilters ( val sortAsc :Boolean = true, val sortBy :String = IGNORE ) +interface CustomFilters{} data class POFilters ( val poNumLike :String = IGNORE, val totalAmountExceeds :Long = Long.MIN_VALUE, @@ -30,16 +31,18 @@ data class POFilters ( val validAfter: LocalDate = baseDate, val validBefore: LocalDate = maxDate, val refQuotation :String = IGNORE, -) +) : CustomFilters data class ProductFilters ( val nameLike :String = IGNORE, val hsnLike :String = IGNORE, val uom :UOM = UOM.ALL, -) +) : CustomFilters data class DocumentFilters ( val nameLike :String = IGNORE, val typeOfDoc :DocType = DocType.ALL, -) + val docDateFrom :LocalDate = baseDate, + val docDataTo :LocalDate = maxDate, +) :CustomFilters data class RFQFilters ( val validBefore :LocalDate = maxDate, val validAfter :LocalDate = baseDate, @@ -51,7 +54,7 @@ data class QuoteFilters ( val validAfter :LocalDate = maxDate, val totalAmountExceeds :Long = Long.MIN_VALUE, val totalAmountLessThan :Long = Long.MAX_VALUE, -) +) :CustomFilters data class VendorFilters ( val nameLike :String = IGNORE, val msmeLike :String = IGNORE, @@ -59,7 +62,7 @@ data class VendorFilters ( val addressLike :String = IGNORE, val ratingExceeds :Double = RATING_MIN, val ratingLessThan :Double = RATING_MAX, -) +) :CustomFilters fun applyVendorHelper(q :io.ebean.ExpressionList, vids :List?) { if (vids.isNullOrEmpty()) return q.apply { @@ -71,6 +74,10 @@ fun applySortHelper(q :io.ebean.ExpressionList, sortBy :String, asc :Boole val order = if (asc) "ASC" else "DESC" q.orderBy("$sortBy $order") } +fun applyFromToHelper(q :io.ebean.ExpressionList, fromDate: LocalDate, toDate: LocalDate, colName :String) { + q.ge(colName, fromDate) + .le(colName, toDate) +} fun applyCommonFilters(q :io.ebean.ExpressionList, commonFilters: CommonFilters) { applyVendorHelper(q, commonFilters.vendor) applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) @@ -78,12 +85,13 @@ fun applyCommonFilters(q :io.ebean.ExpressionList, commonFilters: CommonFi fun searchQuotes(commonFilters: CommonFilters, quoteFilters: QuoteFilters) : List { val q = database.find(Quotation::class.java) .where() - .between("quoteDate", commonFilters.fromDate, commonFilters.toDate) - .ilike("quoteNum", quoteFilters.quoteNumLike ) + .ge("quoteDate", commonFilters.fromDate) + .le("quoteDate", commonFilters.toDate) .ge("validTill",quoteFilters.validAfter) .le("validTill", quoteFilters.validBefore) - .le("totalAmount", quoteFilters.totalAmountLessThan) .ge("totalAmount", quoteFilters.totalAmountExceeds) + .le("totalAmount", quoteFilters.totalAmountLessThan) + .ilike("quoteNum", quoteFilters.quoteNumLike ) .apply { if(!commonFilters.vendor?.isEmpty()!!){ commonFilters.vendor.let { this.`in`("vendor", it) } @@ -114,13 +122,17 @@ fun searchDocs(commonFilters: CommonFilters, documentFilters: DocumentFilters) : } .ilike("name", documentFilters.nameLike ) applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) + applyVendorHelper(q, commonFilters.vendor) return q.findList() } -fun searchPos(commonFilters: CommonFilters, poFilters: POFilters) : List { +fun searchPos(commonFilters: CommonFilters, poFilters: POFilters?) : List { + val poFilters = poFilters ?: POFilters() val q = database.find(PurchaseOrder::class.java) .where() - .between("totalAmount", poFilters.totalAmountExceeds, poFilters.totalAmountLessThan) - .between("validTill", poFilters.validAfter, poFilters.validBefore) + .ge("totalAmount", poFilters.totalAmountExceeds) + .le("totalAmount", poFilters.totalAmountLessThan) + .ge("validTill", poFilters.validAfter) + .le("validTill", poFilters.validBefore) .ilike("poNum", poFilters.poNumLike ) .ilike("referenceQuotation", poFilters.refQuotation ) applyVendorHelper(q, commonFilters.vendor) @@ -130,7 +142,8 @@ fun searchPos(commonFilters: CommonFilters, poFilters: POFilters) : List { val q = database.find(ReqForQuote::class.java) .where() - .between("validTill", rfqFilters.validAfter, rfqFilters.validBefore) + .ge("validTill", rfqFilters.validAfter) + .le("validTill", rfqFilters.validBefore) .ilike("reqForQuoteNum", rfqFilters.reqForQuoteNumLike) applyVendorHelper(q, commonFilters.vendor) applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) diff --git a/src/main/kotlin/com/restapi/domain/models.kt b/src/main/kotlin/com/restapi/domain/models.kt index 2753361..6b75540 100644 --- a/src/main/kotlin/com/restapi/domain/models.kt +++ b/src/main/kotlin/com/restapi/domain/models.kt @@ -312,6 +312,8 @@ open class Document :BaseTenantModel() { var refId: Long? = null var description :String = "" var url :String = "" + var docDate :LocalDate? = null + var vendor :Vendor? = null } enum class RFQStatus{ From 2fecc4b3fdaa08f8cc249b7e587110a2db5c651d Mon Sep 17 00:00:00 2001 From: arsalan Date: Mon, 22 Jan 2024 18:32:24 +0530 Subject: [PATCH 2/4] add excel import and export --- build.gradle.kts | 2 + .../kotlin/com/restapi/controllers/Excel.kt | 359 +++++++++++++++++- src/main/kotlin/com/restapi/domain/models.kt | 13 +- 3 files changed, 360 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2894a75..5abd28d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,8 @@ dependencies { implementation("org.apache.httpcomponents:httpclient:4.5.14") implementation("org.apache.poi:poi:5.0.0") implementation("org.apache.poi:poi-ooxml:5.0.0") + implementation("com.google.code.gson:gson:2.8.8") + api ("net.cactusthorn.config:config-core:0.81") api ("net.cactusthorn.config:config-yaml:0.81") kapt("net.cactusthorn.config:config-compiler:0.81") diff --git a/src/main/kotlin/com/restapi/controllers/Excel.kt b/src/main/kotlin/com/restapi/controllers/Excel.kt index 4d45758..3f9b910 100644 --- a/src/main/kotlin/com/restapi/controllers/Excel.kt +++ b/src/main/kotlin/com/restapi/controllers/Excel.kt @@ -1,13 +1,28 @@ package com.restapi.controllers +import com.google.gson.Gson +import com.restapi.domain.* +import com.restapi.domain.Document +import com.restapi.domain.PurchaseOrder +import com.restapi.domain.Quotation import org.apache.poi.hssf.usermodel.HSSFSheet import org.apache.poi.hssf.usermodel.HSSFWorkbook import com.restapi.domain.Session.database +import com.restapi.domain.Vendor +import org.apache.poi.ss.usermodel.Cell +import org.apache.poi.ss.usermodel.CellType +import org.apache.poi.ss.usermodel.DateUtil +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.ss.usermodel.WorkbookFactory +import java.io.File +import java.io.FileInputStream import java.io.FileOutputStream -enum class DataType { - QUOTE, PO, VENDOR -} +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.ZoneId +import java.util.* + fun createHeaderRow(cols :List, sh :HSSFSheet) { sh.createRow(0).apply { cols.forEachIndexed{index, value -> @@ -15,14 +30,340 @@ fun createHeaderRow(cols :List, sh :HSSFSheet) { } } } -fun ExportToExcel(cols :List, data :List>) { +fun String.parseDate(format: String): Date? { + val locale = Locale.getDefault() + return try { + SimpleDateFormat(format, locale).parse(this) + } catch (e: Exception) { + null + } +} +fun dateFromCellHelper(cell: Cell): LocalDate?{ + val date = when(cell.cellType){ + CellType.STRING -> cell.stringCellValue.parseDate("yyyy-MM-dd") + CellType.NUMERIC -> { + if (DateUtil.isCellDateFormatted(cell)) { + cell.getDateCellValue() + } else{ + null + } + } + else -> null + } + return date?.toInstant()?.atZone(ZoneId.systemDefault())?.toLocalDate() +} +fun stringFromCellHelper(cell: Cell): String { + val string = when(cell.cellType){ + CellType.NUMERIC -> cell.numericCellValue.toString() + CellType.STRING -> cell.stringCellValue + else -> "" + } + return string +} +fun doubleFromCellHelper(cell: Cell): Double { + val double = when(cell.cellType){ + CellType.NUMERIC -> cell.numericCellValue + CellType.STRING -> cell.stringCellValue.toDoubleOrNull() + else -> 0.0 + } + return double?:0.0 +} +fun enumFromCellHelper(cell: Cell, enumFor: EnumFor) :String{ + var string = "" + val cellValue = cell.stringCellValue + when(enumFor){ + EnumFor.UOM -> { + string = "uom" + } + EnumFor.DocType -> { + string = "doc" + } + } + return string +} +fun longIntFromCellHelper(cell : Cell) :Long { + val long = when(cell.cellType){ + CellType.NUMERIC -> cell.numericCellValue.toLong() + CellType.STRING -> cell.stringCellValue.toLong() + else -> 0 + } + return long +} +enum class FileType { + QUOTES, POS, VENDORS, PRODS, DOCS +} +enum class EnumFor { + UOM, DocType +} +fun ExportQuotations(quotes :List) { val wb = HSSFWorkbook() val sh = wb.createSheet() - createHeaderRow(cols, sh) - for((rowCount, row) in data.withIndex()) { - sh.createRow(rowCount).apply{ - for((colCount, cell) in row.withIndex()) { - createCell(colCount).setCellValue(cell) + + val headers : List = listOf("Quotation Number", "Date", "Open Till", "Product Id", "Product Name", "Product Unit Price", "Quantity", "Vendor Name", "Vendor Address", "RFQ Number", "Total AMount", "Terms and Conditions") + createHeaderRow(headers, sh) + val totalCols = headers.size + var rowCnt = 1 + for(quote in quotes){ + val prodCnt = quote.products.size + + for (j in 0..prodCnt - 1){ + val row = sh.createRow(rowCnt++) + var i = 0; + row.createCell(i++).setCellValue(quote.quoteNum) + row.createCell(i++).setCellValue(quote.quoteDate) + row.createCell(i++).setCellValue(quote.validTill) + //6 would be repeated + row.createCell(i++).setCellValue(quote.products[j].productId) + row.createCell(i++).setCellValue(quote.products[j].productName) + row.createCell(i++).setCellValue(quote.products[j].unitPrice) + row.createCell(i++).setCellValue(quote.products[j].quantity) + + row.createCell(i++).setCellValue(quote.vendor?.name) + row.createCell(i++).setCellValue(quote.vendor?.address) + + row.createCell(i++).setCellValue(quote.reqForQuoteNum) + row.createCell(i++).setCellValue(quote.totalAmount) + + row.createCell(i++).setCellValue(quote.tnc.joinToString(";")) + } + } +} + +fun ExportVendors(vendors :List){ + val wb = HSSFWorkbook() + val sh = wb.createSheet() + + val headers : List = listOf("Name", "MSME", "GST Number", "Address", "Rating", "Contact Name", "Contact Email", "Contact Mobile") + createHeaderRow(headers, sh) + + val totalCols = headers.size + var rowCnt = 1 + + for (vendor in vendors){ + val contactCnt = vendor.contacts.size + for (j in 0..contactCnt - 1){ + val row = sh.createRow(rowCnt++) + var i = 0 + row.createCell(i++).setCellValue(vendor.msme) + row.createCell(i++).setCellValue(vendor.gstNumber) + row.createCell(i++).setCellValue(vendor.address) + row.createCell(i++).setCellValue(vendor.rating) + row.createCell(i++).setCellValue(vendor.contacts[j].name) + row.createCell(i++).setCellValue(vendor.contacts[j].email) + row.createCell(i++).setCellValue(vendor.contacts[j].mobile) + } + } +} + +fun ExportProds(prods :List){ + val wb = HSSFWorkbook() + val sh = wb.createSheet() + + val headers : List = listOf("Id", "Name", "Description", "HSN Code", "UOM") + createHeaderRow(headers, sh) + + val totalCols = headers.size + var rowCnt = 1 + + for (prod in prods){ + val row = sh.createRow(rowCnt++) + var i = 0 + row.createCell(i++).setCellValue(prod.id.toString()) + row.createCell(i++).setCellValue(prod.name) + row.createCell(i++).setCellValue(prod.description) + row.createCell(i++).setCellValue(prod.hsnCode) + row.createCell(i++).setCellValue(prod.uom?.name) + } +} +fun ExportPos(pos :List){ + val wb = HSSFWorkbook() + val sh = wb.createSheet() + + val headers : List = listOf("Number", "Date", "Open Till", "Reference Quotation Number", "Vendor Name", "Vendor Address", "Product Id", "Product Name", "Unit Price", "Quantity", "Total Amount", "Terms and Conditions") + createHeaderRow(headers, sh) + + val totalCols = headers.size + var rowCnt = 1 + for(po in pos){ + val prodCnt = po.products.size + + for (j in 0..prodCnt - 1){ + val row = sh.createRow(rowCnt++) + var i = 0 + row.createCell(i++).setCellValue(po.poNum) + row.createCell(i++).setCellValue(po.poDate) + row.createCell(i++).setCellValue(po.validTill) + row.createCell(i++).setCellValue(po.referenceQuotation) + row.createCell(i++).setCellValue(po.vendor?.name) + row.createCell(i++).setCellValue(po.vendor?.address) + + //6 would be repeated + row.createCell(i++).setCellValue(po.products[j].productId) + row.createCell(i++).setCellValue(po.products[j].productName) + row.createCell(i++).setCellValue(po.products[j].unitPrice) + row.createCell(i++).setCellValue(po.products[j].quantity) + + row.createCell(i++).setCellValue(po.totalAmount) + row.createCell(i++).setCellValue(po.tnc.joinToString(";")) + } + } +} +fun main() { + ImportFromExcel(FileType.QUOTES, "C:\\Users\\arsalan\\Downloads\\Book.xlsx") +} + +fun ImportFromExcel(fileType: FileType, filePath : String) { + val wb = WorkbookFactory.create(File(filePath)) + val sh = wb.getSheetAt(0) + + when(fileType){ + FileType.QUOTES -> { + //Quote Number, ProductName, Product Quantity, Product Uom, Total Amount, RFQ Number, Quote Date, Valid Till, TNC[], Documents[] + val quotesMap : MutableMap = mutableMapOf() + val quotesList : List = mutableListOf() + sh.rowIterator().forEach { row -> + if(row == null){ + //reached eof + return@forEach + } + val quoteNum = stringFromCellHelper(row.getCell(0)) + val quoteDate = dateFromCellHelper(row.getCell(1)) + val rfqNum = stringFromCellHelper(row.getCell(2)) + val quoteValidTill = dateFromCellHelper(row.getCell(3)) + val vendorName = stringFromCellHelper(row.getCell(4)) + val vendorGstNum = stringFromCellHelper(row.getCell(5)) + val vendorAddress = stringFromCellHelper(row.getCell(6)) + val prodName = stringFromCellHelper(row.getCell(7)) + val prodQuantity = doubleFromCellHelper(row.getCell(8)) + val prodUnitPrice = doubleFromCellHelper(row.getCell(9)) + val prodUom = enumFromCellHelper(row.getCell(10), EnumFor.UOM) + val totalQuoteAmount = doubleFromCellHelper(row.getCell(11)) + val prod = POProducts("", prodName, prodUnitPrice, prodQuantity) + + if (quotesMap.containsKey(quoteNum)) { + //duplicated row + quotesMap.get(quoteNum)?.products?.add(prod) + }else { + val vendor = Vendor() + vendor.name = vendorName + vendor.address = vendorAddress + vendor.gstNumber = vendorGstNum + val quote = Quotation() + quote.quoteNum = quoteNum + quote.quoteDate = quoteDate + quote.reqForQuoteNum = rfqNum + quote.validTill = quoteValidTill + quote.products = mutableListOf(prod) + quote.vendor = vendor + quote.totalAmount = totalQuoteAmount + quotesMap.put(quoteNum, quote) + } + } + //docs, tncs + // println("$quotesMap") + + quotesMap.forEach { (k, v) -> + println("$v") + } + } + FileType.POS -> { + //poNum, poDate, validTill, refQuoteNum, prodName, prodQuantity, totalAmount, products, vendorName, vendorGst, vendorAddress, tnc[]. docs[] + val PoMap : MutableMap = mutableMapOf() + sh.rowIterator().forEach { row -> + if(row == null) return@forEach + val poNum = stringFromCellHelper(row.getCell(0)) + val poDate = dateFromCellHelper(row.getCell(1)) + val refQuoteNum = stringFromCellHelper(row.getCell(2)) + val poValidTill = dateFromCellHelper(row.getCell(3)) + val prodName = stringFromCellHelper(row.getCell(4)) + val prodQuantity = doubleFromCellHelper(row.getCell(5)) + val vendorName = stringFromCellHelper(row.getCell(6)) + val vendorGstNum = stringFromCellHelper(row.getCell(7)) + val vendorAddress = stringFromCellHelper(row.getCell(8)) + val totalPoAmount = doubleFromCellHelper(row.getCell(9)) + //tncs, docs + + val prod = POProducts("", prodName, 0.0, prodQuantity,"") + if(PoMap.containsKey(poNum)){ + //repeated row + PoMap.get(poNum)?.products?.add(prod) + }else{ + val vendor = Vendor() + vendor.name = vendorName + vendor.address = vendorAddress + vendor.gstNumber = vendorGstNum + val po = PurchaseOrder() + po.poNum = poNum + po.poDate = poDate + po.referenceQuotation = refQuoteNum + po.validTill = poValidTill + PoMap.put(poNum, po) + } + } + } + FileType.VENDORS -> { + sh.rowIterator().forEach { row -> + //name, msme, gstNum, addresss, rating, contacts + if(row == null) return@forEach + val name = stringFromCellHelper(row.getCell(0)) + val msme = stringFromCellHelper(row.getCell(1)) + val gstNum = stringFromCellHelper(row.getCell(2)) + val address = stringFromCellHelper(row.getCell(3)) + val rating = doubleFromCellHelper(row.getCell(4)) + + //vendor object + val vendor = Vendor() + vendor.name = name + vendor.address = address + vendor.msme = msme + vendor.gstNumber = gstNum + vendor.rating = rating + } + } + FileType.PRODS -> { + sh.rowIterator().forEach { row -> + if(row == null) return@forEach + //id, name, description, hsnCode, uom + val prodId = longIntFromCellHelper(row.getCell(0)) + val prodName = stringFromCellHelper(row.getCell(1)) + val prodDesc = stringFromCellHelper(row.getCell(2)) + val prodHsnCode = stringFromCellHelper(row.getCell(3)) + val prodUom = stringFromCellHelper(row.getCell(4)) + + //new prod object + val prod = Product() + prod.id = prodId + prod.name = prodName + prod.description = prodDesc + prod.hsnCode = prodHsnCode + prod.uom = when(prodUom) { + "nos" -> UOM.NOS + "ltr" -> UOM.LTR + "mtr" -> UOM.MTR + else -> UOM.ALL + } + } + } + FileType.DOCS -> { + sh.rowIterator().forEach { row -> + //Document Name, Document Type, RefID, url + if (row == null) return@forEach + val docName = stringFromCellHelper(row.getCell(0)) + val docType = stringFromCellHelper(row.getCell(1)) + val refId = stringFromCellHelper(row.getCell(2)) + val url = stringFromCellHelper(row.getCell(3)) + + //new doc object + val doc = Document() + doc.name = docName + doc.typeOfDoc = when(docType) { + "quote" -> DocType.QUOTE + "po" -> DocType.PO + "invoice" -> DocType.INVOICE + else -> DocType.ALL + } + doc.refId = refId + doc.url = url } } } diff --git a/src/main/kotlin/com/restapi/domain/models.kt b/src/main/kotlin/com/restapi/domain/models.kt index 6b75540..d1f742f 100644 --- a/src/main/kotlin/com/restapi/domain/models.kt +++ b/src/main/kotlin/com/restapi/domain/models.kt @@ -12,7 +12,9 @@ import java.time.LocalDateTime import javax.persistence.* data class Comments(val text: String = "", val by: String = "", val at: LocalDateTime = LocalDateTime.now()) -data class POProducts(val productId: String = "", val unitPrice :Double = 0.0, val quantity: Double = 0.0, val description :String = "") +data class POProducts(val productId: String = "", val productName: String = "", val unitPrice :Double = 0.0, val quantity: Double = 0.0, val description :String = "") + + enum class ApprovalStatus { PENDING, APPROVED, REJECTED } @@ -259,7 +261,7 @@ open class PurchaseOrder :BaseTenantModel() { @ManyToOne var vendor :Vendor? = null var referenceQuotation :String = "" - var totalAmount :Int = 0 + var totalAmount :Double = 0.0 var poNum: String = "" var poDate: LocalDate? = null var validTill: LocalDate? = null @@ -274,7 +276,7 @@ enum class UOM { } @Entity open class Product :BaseTenantModel() { - var id: Int? = null + var id: Long? = null var name :String = "" var description :String = "" var hsnCode :String = "" @@ -288,7 +290,7 @@ open class Quotation :BaseTenantModel() { var products :MutableList = mutableListOf() @ManyToOne var vendor :Vendor? = null - var totalAmount :Long = 0 + var totalAmount :Double = 0.0 var reqForQuoteNum: String = "" var quoteNum: String = "" @@ -309,7 +311,8 @@ open class Document :BaseTenantModel() { var name :String = "" @Enumerated(EnumType.STRING) var typeOfDoc :DocType? = null - var refId: Long? = null + //could be quoteNum, PoNum, InvoiceNum + var refId: String? = null var description :String = "" var url :String = "" var docDate :LocalDate? = null From 3cd5c4c4717688a4229975b58be366dd20a663c2 Mon Sep 17 00:00:00 2001 From: arsalan Date: Wed, 24 Jan 2024 10:01:25 +0530 Subject: [PATCH 3/4] add excel templates fix apis --- Products_Template.xls | Bin 0 -> 4096 bytes api.http | 230 +++++++++++++++++- src/main/kotlin/com/restapi/Main.kt | 38 +-- .../com/restapi/controllers/Entities.kt | 129 ++++++++-- .../kotlin/com/restapi/controllers/Excel.kt | 139 +++++++---- .../kotlin/com/restapi/controllers/Filters.kt | 39 ++- src/main/kotlin/com/restapi/domain/models.kt | 10 +- src/main/resources/dbmigration/1.3.sql | 43 ++++ .../resources/dbmigration/model/1.3.model.xml | 42 ++++ 9 files changed, 555 insertions(+), 115 deletions(-) create mode 100644 Products_Template.xls create mode 100644 src/main/resources/dbmigration/1.3.sql create mode 100644 src/main/resources/dbmigration/model/1.3.model.xml diff --git a/Products_Template.xls b/Products_Template.xls new file mode 100644 index 0000000000000000000000000000000000000000..ca24ad10afedd0e75c8059a41edb52f962f297b9 GIT binary patch literal 4096 zcmeHKNla5w6utlV7m6|#1jXs6NTj7IMoowW3l51U3RW}t(H@EawQPS7CgI9=nV{AnsD zx28TfZGWuNgA2Muu5EA@(c{!lJu;5|RlG8*yreJ2=W%4VS}E;w;;+>FUlKmM^8nWd zKHUxQnP+~T*8U9endN(&&t&TQ^YQQidB6r>Bajai0EIvifD}L_fEOqQ%79HkIZy#? z2DSi|z*b-zupL<6TYt~K%JX0qbKLa4cqX?QYr|)M7CYjpF)jPz6Zmc#ChUhud?Ydu z88m+j9hEUeG~SzyQa*&F5c`QGxb;z%O8G>zuWUcakp z8rAmiFm_^DaEO!y+6x`A=*3hM6Dx!>rcCzMrS$<8-Bja5o6Bv|d$J7=QGn`*!WxUEZaaoomrG{N2BbawW}Vxx6;kwP{69&1t?6*zFtZ9;Q&Kpt1@fW2`l zipXt|D={S2@Pc=CPq@3>mqrz12eY_0Uy#arWg*Y~R?$^s-CyQu z!ENUOuNBd}e69VWAcxa%NpiQgP6x+dV{CNzG~DB@Jjnn|sm#$5Sg#k_)-lA($sYB|$Etw39QCpEXUus2(Lb{QB3 NFI$$B&xU`JegO6wjx_)P literal 0 HcmV?d00001 diff --git a/api.http b/api.http index 69bd308..b33c0ae 100644 --- a/api.http +++ b/api.http @@ -131,4 +131,232 @@ Authorization: {{auth-token}} ### delete a row DELETE http://localhost:9001/api/vendor/product/2 -Authorization: {{auth-token}} \ No newline at end of file +Authorization: {{auth-token}} + +### create vendor +POST http://localhost:9001/api/vendor/ +Content-Type: application/json +Authorization: {{auth-token}} + +{ + "name": "arsalan", + "msme": "1234", + "hsnCode": "1234", + "gstNumber": "GST123", + "address": "Bangalore", + "rating": 2, + "contacts": [ + { + "name": "contact1", + "email": "abc@cyz.com", + "mobile": "01234567890" + } + ] +} + +### create batch vendor +POST http://localhost:9001/api/vendor/batch +Content-Type: application/json +Authorization: {{auth-token}} + +[ + { + "name": "john", + "description": "5678", + "hsnCode": "5678", + "gstNumber": "GST567", + "address": "Mumbai", + "rating": 4, + "contacts": [ + { + "name": "contact1", + "email": "xyz@abc.com", + "phone": "9876543210" + } + ] + }, + { + "name": "emma", + "description": "7890", + "hsnCode": "7890", + "gstNumber": "GST789", + "address": "Delhi", + "rating": 3, + "contacts": [ + { + "name": "contact2", + "email": "def@uvw.com", + "phone": "8765432109" + } + ] + }, + { + "name": "alex", + "description": "2345", + "hsnCode": "2345", + "gstNumber": "GST234", + "address": "Chennai", + "rating": 5, + "contacts": [ + { + "name": "contact3", + "email": "ghi@rst.com", + "phone": "7654321098" + } + ] + } +] + +### GET ALL VENDORS +POST http://localhost:9001/api/vendor/getAll +Content-Type: application/json +Authorization: {{auth-token}} + +{ + "common": { + "sortAsc": true + }, + "vendorFilters": { + "nameLike": "a" + } +} + +### create batch pos +POST http://localhost:9001/api/vendor/po/batch +Content-Type: application/json +Authorization: {{auth-token}} + +[ + { + "products": [ + { + "productId": "1232", + "productName": "chair", + "unitPrice": 34.2, + "quantity": 10, + "description": "wooden chair" + } + ], + "referenceQuotation": "12323", + "totalAmount": 342, + "poNum": "1", + "poDate": "2024-01-10", + "validTill": "2024-02-10", + "tnc": ["tnc1", "tnc2"] + }, + { + "products": [ + { + "productId": "5678", + "productName": "table", + "unitPrice": 45.5, + "quantity": 5, + "description": "glass table" + } + ], + "referenceQuotation": "56789", + "totalAmount": 227.5, + "poNum": "2", + "poDate": "2024-10-25", + "validTill": "2024-10-25", + "tnc": ["tnc3", "tnc4"] + }, + { + "products": [ + { + "productId": "91011", + "productName": "lamp", + "unitPrice": 15.75, + "quantity": 20, + "description": "floor lamp" + } + ], + "referenceQuotation": "9101112", + "totalAmount": 315, + "poNum": "3", + "poDate": "2024-10-25", + "validTill": "2024-12-25", + "tnc": ["tnc5", "tnc6"] + } +] + +### GET ALL POS +POST http://localhost:9001/api/vendor/po/getAll +Content-Type: application/json +Authorization: {{auth-token}} + +{ + "common" : {}, + "poFilters": {} +} + +### CREATE QUOTES +POST http://localhost:9001/api/vendor/quote/batch +Content-Type: application/json +Authorization: {{auth-token}} + +[ + { + "products": [ + { + "productId": "1232", + "productName": "chair", + "unitPrice": 34.2, + "quantity": 10, + "description": "wooden chair" + } + ], + "reqForQuoteNum": "12323", + "totalAmount": 342, + "quoteNum": "1", + "quoteDate": "2024-10-24", + "validTill": "2024-11-24", + "tnc": ["tnc1", "tnc2"] + }, + { + "products": [ + { + "productId": "5678", + "productName": "table", + "unitPrice": 45.5, + "quantity": 5, + "description": "glass table" + } + ], + "reqForQuoteNum": "56789", + "totalAmount": 227.5, + "quoteNum": "2", + "quoteDate": "2024-10-25", + "validTill": "2024-11-25", + "tnc": ["tnc3", "tnc4"] + }, + { + "products": [ + { + "productId": "91011", + "productName": "lamp", + "unitPrice": 15.75, + "quantity": 20, + "description": "floor lamp" + } + ], + "reqForQuoteNum": "9101112", + "totalAmount": 315, + "quoteNum": "3", + "quoteDate": "2024-10-25", + "validTill": "2024-12-25", + "tnc": ["tnc5", "tnc6"] + } +] + + +### GET ALL QUOTES +POST http://localhost:9001/api/vendor/quote/getAll +Content-Type: application/json +Authorization: {{auth-token}} + +{ + "common" : {}, + "quoteFilters": {} +} + diff --git a/src/main/kotlin/com/restapi/Main.kt b/src/main/kotlin/com/restapi/Main.kt index 6bed341..3b279da 100644 --- a/src/main/kotlin/com/restapi/Main.kt +++ b/src/main/kotlin/com/restapi/Main.kt @@ -6,8 +6,6 @@ import com.restapi.config.AppConfig.Companion.appConfig import com.restapi.config.Auth.validateAuthToken import com.restapi.controllers.* import com.restapi.domain.DataNotFoundException -import com.restapi.domain.Product -import com.restapi.domain.Session import com.restapi.domain.Session.currentTenant import com.restapi.domain.Session.currentUser import com.restapi.domain.Session.objectMapper @@ -114,25 +112,31 @@ fun main(args: Array) { path("/vendor"){ path("/"){ - post("", Vendor::create, Roles(Role.Explicit(listOf("ROLE_VENDOR_CREATE", "ROLE_ADMIN")))) - get("", Vendor::get, Roles(Role.Explicit(listOf("ROLE_VENDOR_VIEW", "ROLE_VENDOR_CREATE", "ROLE_ADMIN")))) - get("quotes/{id}", Vendor::getQuotes, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_QUOTE_VIEW", "ROLE_QUOTE_CREATE", "ROLE_VENDOR_VIEW")))) - get("pos/{id}", Vendor::getPos, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_PO_VIEW", "ROLE_PO_CREATE`")))) - put("/rate/{id}/{rating}", Vendor::rate, Roles(Role.Explicit(listOf("ROLE_VENDOR_CREATE")))) + post("", VendorCtrl::create, Roles(Role.Explicit(listOf("ROLE_VENDOR_CREATE", "ROLE_ADMIN")))) + post("/batch", VendorCtrl::createBatch, Roles(Role.Explicit(listOf("ROLE_VENDOR_CREATE", "ROLE_ADMIN")))) + get("/{id}", VendorCtrl::get, Roles(Role.Explicit(listOf("ROLE_VENDOR_VIEW", "ROLE_VENDOR_CREATE", "ROLE_ADMIN")))) + post("/getAll", VendorCtrl::getAll, Roles(Role.Explicit(listOf("ROLE_VENDOR_VIEW", "ROLE_VENDOR_CREATE")))) + get("quotes/{id}", VendorCtrl::getQuotes, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_QUOTE_VIEW", "ROLE_QUOTE_CREATE", "ROLE_VENDOR_VIEW")))) + get("pos/{id}", VendorCtrl::getPos, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_PO_VIEW", "ROLE_PO_CREATE`")))) + put("/rate/{id}/{rating}", VendorCtrl::rate, Roles(Role.Explicit(listOf("ROLE_VENDOR_CREATE")))) } path("/po"){ - post("", PurchaseOrder::create, Roles(Role.Explicit(listOf("ROLE_PO_CREATE", "ROLE_ADMIN")))) - get("/{id}", PurchaseOrder::get, Roles(Role.Explicit(listOf("ROLE_PO_CREATE", "ROLE_PO_VIEW", "ROLE_QUOTE_CREATE")))) - put("/approve/{id}", PurchaseOrder::approve, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_APPROVE")))) - put("/reject/{id}", PurchaseOrder::reject, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_APPROVE")))) - get("/refQuote/{id}", PurchaseOrder::quoteReference, Roles(Role.Explicit(listOf("ROLE_PO_CREATE", "ROLE_PO_VIEW")))) + post("", PurchaseOrderCtrl::create, Roles(Role.Explicit(listOf("ROLE_PO_CREATE", "ROLE_ADMIN")))) + post("/batch", PurchaseOrderCtrl::createBatch, Roles(Role.Explicit(listOf("ROLE_PO_CREATE", "ROLE_VENDOR_CREATE", "ROLE_ADMIN")))) + post("/getAll", PurchaseOrderCtrl::getAll, Roles(Role.Explicit(listOf("ROLE_PO_CREATE", "ROLE_PO_CREATE", "ROLE_VENDOR_CREATE", "ROLE_ADMIN")))) + get("/{id}", PurchaseOrderCtrl::get, Roles(Role.Explicit(listOf("ROLE_PO_CREATE", "ROLE_PO_VIEW", "ROLE_QUOTE_CREATE")))) + put("/approve/{id}", PurchaseOrderCtrl::approve, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_APPROVE")))) + put("/reject/{id}", PurchaseOrderCtrl::reject, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_APPROVE")))) + get("/refQuote/{id}", PurchaseOrderCtrl::quoteReference, Roles(Role.Explicit(listOf("ROLE_PO_CREATE", "ROLE_PO_VIEW")))) } path("/quote"){ - post("", Quotation::create, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_ADMIN")))) - get("/{id}", Quotation::get, Roles(Role.Explicit(listOf("ROLE_QUOTE_VIEW", "ROLE_ADMIN", "ROLE_PO_CREATE", "ROLE_QUOTE_CREATE")))) - get("/po/{id}", Quotation::generatePO, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_PO_CRETE")))) - get("/rfq/{rfqNum}", Quotation::reqForQuote, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_QUOTE_VIEW")))) - delete("/{id}", Quotation::delete, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_ADMIN")))) + post("", QuotationCtrl::create, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_ADMIN")))) + post("/batch", QuotationCtrl::createBatch, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_ADMIN", "ROLE_VENDOR_CREATE")))) + post("/getAll", QuotationCtrl::getAll, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_ADMIN", "ROLE_VENDOR_CREATE")))) + get("/{id}", QuotationCtrl::get, Roles(Role.Explicit(listOf("ROLE_QUOTE_VIEW", "ROLE_ADMIN", "ROLE_PO_CREATE", "ROLE_QUOTE_CREATE")))) + get("/po/{id}", QuotationCtrl::generatePO, Roles(Role.Explicit(listOf("ROLE_ADMIN", "ROLE_PO_CRETE")))) + get("/rfq/{rfqNum}", QuotationCtrl::reqForQuote, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_QUOTE_VIEW")))) + delete("/{id}", QuotationCtrl::delete, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_ADMIN")))) } path("/product"){ post("", ProductCtrl::create, Roles(Role.Explicit(listOf("ROLE_PRODUCT_CREATE", "ROLE_ADMIN")))) diff --git a/src/main/kotlin/com/restapi/controllers/Entities.kt b/src/main/kotlin/com/restapi/controllers/Entities.kt index 9276b00..d1772ab 100644 --- a/src/main/kotlin/com/restapi/controllers/Entities.kt +++ b/src/main/kotlin/com/restapi/controllers/Entities.kt @@ -374,30 +374,46 @@ object Entities { } } data class Filters(val common :CommonFilters, val custom :CustomFilters) -object PurchaseOrder { +data class BatchPos(val pos :List) +object PurchaseOrderCtrl { fun get(ctx :Context){ val id = ctx.pathParam("id") val po = database.find(PurchaseOrder::class.java, id) ?: throw NotFoundResponse("po not found for $id") - - ctx.json(po) + ctx.json(po).status(HttpStatus.OK) } + data class PF(val common: CommonFilters, val poFilters: POFilters) fun getAll(ctx :Context){ - val filters = ctx.bodyAsClass() - val poFilters :POFilters? = filters.custom as? POFilters - val pos = searchPos(filters.common, poFilters) - ctx.json(pos) + val filters = ctx.bodyAsClass() + val pos = searchPos(filters.common, filters.poFilters) + ctx.json(pos).status(HttpStatus.OK) } fun create(ctx :Context){ val po = ctx.bodyAsClass() database.save(po) - ctx.result("po created") + ctx.json(po).status(HttpStatus.CREATED) + } + fun createBatch(ctx :Context){ + val pos = ctx.bodyAsClass>() + val txn = database.beginTransaction() + try { + txn.isBatchMode = true + for(po in pos) database.save(po) + txn.commit() + ctx.status(HttpStatus.CREATED).result("POS Created") + } catch(e :Exception){ + txn.rollback() + ctx.status(HttpStatus.INTERNAL_SERVER_ERROR).result("Pos Creation failed" + e.message) + } finally { + txn.end() + } + ctx.result("pos batch created").status(HttpStatus.CREATED) } fun approve(ctx :Context){ val id = ctx.pathParam("id") val po = database.find(PurchaseOrder::class.java, id) ?: throw NotFoundResponse("po not found for $id") po.approvalStatus = ApprovalStatus.APPROVED po.save() - ctx.result("po with id $id approved") + ctx.json(po).status(HttpStatus.CREATED) //reject all other pos pertaining to the same tx ?? } fun reject(ctx :Context){ @@ -405,7 +421,7 @@ object PurchaseOrder { val po = database.find(PurchaseOrder::class.java, id) ?: throw NotFoundResponse("po not found for $id") po.approvalStatus = ApprovalStatus.REJECTED po.save() - ctx.result("po with id $id rejected") + ctx.json(po).status(HttpStatus.CREATED) } fun quoteReference(ctx :Context){ //gets the quote reference on which this po is based on @@ -457,12 +473,20 @@ object ProductCtrl { } } -object Quotation { +object QuotationCtrl { fun get(ctx :Context){ val id = ctx.pathParam("id") val quote = database.find(Quotation::class.java, id) ?: throw NotFoundResponse("quote not found for $id") + ctx.status(HttpStatus.OK) ctx.json(quote) } + data class QF(val common: CommonFilters, val quoteFilters: QuoteFilters) + fun getAll(ctx :Context){ + val filters = ctx.bodyAsClass() + val quotes = searchQuotes(filters.common, filters.quoteFilters) + ctx.json(quotes).status(HttpStatus.OK) + } + fun create(ctx :Context){ val quote = ctx.bodyAsClass() //we have to check if the quotation created date is below the expiry of rfq @@ -475,25 +499,44 @@ object Quotation { if(quote.quoteDate!! <= rfq.openTill) { //valid database.save(quote) - ctx.result("quote created") + ctx.status(HttpStatus.CREATED) + ctx.json(quote) }else { + ctx.status(HttpStatus.BAD_REQUEST) ctx.result("request for quote closed") } }else { throw NotFoundResponse("request for quote not found for this quotation") } + + } + fun createBatch(ctx :Context){ + val quotes = ctx.bodyAsClass>() + val txn = database.beginTransaction() + try { + txn.isBatchMode = true + for(quote in quotes) database.save(quote) + txn.commit() + ctx.status(HttpStatus.CREATED).result("Quotes Created") + } catch(e :Exception){ + txn.rollback() + ctx.status(HttpStatus.INTERNAL_SERVER_ERROR).result("Quotes Creation failed" + e.message) + } finally { + txn.end() + } + ctx.result("Quotes batch created").status(HttpStatus.CREATED) } fun delete(ctx :Context){ val id = ctx.pathParam("id") val quote = database.find(Quotation::class.java, id) ?: throw NotFoundResponse("quote not found for id $id") quote.delete() + ctx.status(HttpStatus.OK) ctx.result("quote with $id deleted") } fun generatePO(ctx :Context){ //user should be redirected to a po form submission with prefilled values //create a PO object with values from the quote and then send it as body to vendor/po/create ?? - } fun reqForQuote(ctx :Context){ val reqForQuoteNum = ctx.pathParam(("rfqNum")) @@ -501,6 +544,7 @@ object Quotation { .where() .eq("reqForQuoteNum", reqForQuoteNum) ?: throw NotFoundResponse("request for quote not found for this quotation") + ctx.status(HttpStatus.OK) ctx.json(rfq) } } @@ -508,12 +552,14 @@ object Document { fun get(ctx :Context){ val id = ctx.pathParam("id") val doc = database.find(Document::class.java, id) ?: throw NotFoundResponse("no doc found with id $id") + ctx.status(HttpStatus.OK) ctx.json(doc) } fun create(ctx :Context){ val doc = ctx.bodyAsClass() database.save(doc) - ctx.result("doc created") + ctx.status(HttpStatus.CREATED) + ctx.json(doc) } fun print(ctx :Context){ //would be handled in the frontend ?? @@ -522,7 +568,8 @@ object Document { val id = ctx.pathParam("id") val doc = database.find(Document::class.java, id) ?: throw NotFoundResponse("no doc found with id $id") //doc.delete() - ctx.result("document deleted") + ctx.status(HttpStatus.OK) + ctx.result("document deleted with id $id") } fun getWithRefId(ctx :Context){ //fetches a particular doc (po, quote) with ref id @@ -531,20 +578,47 @@ object Document { .where() .eq("refId", refId) ?: throw NotFoundResponse("no doc found for refId $refId") + ctx.status(HttpStatus.OK) ctx.json(doc) } } -object Vendor { +object VendorCtrl { fun get(ctx :Context){ val id = ctx.pathParam("id") val vendor = database.find(Vendor::class.java, id) ?: throw NotFoundResponse("no vendor found with id $id") + ctx.status(HttpStatus.OK) ctx.json(vendor) } - fun create(ctx :Context){ + data class VF(val common : CommonFilters, val vendorFilters: VendorFilters) + fun getAll(ctx :Context){ + val filters = ctx.bodyAsClass() + println(filters.common) + println(filters.vendorFilters) + val pos = searchVendors(filters.common, filters.vendorFilters) + ctx.status(HttpStatus.OK) + ctx.json(pos) + } + fun createBatch(ctx: Context){ + val vendors = ctx.bodyAsClass>() + val txn = database.beginTransaction() + try { + txn.isBatchMode = true + for(v in vendors) database.save(v) + txn.commit() + ctx.status(HttpStatus.CREATED).result("Vendors Created") + } catch(e :Exception){ + txn.rollback() + ctx.status(HttpStatus.INTERNAL_SERVER_ERROR).result("Vendor Creation failed" + e.message) + } finally { + txn.end() + } + } + fun create(ctx :Context){ val vendor = ctx.bodyAsClass() database.save(vendor) - ctx.result("vendor created") + ctx.status(HttpStatus.CREATED) + ctx.json(vendor) } fun update(ctx :Context){ @@ -558,6 +632,7 @@ object Vendor { .where() .eq("vendor", id) .findList() + ctx.status(HttpStatus.OK) ctx.json(quotes) } fun getPos(ctx :Context){ @@ -566,6 +641,7 @@ object Vendor { .where() .eq("vendor", id) .findList() + ctx.status(HttpStatus.OK) ctx.json(pos) } fun rate(ctx :Context){ @@ -575,24 +651,29 @@ object Vendor { //could place some rating validation checks vendor.rating = rating vendor.save() + ctx.status(HttpStatus.OK) ctx.result("rating changed") } } object RequestForQuote { - fun create(ctx :Context) { + fun create(ctx: Context) { val rfq = ctx.bodyAsClass() database.save(rfq) //ctx.result("request for quote created") //ctx.json(rfq) - //ctx.status(HttpStatus.CREATED) - //ctx.json("asss") + ctx.status(HttpStatus.CREATED) + ctx.json("asss") } - fun get(ctx :Context){ + + fun get(ctx: Context) { val id = ctx.pathParam("id") - val rfq = database.find(ReqForQuote::class.java, id) ?: throw NotFoundResponse("request for quote not found for id $id") + val rfq = database.find(ReqForQuote::class.java, id) + ?: throw NotFoundResponse("request for quote not found for id $id") + ctx.status(HttpStatus.OK) ctx.json(rfq) } - fun update(ctx :Context){ + + fun update(ctx: Context) { //shuld we compare the new body fields with preexisting ones and prepare a sql query to update those fields?? } diff --git a/src/main/kotlin/com/restapi/controllers/Excel.kt b/src/main/kotlin/com/restapi/controllers/Excel.kt index 3f9b910..f157c23 100644 --- a/src/main/kotlin/com/restapi/controllers/Excel.kt +++ b/src/main/kotlin/com/restapi/controllers/Excel.kt @@ -4,29 +4,39 @@ import com.restapi.domain.* import com.restapi.domain.Document import com.restapi.domain.PurchaseOrder import com.restapi.domain.Quotation +import com.restapi.domain.Session.currentUser import org.apache.poi.hssf.usermodel.HSSFSheet import org.apache.poi.hssf.usermodel.HSSFWorkbook import com.restapi.domain.Session.database import com.restapi.domain.Vendor +import org.apache.poi.hssf.usermodel.DVConstraint +import org.apache.poi.hssf.usermodel.HSSFDataValidation import org.apache.poi.ss.usermodel.Cell import org.apache.poi.ss.usermodel.CellType import org.apache.poi.ss.usermodel.DateUtil import org.apache.poi.ss.usermodel.Workbook import org.apache.poi.ss.usermodel.WorkbookFactory +import org.apache.poi.ss.util.CellRangeAddressList import java.io.File import java.io.FileInputStream - - import java.io.FileOutputStream import java.text.SimpleDateFormat import java.time.LocalDate import java.time.ZoneId import java.util.* -fun createHeaderRow(cols :List, sh :HSSFSheet) { +fun createHeaderRow(cols :List, sh :HSSFSheet, wb: Workbook) { + val boldFont = wb.createFont() + boldFont.bold = true + val style = wb.createCellStyle() + style.setFont(boldFont) + style.locked = true + sh.createRow(0).apply { cols.forEachIndexed{index, value -> - createCell(index).setCellValue(value) + val cell = createCell(index) + cell.setCellValue(value) + cell.setCellStyle(style) } } } @@ -68,19 +78,7 @@ fun doubleFromCellHelper(cell: Cell): Double { } return double?:0.0 } -fun enumFromCellHelper(cell: Cell, enumFor: EnumFor) :String{ - var string = "" - val cellValue = cell.stringCellValue - when(enumFor){ - EnumFor.UOM -> { - string = "uom" - } - EnumFor.DocType -> { - string = "doc" - } - } - return string -} + fun longIntFromCellHelper(cell : Cell) :Long { val long = when(cell.cellType){ CellType.NUMERIC -> cell.numericCellValue.toLong() @@ -95,12 +93,59 @@ enum class FileType { enum class EnumFor { UOM, DocType } +fun saveExcelFileLocally(fileName :String, wb: Workbook){ + val out = FileOutputStream(fileName) + wb.use { + it.write(out) + } + out.close() +} +fun TemplateExcelFile(fileType: FileType){ + when(fileType){ + FileType.QUOTES -> { + val headers : List = listOf("Quotation Number", "Date", "Open Till", "Product Id", "Product Name", "Product Unit Price", "Quantity", "Vendor Name", "Vendor Address", "RFQ Number", "Total Amount", "Terms and Conditions") + val wb = HSSFWorkbook() + val sh = wb.createSheet() + createHeaderRow(headers, sh, wb) + saveExcelFileLocally("Quotes_Template.xls", wb) + } + FileType.POS -> { + val headers : List = listOf("Number", "Date", "Open Till", "Reference Quotation Number", "Vendor Name", "Vendor Address", "Product Id", "Product Name", "Unit Price", "Quantity", "Total Amount", "Terms and Conditions") + val wb = HSSFWorkbook() + val sh = wb.createSheet() + createHeaderRow(headers, sh, wb) + saveExcelFileLocally("Purchase_Order_Template.xls", wb) + } + FileType.VENDORS -> { + val headers : List = listOf("Name", "MSME", "GST Number", "Address", "Rating", "Contact Name", "Contact Email", "Contact Mobile") + val wb = HSSFWorkbook() + val sh = wb.createSheet() + createHeaderRow(headers, sh, wb) + saveExcelFileLocally("Vendors_Template.xls", wb) + } + FileType.PRODS -> { + val headers : List = listOf("Id", "Name", "Description", "HSN Code", "UOM") + val wb = HSSFWorkbook() + val sh = wb.createSheet() + createHeaderRow(headers, sh, wb) + val r0 = CellRangeAddressList(0, 1000, 4, 4) + val dv0 = HSSFDataValidation(r0, DVConstraint.createExplicitListConstraint(arrayOf("LTR", "MTR", "NOS", "ALL"))).apply { + suppressDropDownArrow = true + } + sh.addValidationData(dv0) + saveExcelFileLocally("Products_Template.xls", wb) + } + FileType.DOCS -> { + + } + } +} fun ExportQuotations(quotes :List) { val wb = HSSFWorkbook() val sh = wb.createSheet() val headers : List = listOf("Quotation Number", "Date", "Open Till", "Product Id", "Product Name", "Product Unit Price", "Quantity", "Vendor Name", "Vendor Address", "RFQ Number", "Total AMount", "Terms and Conditions") - createHeaderRow(headers, sh) + createHeaderRow(headers, sh, wb) val totalCols = headers.size var rowCnt = 1 for(quote in quotes){ @@ -134,7 +179,7 @@ fun ExportVendors(vendors :List){ val sh = wb.createSheet() val headers : List = listOf("Name", "MSME", "GST Number", "Address", "Rating", "Contact Name", "Contact Email", "Contact Mobile") - createHeaderRow(headers, sh) + createHeaderRow(headers, sh, wb) val totalCols = headers.size var rowCnt = 1 @@ -160,7 +205,7 @@ fun ExportProds(prods :List){ val sh = wb.createSheet() val headers : List = listOf("Id", "Name", "Description", "HSN Code", "UOM") - createHeaderRow(headers, sh) + createHeaderRow(headers, sh, wb) val totalCols = headers.size var rowCnt = 1 @@ -180,7 +225,7 @@ fun ExportPos(pos :List){ val sh = wb.createSheet() val headers : List = listOf("Number", "Date", "Open Till", "Reference Quotation Number", "Vendor Name", "Vendor Address", "Product Id", "Product Name", "Unit Price", "Quantity", "Total Amount", "Terms and Conditions") - createHeaderRow(headers, sh) + createHeaderRow(headers, sh, wb) val totalCols = headers.size var rowCnt = 1 @@ -204,12 +249,13 @@ fun ExportPos(pos :List){ row.createCell(i++).setCellValue(po.products[j].quantity) row.createCell(i++).setCellValue(po.totalAmount) - row.createCell(i++).setCellValue(po.tnc.joinToString(";")) + row.createCell(i++).setCellValue(po.tnc?.joinToString(";")) } } } fun main() { - ImportFromExcel(FileType.QUOTES, "C:\\Users\\arsalan\\Downloads\\Book.xlsx") + //ImportFromExcel(FileType.QUOTES, "C:\\Users\\arsalan\\Downloads\\Book.xlsx") + TemplateExcelFile(FileType.PRODS) } fun ImportFromExcel(fileType: FileType, filePath : String) { @@ -218,7 +264,7 @@ fun ImportFromExcel(fileType: FileType, filePath : String) { when(fileType){ FileType.QUOTES -> { - //Quote Number, ProductName, Product Quantity, Product Uom, Total Amount, RFQ Number, Quote Date, Valid Till, TNC[], Documents[] + //Quote Number, ProductName, Product Quantity, Total Amount, RFQ Number, Quote Date, Valid Till, TNC[], Documents[] val quotesMap : MutableMap = mutableMapOf() val quotesList : List = mutableListOf() sh.rowIterator().forEach { row -> @@ -226,8 +272,8 @@ fun ImportFromExcel(fileType: FileType, filePath : String) { //reached eof return@forEach } - val quoteNum = stringFromCellHelper(row.getCell(0)) - val quoteDate = dateFromCellHelper(row.getCell(1)) + val quoteNumber = stringFromCellHelper(row.getCell(0)) + val quoteDt = dateFromCellHelper(row.getCell(1)) val rfqNum = stringFromCellHelper(row.getCell(2)) val quoteValidTill = dateFromCellHelper(row.getCell(3)) val vendorName = stringFromCellHelper(row.getCell(4)) @@ -236,35 +282,38 @@ fun ImportFromExcel(fileType: FileType, filePath : String) { val prodName = stringFromCellHelper(row.getCell(7)) val prodQuantity = doubleFromCellHelper(row.getCell(8)) val prodUnitPrice = doubleFromCellHelper(row.getCell(9)) - val prodUom = enumFromCellHelper(row.getCell(10), EnumFor.UOM) - val totalQuoteAmount = doubleFromCellHelper(row.getCell(11)) + val totalQuoteAmount = doubleFromCellHelper(row.getCell(10)) val prod = POProducts("", prodName, prodUnitPrice, prodQuantity) - if (quotesMap.containsKey(quoteNum)) { + if (quotesMap.containsKey(quoteNumber)) { //duplicated row - quotesMap.get(quoteNum)?.products?.add(prod) + quotesMap.get(quoteNumber)?.products?.add(prod) }else { - val vendor = Vendor() - vendor.name = vendorName - vendor.address = vendorAddress - vendor.gstNumber = vendorGstNum + val v = Vendor() + v.apply { + name = vendorName + address = vendorAddress + gstNumber = vendorGstNum + } val quote = Quotation() - quote.quoteNum = quoteNum - quote.quoteDate = quoteDate - quote.reqForQuoteNum = rfqNum - quote.validTill = quoteValidTill - quote.products = mutableListOf(prod) - quote.vendor = vendor - quote.totalAmount = totalQuoteAmount - quotesMap.put(quoteNum, quote) + quote.apply { + quoteNum = quoteNumber + quoteDate = quoteDt + reqForQuoteNum = rfqNum + validTill = quoteValidTill + products = mutableListOf(prod) + vendor = v + totalAmount = totalQuoteAmount + } + quotesMap.put(quoteNumber, quote) } } //docs, tncs // println("$quotesMap") - quotesMap.forEach { (k, v) -> - println("$v") - } +// quotesMap.forEach { (k, v) -> +// println("$v") +// } } FileType.POS -> { //poNum, poDate, validTill, refQuoteNum, prodName, prodQuantity, totalAmount, products, vendorName, vendorGst, vendorAddress, tnc[]. docs[] diff --git a/src/main/kotlin/com/restapi/controllers/Filters.kt b/src/main/kotlin/com/restapi/controllers/Filters.kt index 411344e..40c6352 100644 --- a/src/main/kotlin/com/restapi/controllers/Filters.kt +++ b/src/main/kotlin/com/restapi/controllers/Filters.kt @@ -1,17 +1,14 @@ package com.restapi.controllers -import com.restapi.domain.DocType -import com.restapi.domain.PurchaseOrder +import com.restapi.domain.* import com.restapi.domain.Quotation -import com.restapi.domain.ReqForQuote -import com.restapi.domain.UOM -import java.time.LocalDate import com.restapi.domain.Session.database +import java.time.LocalDate //constants const val IGNORE = "%" -val baseDate :LocalDate = LocalDate.MIN -val maxDate :LocalDate = LocalDate.MAX +val baseDate :LocalDate = LocalDate.of(1500, 1,1, ) +val maxDate :LocalDate = LocalDate.of(3000, 1 ,1) const val RATING_MAX = 10.0 const val RATING_MIN = 0.0 //common filters would be used by most of the handlers @@ -50,8 +47,8 @@ data class RFQFilters ( ) data class QuoteFilters ( val quoteNumLike :String = IGNORE, - val validBefore :LocalDate = baseDate, - val validAfter :LocalDate = maxDate, + val validBefore :LocalDate = maxDate, + val validAfter :LocalDate = baseDate, val totalAmountExceeds :Long = Long.MIN_VALUE, val totalAmountLessThan :Long = Long.MAX_VALUE, ) :CustomFilters @@ -91,12 +88,8 @@ fun searchQuotes(commonFilters: CommonFilters, quoteFilters: QuoteFilters) : Lis .le("validTill", quoteFilters.validBefore) .ge("totalAmount", quoteFilters.totalAmountExceeds) .le("totalAmount", quoteFilters.totalAmountLessThan) - .ilike("quoteNum", quoteFilters.quoteNumLike ) - .apply { - if(!commonFilters.vendor?.isEmpty()!!){ - commonFilters.vendor.let { this.`in`("vendor", it) } - } - } + .ilike("quoteNum", "%" + quoteFilters.quoteNumLike + "%") + applyVendorHelper(q, commonFilters.vendor) applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) return q.findList() } @@ -105,10 +98,10 @@ fun searchVendors(commonFilters: CommonFilters, vendorFilters: VendorFilters) : .where() .ge("rating", vendorFilters.ratingExceeds) .le("rating", vendorFilters.ratingLessThan) - .ilike("name", vendorFilters.nameLike) - .ilike("msme", vendorFilters.msmeLike) - .ilike("gstNum", vendorFilters.gstNumLike) - .ilike("address", vendorFilters.addressLike) + .ilike("name", "%" + vendorFilters.nameLike + "%") + .ilike("msme", "%" + vendorFilters.msmeLike + "%") + .ilike("gstNumber", "%" + vendorFilters.gstNumLike + "%") + .ilike("address", "%" + vendorFilters.addressLike + "%") applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) return q.findList() } @@ -120,7 +113,7 @@ fun searchDocs(commonFilters: CommonFilters, documentFilters: DocumentFilters) : this.eq("docType", documentFilters.typeOfDoc) } } - .ilike("name", documentFilters.nameLike ) + .ilike("name", "%" + documentFilters.nameLike + "%") applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) applyVendorHelper(q, commonFilters.vendor) return q.findList() @@ -133,8 +126,8 @@ fun searchPos(commonFilters: CommonFilters, poFilters: POFilters?) : List() { } } -data class ContactPerson(val name: String, val email: String, val mobile: String) +data class ContactPerson(val name: String = "", val email: String = "", val mobile: String = "") @Entity open class Vendor :BaseTenantModel() { var name :String = "" @@ -260,15 +260,15 @@ open class PurchaseOrder :BaseTenantModel() { var products :MutableList = mutableListOf() @ManyToOne var vendor :Vendor? = null - var referenceQuotation :String = "" + var referenceQuotation :String? = "" var totalAmount :Double = 0.0 var poNum: String = "" var poDate: LocalDate? = null var validTill: LocalDate? = null @DbArray - var tnc: List = arrayListOf() + var tnc: List? = arrayListOf() @DbArray - var documents: MutableList = arrayListOf() + var documents: MutableList? = arrayListOf() } enum class UOM { @@ -292,7 +292,7 @@ open class Quotation :BaseTenantModel() { var vendor :Vendor? = null var totalAmount :Double = 0.0 - var reqForQuoteNum: String = "" + var reqForQuoteNum: String? = "" var quoteNum: String = "" var quoteDate: LocalDate? = null var validTill: LocalDate? = null diff --git a/src/main/resources/dbmigration/1.3.sql b/src/main/resources/dbmigration/1.3.sql new file mode 100644 index 0000000..f4f1f53 --- /dev/null +++ b/src/main/resources/dbmigration/1.3.sql @@ -0,0 +1,43 @@ +-- drop dependencies +alter table document drop constraint if exists ck_document_type_of_doc; +alter table product drop constraint if exists ck_product_uom; +-- apply changes +create table req_for_quote ( + sys_pk bigint generated by default as identity not null, + deleted_on timestamp, + current_approval_level integer default 0 not null, + required_approval_levels integer default 0 not null, + potential_vendors bigint[], + open_till date, + deleted boolean default false not null, + version integer default 1 not null, + created_at timestamp default 'now()' not null, + modified_at timestamp default 'now()' not null, + deleted_by varchar(255), + approval_status varchar(8) default 'APPROVED' not null, + tags varchar[] default '{}' not null, + comments jsonb default '[]' not null, + tenant_id varchar(255) not null, + status varchar(9), + products jsonb, + req_for_quote_num varchar(255), + created_by varchar(255) not null, + modified_by varchar(255) not null, + constraint ck_req_for_quote_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')), + constraint ck_req_for_quote_status check ( status in ('DELIVERED','PO','QUOTE','CANCELLED')), + constraint pk_req_for_quote primary key (sys_pk) +); + +-- apply alter tables +alter table document alter column type_of_doc type varchar(7) using type_of_doc::varchar(7); +alter table document alter column type_of_doc drop not null; +alter table document add column if not exists ref_id varchar(255); +alter table document add column if not exists doc_date date; +alter table product add column if not exists id bigint; +alter table purchase_order alter column reference_quotation drop not null; +alter table purchase_order alter column total_amount type float using total_amount::float; +alter table quotation alter column total_amount type float using total_amount::float; +alter table quotation add column if not exists req_for_quote_num varchar(255); +-- apply post alter +alter table document add constraint ck_document_type_of_doc check ( type_of_doc in ('PO','QUOTE','INVOICE','ALL')); +alter table product add constraint ck_product_uom check ( uom in ('NOS','LTR','MTR','ALL')); diff --git a/src/main/resources/dbmigration/model/1.3.model.xml b/src/main/resources/dbmigration/model/1.3.model.xml new file mode 100644 index 0000000..7461c11 --- /dev/null +++ b/src/main/resources/dbmigration/model/1.3.model.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From b5c28dd288ceed5383dd87b8a28f277a6cdda5b9 Mon Sep 17 00:00:00 2001 From: arsalan Date: Wed, 24 Jan 2024 17:40:20 +0530 Subject: [PATCH 4/4] add fixes --- api.http | 1 - src/main/kotlin/com/restapi/Main.kt | 2 +- src/main/kotlin/com/restapi/controllers/Entities.kt | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api.http b/api.http index b33c0ae..036e066 100644 --- a/api.http +++ b/api.http @@ -141,7 +141,6 @@ Authorization: {{auth-token}} { "name": "arsalan", "msme": "1234", - "hsnCode": "1234", "gstNumber": "GST123", "address": "Bangalore", "rating": 2, diff --git a/src/main/kotlin/com/restapi/Main.kt b/src/main/kotlin/com/restapi/Main.kt index 3b279da..135cbde 100644 --- a/src/main/kotlin/com/restapi/Main.kt +++ b/src/main/kotlin/com/restapi/Main.kt @@ -139,7 +139,7 @@ fun main(args: Array) { delete("/{id}", QuotationCtrl::delete, Roles(Role.Explicit(listOf("ROLE_QUOTE_CREATE", "ROLE_ADMIN")))) } path("/product"){ - post("", ProductCtrl::create, Roles(Role.Explicit(listOf("ROLE_PRODUCT_CREATE", "ROLE_ADMIN")))) + post("", ProductCtrl::create, Roles(Role.Explicit(listOf("ROLE_PRODUCT_CREATE", "ROLE_ADMIN", "ROLE_VENDOR_CREATE")))) //get("/{hsnCode}", ProductCtrl::get, Roles(Role.Explicit(listOf("ROLE_PRODUCT_VIEW", "ROLE_ADMIN")))) put("/{id}", ProductCtrl::update, Roles(Role.Explicit(listOf("ROLE_PRODUCT_UPDATE", "ROLE_ADMIN")))) //patch("/{id}", ProductCtrl::patch, Roles(Role.Explicit(listOf("ROLE_PRODUCT_UPDATE", "ROLE_ADMIN")))) diff --git a/src/main/kotlin/com/restapi/controllers/Entities.kt b/src/main/kotlin/com/restapi/controllers/Entities.kt index d1772ab..d6d6ff2 100644 --- a/src/main/kotlin/com/restapi/controllers/Entities.kt +++ b/src/main/kotlin/com/restapi/controllers/Entities.kt @@ -456,6 +456,7 @@ object ProductCtrl { fun create(ctx :Context){ val product = ctx.bodyAsClass() database.save(product) + ctx.json(product).status(HttpStatus.CREATED) } fun delete(ctx: Context) {