diff --git a/Products_Template.xls b/Products_Template.xls new file mode 100644 index 0000000..ca24ad1 Binary files /dev/null and b/Products_Template.xls differ diff --git a/api.http b/api.http index b0d12b8..72763d3 100644 --- a/api.http +++ b/api.http @@ -131,4 +131,231 @@ 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", + "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/build.gradle.kts b/build.gradle.kts index 821aee9..bdbc8a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation("org.apache.httpcomponents:httpclient:4.5.14") implementation("org.apache.poi:poi:5.2.3") implementation("org.apache.poi:poi-ooxml:5.2.3") + 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/Main.kt b/src/main/kotlin/com/restapi/Main.kt index b5525d5..cf14702 100644 --- a/src/main/kotlin/com/restapi/Main.kt +++ b/src/main/kotlin/com/restapi/Main.kt @@ -113,173 +113,84 @@ fun main(args: Array) { it.json(mapOf("status" to true)) } - 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")))) + path("/vendor"){ + path("/"){ + 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"))) - ) + path("/po"){ + 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"))) - ) + path("/quote"){ + 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")))) + path("/product"){ + 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"))) - ) + 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")))) - delete( - "/{id}", - ProductCtrl::delete, - Roles(Role.Explicit(listOf("ROLE_PRODUCT_DELETE", "ROLE_ADMIN"))) - ) + delete("/{id}", ProductCtrl::delete, Roles(Role.Explicit(listOf("ROLE_PRODUCT_DELETE", "ROLE_ADMIN")))) get("", ProductCtrl::getAll, Roles(Role.Explicit(listOf("ROLE_PRODUCT_VIEW", "ROLE_ADMIN")))) - post("/product-excel", ProductCtrl::prodExcel) - get("/product-import") { ctx -> //ctx.json(ExcelRead())} - val fileItem = ctx.uploadedFiles("file") - if (fileItem != null) { - ctx.result("Data imported successfully!") - } else { - ctx.result("No file uploaded") - } - } } - path("/doc") { - post("", Document::create, Roles(Role.Explicit(listOf("ROLE_DOC_CREATE", "ROLE_ADMIN")))) - //why type and refid are clubbed ?? - get( - "/{type}/{refId}", - Document::getWithRefId, - Roles(Role.Explicit(listOf("ROLE_DOC_VIEW", "ROLE_ADMIN", "ROLE_PRODUCT_CREATE"))) - ) - get( - "/{id}", - Document::get, - Roles(Role.Explicit(listOf("ROLE_DOC_VIEW", "ROLE_ADMIN", "ROLE_PRODUCT_CREATE"))) - ) - get( - "/print/{id}", - Document::print, - Roles(Role.Explicit(listOf("ROLE_DOC_CREATE", "ROLE_DOC_VIEW"))) - ) - delete("/{id}", Document::delete, Roles(Role.Explicit(listOf("ROLE_DOC_CREATE")))) - } - path("/reqForQuote") { - post("", RequestForQuote::create, Roles(Role.Explicit(listOf("ROLE_RFQ_CREATE")))) - get( - "/{id}", - RequestForQuote::get, - Roles(Role.Explicit(listOf("ROLE_RFQ_CREATE", "ROLE_RFQ_VIEW"))) - ) - put("/{id}", RequestForQuote::update, Roles(Role.Explicit(listOf("ROLE_RFQ_CREATE")))) - } + path("/doc"){ + post("", Document::create, Roles(Role.Explicit(listOf("ROLE_DOC_CREATE", "ROLE_ADMIN")))) + //why type and refid are clubbed ?? + get("/{type}/{refId}", Document::getWithRefId, Roles(Role.Explicit(listOf("ROLE_DOC_VIEW", "ROLE_ADMIN", "ROLE_PRODUCT_CREATE")))) + get("/{id}", Document::get, Roles(Role.Explicit(listOf("ROLE_DOC_VIEW", "ROLE_ADMIN", "ROLE_PRODUCT_CREATE")))) + get("/print/{id}", Document::print, Roles(Role.Explicit(listOf("ROLE_DOC_CREATE", "ROLE_DOC_VIEW")))) + delete("/{id}", Document::delete, Roles(Role.Explicit(listOf("ROLE_DOC_CREATE")))) + } + path("/reqForQuote"){ + post("", RequestForQuote::create, Roles(Role.Explicit(listOf("ROLE_RFQ_CREATE")))) + get("/{id}", RequestForQuote::get, Roles(Role.Explicit(listOf("ROLE_RFQ_CREATE", "ROLE_RFQ_VIEW")))) + put("/{id}", RequestForQuote::update, Roles(Role.Explicit(listOf("ROLE_RFQ_CREATE")))) } - post("/script/database/{name}", Entities::executeStoredProcedure, Roles(adminRole, Role.DbOps)) - post("/script/{file}/{name}", Entities::executeScript, Roles(adminRole, Role.DbOps)) - - get("/{entity}/{id}", Entities::view, Roles(adminRole, viewRole)) - post("/{entity}/query/{id}", Entities::sqlQueryById, Roles(adminRole, viewRole)) - post("/{entity}/query", Entities::sqlQueryRaw, Roles(adminRole, viewRole)) - post("/{entity}", Entities::create, Roles(adminRole, createRole)) - - put("/{entity}/approve/{id}", Entities::approve, Roles(adminRole, approveOrRejectRole)) - put("/{entity}/reject/{id}", Entities::reject, Roles(adminRole, approveOrRejectRole)) - put("/{entity}/{action}/{id}", Entities::action, Roles(adminRole, Role.Entity)) - - put("/{entity}/{id}", Entities::update, Roles(adminRole, updateRole)) - patch("/{entity}/{id}", Entities::patch, Roles(adminRole, updateRole)) - delete("/{entity}/{id}", Entities::delete, Roles(adminRole, Role.Standard(Action.DELETE))) } + post("/script/database/{name}", Entities::executeStoredProcedure, Roles(adminRole, Role.DbOps)) + post("/script/{file}/{name}", Entities::executeScript, Roles(adminRole, Role.DbOps)) + + get("/{entity}/{id}", Entities::view, Roles(adminRole, viewRole)) + post("/{entity}/query/{id}", Entities::sqlQueryById, Roles(adminRole, viewRole)) + post("/{entity}/query", Entities::sqlQueryRaw, Roles(adminRole, viewRole)) + post("/{entity}", Entities::create, Roles(adminRole, createRole)) + + put("/{entity}/approve/{id}", Entities::approve, Roles(adminRole, approveOrRejectRole)) + put("/{entity}/reject/{id}", Entities::reject, Roles(adminRole, approveOrRejectRole)) + put("/{entity}/{action}/{id}", Entities::action, Roles(adminRole, Role.Entity)) + + put("/{entity}/{id}", Entities::update, Roles(adminRole, updateRole)) + patch("/{entity}/{id}", Entities::patch, Roles(adminRole, updateRole)) + delete("/{entity}/{id}", Entities::delete, Roles(adminRole, Role.Standard(Action.DELETE))) } - .exception(DuplicateKeyException::class.java, Exceptions.dupKeyExceptionHandler) - .exception(DataIntegrityException::class.java, Exceptions.dataIntegrityException) - .exception(DataNotFoundException::class.java, Exceptions.dataNotFoundException) - .exception(IllegalArgumentException::class.java, Exceptions.illegalArgumentException) - .exception(JsonMappingException::class.java, Exceptions.jsonMappingException) - .exception(InvalidJwtException::class.java, Exceptions.invalidJwtException) - .start(appConfig.portNumber()) } + .exception(DuplicateKeyException::class.java, Exceptions.dupKeyExceptionHandler) + .exception(DataIntegrityException::class.java, Exceptions.dataIntegrityException) + .exception(DataNotFoundException::class.java, Exceptions.dataNotFoundException) + .exception(IllegalArgumentException::class.java, Exceptions.illegalArgumentException) + .exception(JsonMappingException::class.java, Exceptions.jsonMappingException) + .exception(InvalidJwtException::class.java, Exceptions.invalidJwtException) + .start(appConfig.portNumber()) +} - private fun Context.getAuthHeader() = header("Authorization") - ?.replace("Bearer ", "") - ?.replace("Bearer: ", "") - ?.trim() +private fun Context.getAuthHeader() = header("Authorization") + ?.replace("Bearer ", "") + ?.replace("Bearer: ", "") + ?.trim() diff --git a/src/main/kotlin/com/restapi/controllers/Entities.kt b/src/main/kotlin/com/restapi/controllers/Entities.kt index 1994478..25aa06c 100644 --- a/src/main/kotlin/com/restapi/controllers/Entities.kt +++ b/src/main/kotlin/com/restapi/controllers/Entities.kt @@ -377,25 +377,47 @@ object Entities { false } } - -object PurchaseOrder { +data class Filters(val common :CommonFilters, val custom :CustomFilters) +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 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){ @@ -403,7 +425,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 @@ -437,6 +459,7 @@ object ProductCtrl { fun create(ctx :Context){ val product = ctx.bodyAsClass() database.save(product) + ctx.json(product).status(HttpStatus.CREATED) } fun delete(ctx: Context) { @@ -486,12 +509,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 @@ -504,25 +535,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")) @@ -530,6 +580,7 @@ object Quotation { .where() .eq("reqForQuoteNum", reqForQuoteNum) ?: throw NotFoundResponse("request for quote not found for this quotation") + ctx.status(HttpStatus.OK) ctx.json(rfq) } } @@ -537,12 +588,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 ?? @@ -551,7 +604,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 @@ -560,20 +614,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){ @@ -587,6 +668,7 @@ object Vendor { .where() .eq("vendor", id) .findList() + ctx.status(HttpStatus.OK) ctx.json(quotes) } fun getPos(ctx :Context){ @@ -595,6 +677,7 @@ object Vendor { .where() .eq("vendor", id) .findList() + ctx.status(HttpStatus.OK) ctx.json(pos) } fun rate(ctx :Context){ @@ -604,24 +687,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 89bce83..039bcc4 100644 --- a/src/main/kotlin/com/restapi/controllers/Excel.kt +++ b/src/main/kotlin/com/restapi/controllers/Excel.kt @@ -1,46 +1,424 @@ package com.restapi.controllers - import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.restapi.domain.Product +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 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.Row +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.xssf.usermodel.XSSFWorkbook -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream +import org.apache.poi.ss.util.CellRangeAddressList +import java.io.File import java.io.FileInputStream -import java.io.InputStream +import java.io.FileOutputStream +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.ZoneId +import java.util.* -fun CreateExcel(productList: List): InputStream { - val wb = XSSFWorkbook() - val sh = wb.createSheet() - val rows: Row = sh.createRow(0) - rows.createCell(0).setCellValue("Name") - rows.createCell(1).setCellValue("Description") - rows.createCell(2).setCellValue("HSN") - rows.createCell(3).setCellValue("UOM") - - var rowNum = 1 - for (product in productList) { - val row: Row = sh.createRow(rowNum++) +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 - row.createCell(0).setCellValue(product.name) - row.createCell(1).setCellValue(product.description) - row.createCell(2).setCellValue(product.hsnCode) - - val uomCell: Cell = row.createCell(3) - uomCell.setCellValue(product.uom?.name ?: "") + sh.createRow(0).apply { + cols.forEachIndexed{index, value -> + val cell = createCell(index) + cell.setCellValue(value) + cell.setCellStyle(style) + } } +} +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 +} - val baos = ByteArrayOutputStream() - wb.write(baos) - wb.close() +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 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 -> { - return ByteArrayInputStream(baos.toByteArray()) + } + } +} +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, wb) + 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, wb) + + 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, wb) + + 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, wb) + + 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") + TemplateExcelFile(FileType.PRODS) +} + +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, 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 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)) + 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 totalQuoteAmount = doubleFromCellHelper(row.getCell(10)) + val prod = POProducts("", prodName, prodUnitPrice, prodQuantity) + + if (quotesMap.containsKey(quoteNumber)) { + //duplicated row + quotesMap.get(quoteNumber)?.products?.add(prod) + }else { + val v = Vendor() + v.apply { + name = vendorName + address = vendorAddress + gstNumber = vendorGstNum + } + val quote = Quotation() + 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") +// } + } + 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 + } + } + } } data class validateExcel( diff --git a/src/main/kotlin/com/restapi/controllers/Filters.kt b/src/main/kotlin/com/restapi/controllers/Filters.kt index 0d89cf3..ec909b6 100644 --- a/src/main/kotlin/com/restapi/controllers/Filters.kt +++ b/src/main/kotlin/com/restapi/controllers/Filters.kt @@ -1,15 +1,14 @@ package com.restapi.controllers import com.restapi.domain.* -import com.restapi.domain.PurchaseOrder import com.restapi.domain.Quotation -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 @@ -21,6 +20,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, @@ -28,16 +28,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, @@ -45,11 +47,11 @@ 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 data class VendorFilters ( val nameLike :String = IGNORE, val msmeLike :String = IGNORE, @@ -57,7 +59,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 { @@ -69,6 +71,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) @@ -76,17 +82,14 @@ 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) - .apply { - if(!commonFilters.vendor?.isEmpty()!!){ - commonFilters.vendor.let { this.`in`("vendor", it) } - } - } + .le("totalAmount", quoteFilters.totalAmountLessThan) + .ilike("quoteNum", "%" + quoteFilters.quoteNumLike + "%") + applyVendorHelper(q, commonFilters.vendor) applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) return q.findList() } @@ -95,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() } @@ -110,17 +113,21 @@ 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() } -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) - .ilike("poNum", poFilters.poNumLike ) - .ilike("referenceQuotation", poFilters.refQuotation ) + .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) applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) return q.findList() @@ -128,8 +135,9 @@ fun searchPos(commonFilters: CommonFilters, poFilters: POFilters) : List { val q = database.find(ReqForQuote::class.java) .where() - .between("validTill", rfqFilters.validAfter, rfqFilters.validBefore) - .ilike("reqForQuoteNum", rfqFilters.reqForQuoteNumLike) + .ge("validTill", rfqFilters.validAfter) + .le("validTill", rfqFilters.validBefore) + .ilike("reqForQuoteNum", "%" + rfqFilters.reqForQuoteNumLike + "%") applyVendorHelper(q, commonFilters.vendor) applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) return q.findList() diff --git a/src/main/kotlin/com/restapi/domain/models.kt b/src/main/kotlin/com/restapi/domain/models.kt index 952489c..a78edb3 100644 --- a/src/main/kotlin/com/restapi/domain/models.kt +++ b/src/main/kotlin/com/restapi/domain/models.kt @@ -13,7 +13,9 @@ import java.util.* 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 } @@ -242,7 +244,7 @@ class SafeStringDeserializer : JsonDeserializer() { } } -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 = "" @@ -259,15 +261,15 @@ open class PurchaseOrder :BaseTenantModel() { var products :MutableList = mutableListOf() @ManyToOne var vendor :Vendor? = null - var referenceQuotation :String = "" - var totalAmount :Int = 0 + 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 { @@ -276,7 +278,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 = "" @@ -290,9 +292,9 @@ 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 reqForQuoteNum: String? = "" var quoteNum: String = "" var quoteDate: LocalDate? = null var validTill: LocalDate? = null @@ -311,9 +313,12 @@ 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 + var vendor :Vendor? = null } enum class RFQStatus{ 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