From 3cd5c4c4717688a4229975b58be366dd20a663c2 Mon Sep 17 00:00:00 2001 From: arsalan Date: Wed, 24 Jan 2024 10:01:25 +0530 Subject: [PATCH] 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