Compare commits

19 Commits

Author SHA1 Message Date
cfa3a7e289 Excel to Db 2024-01-24 17:27:37 +05:30
d23ac5261d Excel to Db 2024-01-24 11:25:18 +05:30
976aebec5b Product Excel Validation 2024-01-23 18:18:55 +05:30
a3f7614979 Product Excel Validation 2024-01-23 16:59:25 +05:30
8745db2127 Excel R/W 2024-01-22 15:12:58 +05:30
438daacdc6 ProductFilters 2024-01-19 17:47:18 +05:30
arsalan
dd55cd22cf Merge branch 'master' of https://git.basuvaraj.com/gowthaman/readymixerp_modules_api 2024-01-19 15:04:08 +05:30
arsalan
aa97275b9c add filters 2024-01-19 15:03:45 +05:30
arsalan
cfdb792aa5 add filters 2024-01-19 14:58:53 +05:30
cb184dee3b product 2024-01-19 14:54:17 +05:30
043cddcaa0 product 2024-01-19 10:42:50 +05:30
arsalan
20f6abf3b7 add endpoints 2024-01-19 09:51:58 +05:30
arsalan
f440ca89f3 add entities and db migrations 2024-01-17 18:36:44 +05:30
arsalan
1abf482a7a fix jwt aud 2024-01-16 17:37:23 +05:30
gowthaman.b
2b60e9cc29 fix deleted report 2024-01-16 16:19:27 +05:30
gowthaman.b
81afbdab49 add anon session 2024-01-05 12:17:16 +05:30
gowthaman.b
2be4df0b6d add more stuff 2024-01-05 12:15:53 +05:30
gowthaman.b
e09ff4ce2b add more stuff 2024-01-05 12:11:15 +05:30
gowthaman.b
d506078804 add more stuff 2024-01-05 12:08:27 +05:30
20 changed files with 1509 additions and 65 deletions

18
.idea/dataSources.xml generated
View File

@@ -6,6 +6,24 @@
<synchronize>true</synchronize> <synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver> <jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://192.168.64.6:5432/uiapp</jdbc-url> <jdbc-url>jdbc:postgresql://192.168.64.6:5432/uiapp</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.resource.type" value="Deployment" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="arsalan_rmc_module_app@10.10.10.211" uuid="572880af-561c-4a5a-90bd-e024c09c674b">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://10.10.10.211:5432/arsalan_rmc_module_app</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir> <working-dir>$ProjectFileDir$</working-dir>
</data-source> </data-source>
</component> </component>

9
.idea/misc.xml generated
View File

@@ -4,10 +4,13 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="NimToolchainService"> <component name="MavenProjectsManager">
<option name="rootPaths"> <option name="originalFiles">
<list /> <list>
<option value="$PROJECT_DIR$/../txn_workflow/pom.xml" />
</list>
</option> </option>
<option name="workspaceImportForciblyTurnedOn" value="true" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />

1
.idea/vcs.xml generated
View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../rmc-modules-app" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

View File

@@ -5,10 +5,22 @@ Authorization: {{auth-token}}
{ {
"data": { "data": {
"number": "KA01HD6667", "number": "KA01HD6677",
"owner": "gowthaman" "owner": "gowthaman"
}, },
"uniqueIdentifier": "KA01HD6667" "uniqueIdentifier": ""
}
### create row
POST http://localhost:9001/api/vendor/po
Content-Type: application/json
Authorization: {{auth-token}}
{
"data": {
"name": "arsalan",
"rating": 5
}
} }
### create row, with autogenerated identifier ### create row, with autogenerated identifier
@@ -23,14 +35,30 @@ Authorization: {{auth-token}}
} }
} }
### create row, with autogenerated identifier
POST http://localhost:9001/api/purchaseOrder
Content-Type: application/json
Authorization: {{auth-token}}
{
"vendor" : "arslan",
"products": ["chairs", "tables"]
}
### get row ### get row
GET http://localhost:9001/api/vehicle/TN38BA5009 GET http://localhost:9001/api/log/log-0000000001
Authorization: Bearer {{auth-token}}
### get row
GET http://localhost:9001/api/vehicle/KA01HD6667
Authorization: Bearer {{auth-token}} Authorization: Bearer {{auth-token}}
### query row ### query row
POST http://localhost:9001/api/vehicle/query POST http://localhost:9001/api/vehicle/query
Content-Type: application/json Content-Type: application/json
Authorization: set-auth-token Authorization: {{set-auth-token}}
{ {
"sql": "select sys_pk, tenant_id, deleted_on, deleted_by, deleted, version, created_at, modified_at, created_by, modified_by, data, tags, comments, unique_identifier, entity_name from data_model where data ->> 'number' = :number", "sql": "select sys_pk, tenant_id, deleted_on, deleted_by, deleted, version, created_at, modified_at, created_by, modified_by, data, tags, comments, unique_identifier, entity_name from data_model where data ->> 'number' = :number",
@@ -40,7 +68,7 @@ Authorization: set-auth-token
} }
### update field ### update field
PATCH http://localhost:9001/api/vehicle/KA01MU0556 PATCH http://localhost:9001/api/vehicle/KA01HD6667
Content-Type: application/json Content-Type: application/json
Authorization: {{auth-token}} Authorization: {{auth-token}}
@@ -51,9 +79,9 @@ Authorization: {{auth-token}}
### upate a row ### upate a row
PUT http://localhost:9001/api/vehicle/KA03HD6064 PUT http://localhost:9001/api/vehicle/KA01HD6667
Content-Type: application/json Content-Type: application/json
Authorization: set-auth-token Authorization: {{auth-token}}
{ {
"number": "KA03HD6064", "number": "KA03HD6064",
@@ -62,5 +90,53 @@ Authorization: set-auth-token
} }
### delete a row ### delete a row
DELETE http://localhost:9001/api/vehicle/KA01MU0556 DELETE http://localhost:9001/api/vehicle/KA01HD6667
Authorization: {{auth-token}}
### get products
GET http://localhost:9001/api/vendor/product
Authorization: {{auth-token}}
### get products by hsn code
GET http://localhost:9001/api/vendor/product/#123
Authorization: {{auth-token}}
### create excel for products
POST http://localhost:9001/api/vendor/product/product-excel
Authorization: {{auth-token}}
### excel read
GET http://localhost:9001/api/vendor/product/validation
Authorization: Bearer {{auth-token}}
### create product
POST http://localhost:9001/api/vendor/product
Content-Type: application/json
Authorization: {{auth-token}}
{
"name": "Shirt",
"description": "Black Shirt",
"hsnCode": "BSM1XL"
}
### update field
PATCH http://localhost:9001/api/vendor/product/11
Content-Type: application/json
Authorization: {{auth-token}}
### upate a row
PUT http://localhost:9001/api/vendor/product/11
Content-Type: application/json
Authorization: {{auth-token}}
### delete a row
DELETE http://localhost:9001/api/vendor/product/#1
Authorization: {{auth-token}}
###
POST http://localhost:9001/api/vendor/product/import
Authorization: {{auth-token}} Authorization: {{auth-token}}

View File

@@ -12,13 +12,14 @@ app:
cache: cache:
redis_uri: redis://127.0.0.1:6379/0 redis_uri: redis://127.0.0.1:6379/0
iam: iam:
url: https://auth.compegence.com url: https://auth.readymixerp.com
realm: forewarn-dev realm: rmc-dev
client_redirect_uri: http://localhost:9001/auth/code client_redirect_uri: http://localhost:9001/auth/code
client: forewarn client: rmc
scripts: scripts:
path: /Users/gowthaman.b/IdeaProjects/rmc_modules_api/src/main/resources/scripts path: /Users/gowthaman.b/IdeaProjects/rmc_modules_api/src/main/resources/scripts
security: security:
enforce_role_restriction: 'true'
private_key: |- private_key: |-
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD4ba8OhlyB9MUx MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD4ba8OhlyB9MUx

View File

@@ -34,6 +34,10 @@ dependencies {
implementation("org.bouncycastle:bcprov-jdk18on:1.76") implementation("org.bouncycastle:bcprov-jdk18on:1.76")
implementation("org.bouncycastle:bcpkix-jdk18on:1.76") implementation("org.bouncycastle:bcpkix-jdk18on:1.76")
implementation("org.yaml:snakeyaml:2.2") implementation("org.yaml:snakeyaml:2.2")
implementation("io.minio:minio:8.5.7")
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")
api ("net.cactusthorn.config:config-core:0.81") api ("net.cactusthorn.config:config-core:0.81")
api ("net.cactusthorn.config:config-yaml:0.81") api ("net.cactusthorn.config:config-yaml:0.81")
kapt("net.cactusthorn.config:config-compiler:0.81") kapt("net.cactusthorn.config:config-compiler:0.81")

View File

@@ -42,6 +42,7 @@ class AppAccessManager : AccessManager {
Role.DbOps -> listOf("ROLE_DB_OPS") Role.DbOps -> listOf("ROLE_DB_OPS")
Role.Entity -> loadEntityActionRole(entity, action) Role.Entity -> loadEntityActionRole(entity, action)
is Role.Standard -> role.action.toList().map { "ROLE_${entity}_${it}" } is Role.Standard -> role.action.toList().map { "ROLE_${entity}_${it}" }
is Role.Explicit -> role.roles
}.map(String::uppercase) }.map(String::uppercase)
} }

View File

@@ -4,8 +4,10 @@ import com.fasterxml.jackson.databind.JsonMappingException
import com.restapi.config.* import com.restapi.config.*
import com.restapi.config.AppConfig.Companion.appConfig import com.restapi.config.AppConfig.Companion.appConfig
import com.restapi.config.Auth.validateAuthToken import com.restapi.config.Auth.validateAuthToken
import com.restapi.controllers.Entities import com.restapi.controllers.*
import com.restapi.domain.DataNotFoundException import com.restapi.domain.DataNotFoundException
import com.restapi.domain.Product
import com.restapi.domain.Session.a
import com.restapi.domain.Session.currentTenant import com.restapi.domain.Session.currentTenant
import com.restapi.domain.Session.currentUser import com.restapi.domain.Session.currentUser
import com.restapi.domain.Session.objectMapper import com.restapi.domain.Session.objectMapper
@@ -15,12 +17,15 @@ import io.ebean.DataIntegrityException
import io.ebean.DuplicateKeyException import io.ebean.DuplicateKeyException
import io.javalin.Javalin import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.* import io.javalin.apibuilder.ApiBuilder.*
import io.javalin.http.* import io.javalin.http.ContentType
import io.javalin.http.Context
import io.javalin.http.UnauthorizedResponse
import io.javalin.http.util.NaiveRateLimit import io.javalin.http.util.NaiveRateLimit
import io.javalin.http.util.RateLimitUtil import io.javalin.http.util.RateLimitUtil
import io.javalin.json.JavalinJackson import io.javalin.json.JavalinJackson
import org.jose4j.jwt.consumer.InvalidJwtException import org.jose4j.jwt.consumer.InvalidJwtException
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
@@ -60,6 +65,7 @@ fun main(args: Array<String>) {
} }
.routes { .routes {
path("/auth") { path("/auth") {
get("/endpoint", Auth::endPoint) get("/endpoint", Auth::endPoint)
get("/init", Auth::init) get("/init", Auth::init)
get("/code", Auth::code) get("/code", Auth::code)
@@ -74,10 +80,10 @@ fun main(args: Array<String>) {
TimeUnit.MINUTES TimeUnit.MINUTES
) )
val authToken = ctx.header("Authorization") val authToken = ctx.getAuthHeader() ?: throw UnauthorizedResponse()
?.replace("Bearer ", "")
?.replace("Bearer: ", "")
?.trim() ?: throw UnauthorizedResponse() //there are 2 scenarios, 1) auth user for admin 2) non user for flow, we need to handle both
setAuthorizedUser(validateAuthToken(authToken = authToken)) setAuthorizedUser(validateAuthToken(authToken = authToken))
@@ -96,39 +102,169 @@ fun main(args: Array<String>) {
it.header("X-Signature", signPayload(outEncoded)) it.header("X-Signature", signPayload(outEncoded))
if (appConfig.enforcePayloadEncryption()) { if (appConfig.enforcePayloadEncryption()) {
//todo:, encrypt and set the response back to user //todo: encrypt and send the response back to user
} }
} }
path("/api") { path("/api") {
post("/audit/{action}") { post("/audit/{action}") {
logger.warn("User ${currentUser()} of tenant ${currentTenant()} has performed ${it.pathParam("action")} @ ${LocalDateTime.now()}") logger.warn("User ${currentUser()} of tenant ${currentTenant()} has performed ${it.pathParam("action")} @ ${LocalDateTime.now()}")
it.json(mapOf("status" to true)) it.json(mapOf("status" to true))
} }
post("/script/database/{name}", Entities::executeStoredProcedure, Roles(adminRole, Role.DbOps)) path("/vendor") {
post("/script/{file}/{name}", Entities::executeScript, Roles(adminRole, Role.DbOps)) 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("/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("/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("/product") {
post("", ProductCtrl::create, Roles(Role.Explicit(listOf("ROLE_PRODUCT_CREATE", "ROLE_ADMIN"))))
get("/{hsnCode}", ProductCtrl::get, Roles(Role.Explicit(listOf("ROLE_PRODUCT_VIEW", "ROLE_ADMIN"))))
patch("/{id}", ProductCtrl::patch, Roles(Role.Explicit(listOf("ROLE_PRODUCT_UPDATE", "ROLE_ADMIN"))))
put("/{id}", ProductCtrl::update, Roles(Role.Explicit(listOf("ROLE_PRODUCT_UPDATE", "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)
post("/import") {ctx -> ctx.json(excelToDb())}
}
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)) get("/{entity}/{id}", Entities::view, Roles(adminRole, viewRole))
post("/{entity}/query/{id}", Entities::sqlQueryById, Roles(adminRole, viewRole)) post("/{entity}/query/{id}", Entities::sqlQueryById, Roles(adminRole, viewRole))
post("/{entity}/query", Entities::sqlQueryRaw, Roles(adminRole, viewRole)) post("/{entity}/query", Entities::sqlQueryRaw, Roles(adminRole, viewRole))
post("/{entity}", Entities::create, Roles(adminRole, createRole)) post("/{entity}", Entities::create, Roles(adminRole, createRole))
put("/{entity}/approve/{id}", Entities::approve, Roles(adminRole, approveOrRejectRole)) put("/{entity}/approve/{id}", Entities::approve, Roles(adminRole, approveOrRejectRole))
put("/{entity}/reject/{id}", Entities::reject, Roles(adminRole, approveOrRejectRole)) put("/{entity}/reject/{id}", Entities::reject, Roles(adminRole, approveOrRejectRole))
put("/{entity}/{action}/{id}", Entities::action, Roles(adminRole, Role.Entity)) put("/{entity}/{action}/{id}", Entities::action, Roles(adminRole, Role.Entity))
put("/{entity}/{id}", Entities::update, Roles(adminRole, updateRole)) put("/{entity}/{id}", Entities::update, Roles(adminRole, updateRole))
patch("/{entity}/{id}", Entities::patch, Roles(adminRole, updateRole)) patch("/{entity}/{id}", Entities::patch, Roles(adminRole, updateRole))
delete("/{entity}/{id}", Entities::delete, Roles(adminRole, Role.Standard(Action.DELETE))) 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) private fun Context.getAuthHeader() = header("Authorization")
.exception(DataNotFoundException::class.java, Exceptions.dataNotFoundException) ?.replace("Bearer ", "")
.exception(IllegalArgumentException::class.java,Exceptions.illegalArgumentException) ?.replace("Bearer: ", "")
.exception(JsonMappingException::class.java, Exceptions.jsonMappingException) ?.trim()
.exception(InvalidJwtException::class.java, Exceptions.invalidJwtException)
.start(appConfig.portNumber())
}

View File

@@ -81,6 +81,21 @@ interface AppConfig {
@Key("app.security.public_key") @Key("app.security.public_key")
fun publicKey(): Optional<String> fun publicKey(): Optional<String>
@Key("app.locate.api_key")
fun locateApiKey(): Optional<String>
@Key("app.s3.url")
fun s3Url(): Optional<String>
@Key("app.s3.bucket")
fun s3Bucket(): Optional<String>
@Key("app.s3.access_key")
fun s3AccessKey(): Optional<String>
@Key("app.s3.secret_key")
fun s3SecretKey(): Optional<String>
companion object { companion object {
val appConfig: AppConfig = ConfigFactory.builder().build().create(AppConfig::class.java) val appConfig: AppConfig = ConfigFactory.builder().build().create(AppConfig::class.java)
} }

View File

@@ -62,8 +62,8 @@ object Auth {
.setRequireExpirationTime() .setRequireExpirationTime()
.setAllowedClockSkewInSeconds(30) .setAllowedClockSkewInSeconds(30)
.setRequireSubject() .setRequireSubject()
.setExpectedIssuer(getAuthEndpoint().issuer)
.setExpectedAudience("account") .setExpectedAudience("account")
.setExpectedIssuer(getAuthEndpoint().issuer)
.setVerificationKeyResolver(HttpsJwksVerificationKeyResolver(HttpsJwks(getAuthEndpoint().jwksUri))) .setVerificationKeyResolver(HttpsJwksVerificationKeyResolver(HttpsJwks(getAuthEndpoint().jwksUri)))
.build() .build()
@@ -235,6 +235,7 @@ enum class Action {
sealed class Role { sealed class Role {
open class Standard(vararg val action: Action) : Role() open class Standard(vararg val action: Action) : Role()
data object Entity : Role() data object Entity : Role()
data class Explicit(val roles: List<String>) : Role()
data object DbOps : Role() data object DbOps : Role()
} }

View File

@@ -6,17 +6,24 @@ import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.restapi.domain.* import com.restapi.domain.*
import com.restapi.domain.Product
import com.restapi.domain.PurchaseOrder
import com.restapi.domain.Quotation
import com.restapi.domain.Session.currentUser import com.restapi.domain.Session.currentUser
import com.restapi.domain.Session.database import com.restapi.domain.Session.database
import com.restapi.domain.Session.findDataModelByEntityAndUniqId import com.restapi.domain.Session.findDataModelByEntityAndUniqId
import com.restapi.domain.Vendor
import com.restapi.integ.Scripting import com.restapi.integ.Scripting
import io.ebean.CallableSql import io.ebean.CallableSql
import io.ebean.RawSqlBuilder import io.ebean.RawSqlBuilder
import io.javalin.http.BadRequestResponse import io.javalin.http.*
import io.javalin.http.Context import org.apache.poi.ss.usermodel.Cell
import io.javalin.http.NotFoundResponse import org.apache.poi.ss.usermodel.CellType
import io.javalin.http.bodyAsClass import org.apache.poi.ss.usermodel.WorkbookFactory
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.sql.Types import java.sql.Types
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -372,3 +379,225 @@ object Entities {
false false
} }
} }
object PurchaseOrder {
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)
}
fun create(ctx :Context){
val po = ctx.bodyAsClass<PurchaseOrder>()
database.save(po)
ctx.result("po 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")
//reject all other pos pertaining to the same tx ??
}
fun reject(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.REJECTED
po.save()
ctx.result("po with id $id rejected")
}
fun quoteReference(ctx :Context){
//gets the quote reference on which this po is based on
val id = ctx.pathParam("id")
val quote = database.find(Quotation::class.java)
.where()
.eq("referenceQuotation", id)
?: throw NotFoundResponse("reference quotation not found for po $id")
ctx.json(quote)
}
}
data class ProductSearch(
var isSort: String? = null
)
object ProductCtrl {
fun get(ctx :Context){
val hsnCode = ctx.pathParam("hsnCode")
val product = database.find(Product::class.java, hsnCode) ?: throw NotFoundResponse("Product not found for $hsnCode")
ctx.json(product)
}
fun getAll(ctx: Context){
val productList = Session.database.find(Product::class.java)
.findList()
//.sortedBy { it.hsnCode }
ctx.json(productList)
}
fun create(ctx :Context){
val product = ctx.bodyAsClass<Product>()
database.save(product)
}
fun delete(ctx: Context) {
val id = ctx.pathParam("id")
val product = database.delete(Product::class.java, id)
}
fun patch(ctx: Context) {
}
fun update(ctx: Context) {
}
fun prodExcel(it: Context) {
val product = database.find(Product::class.java).findList()
it.result(CreateExcel(product)).contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.header("Content-Disposition", "attachment; filename=\"product.xlsx\"")
}
}
object Quotation {
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.json(quote)
}
fun create(ctx :Context){
val quote = ctx.bodyAsClass<Quotation>()
//we have to check if the quotation created date is below the expiry of rfq
val rfq = database.find(com.restapi.domain.ReqForQuote::class.java)
.where()
.eq("reqForQuoteNum", quote.reqForQuoteNum)
.findOne()
if(rfq != null){
//compare dates
if(quote.quoteDate!! <= rfq.openTill) {
//valid
database.save(quote)
ctx.result("quote created")
}else {
ctx.result("request for quote closed")
}
}else {
throw NotFoundResponse("request for quote not found for this quotation")
}
}
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.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"))
val rfq = database.find(RequestForQuote::class.java)
.where()
.eq("reqForQuoteNum", reqForQuoteNum)
?: throw NotFoundResponse("request for quote not found for this quotation")
ctx.json(rfq)
}
}
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.json(doc)
}
fun create(ctx :Context){
val doc = ctx.bodyAsClass<Document>()
database.save(doc)
ctx.result("doc created")
}
fun print(ctx :Context){
//would be handled in the frontend ??
}
fun delete(ctx :Context){
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")
}
fun getWithRefId(ctx :Context){
//fetches a particular doc (po, quote) with ref id
val refId = ctx.pathParam("refId")
val doc = database.find(Document::class.java)
.where()
.eq("refId", refId)
?: throw NotFoundResponse("no doc found for refId $refId")
ctx.json(doc)
}
}
object Vendor {
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.json(vendor)
}
fun create(ctx :Context){
val vendor = ctx.bodyAsClass<Vendor>()
database.save(vendor)
ctx.result("vendor created")
}
fun update(ctx :Context){
}
fun delete(ctx :Context){
}
fun getQuotes(ctx :Context){
val id = ctx.pathParam("id")
val quotes = database.find(Quotation::class.java)
.where()
.eq("vendor", id)
.findList()
ctx.json(quotes)
}
fun getPos(ctx :Context){
val id = ctx.pathParam("id")
val pos = database.find(PurchaseOrder::class.java)
.where()
.eq("vendor", id)
.findList()
ctx.json(pos)
}
fun rate(ctx :Context){
val id = ctx.pathParam("id")
val rating = ctx.pathParam("rating").toDouble()
val vendor = database.find(Vendor::class.java, id) ?: throw NotFoundResponse("vendor not found for id $id")
//could place some rating validation checks
vendor.rating = rating
vendor.save()
ctx.result("rating changed")
}
}
object RequestForQuote {
fun create(ctx :Context) {
val rfq = ctx.bodyAsClass<ReqForQuote>()
database.save(rfq)
//ctx.result("request for quote created")
//ctx.json(rfq)
//ctx.status(HttpStatus.CREATED)
//ctx.json("asss")
}
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")
ctx.json(rfq)
}
fun update(ctx :Context){
//shuld we compare the new body fields with preexisting ones and prepare a sql query to update those fields??
}
}

View File

@@ -0,0 +1,184 @@
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.restapi.domain.Session
import com.restapi.domain.Session.database
import com.restapi.domain.UOM
import io.ebean.text.json.JsonContext
import io.javalin.http.Context
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.WorkbookFactory
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.io.InputStream
fun CreateExcel(productList: List<Product>): 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++)
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 ?: "")
}
val baos = ByteArrayOutputStream()
wb.write(baos)
wb.close()
return ByteArrayInputStream(baos.toByteArray())
}
data class validateExcel(
val name: String,
val description: String,
val hsnCode: String,
val ok: Boolean,
val err: String,
)
val app_common_om = jacksonObjectMapper().apply {
registerModule(JavaTimeModule())
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
fun ExcelRead(): String{
val inputStream = FileInputStream("C:\\Users\\vinay\\IdeaProjects\\readymixerp_modules_api_git\\product-4.xlsx")
val workbook = WorkbookFactory.create(inputStream)
val workSheet = workbook.getSheetAt(0)
var h = true
//Header check
if(workSheet.getRow(0).getCell(0).stringCellValue.equals("Name")) {
if (workSheet.getRow(0).getCell(1).stringCellValue.equals("Description")) {
if (workSheet.getRow(0).getCell(2).stringCellValue.equals("HSN")) {
if (workSheet.getRow(0).getCell(3).stringCellValue.equals("UOM")) {
h = false
}else return "Header UOM mismatch"
}else return "Header-HSN mismatch"
}else return "Header-Desc mismatch"
}else return "Header-Name mismatch"
val resp = arrayListOf<validateExcel>()
if(h==false) {
workSheet.rowIterator().forEach { row ->
if (row == null) return@forEach
if (h) {
val pName = row.getCell(0).run {
when {
this == null -> ""
this.cellType == CellType.STRING -> this.stringCellValue
else -> ""
}
}
val pDesc = row.getCell(1).run {
when {
this == null -> ""
this.cellType == CellType.STRING -> this.stringCellValue
else -> ""
}
}
val pHsn = row.getCell(2).run {
when {
this == null -> ""
this.cellType == CellType.STRING -> this.stringCellValue
else -> ""
}
}
if (pName.isEmpty() && pDesc.isEmpty() && pHsn.isEmpty()) {
return@forEach
}
if (pName.isEmpty()) {
resp.add(
validateExcel(
name = pName,
description = pDesc,
hsnCode = pHsn,
ok = false,
err = "Product name is required"
)
)
return@forEach
}
if (pDesc.isEmpty()) {
resp.add(
validateExcel(
name = pName,
description = pDesc,
hsnCode = pHsn,
ok = false,
err = "Product description is required"
)
)
return@forEach
}
if (pHsn.isEmpty()) {
resp.add(
validateExcel(
name = pName,
description = pDesc,
hsnCode = pHsn,
ok = false,
err = "Product HSN is required"
)
)
return@forEach
}
}
h = true
}
}
return app_common_om.writeValueAsString(resp)
}
fun excelToDb(): List<Product> {
val inputStream = FileInputStream("C:\\Users\\vinay\\IdeaProjects\\readymixerp_modules_api_git\\product-4.xlsx")
val workbook = WorkbookFactory.create(inputStream)
val workSheet = workbook.getSheetAt(0)
for (row in workSheet) {
val cell1Value = row.getCell(0).stringCellValue
val cell2Value = row.getCell(1).stringCellValue
val cell3Value = row.getCell(2).stringCellValue
val cell4Value = row.getCell(3).stringCellValue
val prod = Product()
prod.name = cell1Value
prod.description = cell2Value
prod.hsnCode = cell3Value
prod.uom = when(cell4Value) {
"nos" -> UOM.NOS
"ltr" -> UOM.LTR
"mtr" -> UOM.MTR
else -> UOM.ALL
}
database.saveAll(prod)
}
val productList = Session.database.find(Product::class.java).findList()
return productList
}

View File

@@ -0,0 +1,145 @@
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
//constants
const val IGNORE = "%"
val baseDate :LocalDate = LocalDate.MIN
val maxDate :LocalDate = LocalDate.MAX
const val RATING_MAX = 10.0
const val RATING_MIN = 0.0
//common filters would be used by most of the handlers
//require a list of vendor ids to be passed
data class CommonFilters (
val fromDate :LocalDate = baseDate,
val toDate :LocalDate = maxDate,
val vendor :List<Long>? = null,
val sortAsc :Boolean = true,
val sortBy :String = IGNORE
)
data class POFilters (
val poNumLike :String = IGNORE,
val totalAmountExceeds :Long = Long.MIN_VALUE,
val totalAmountLessThan :Long = Long.MAX_VALUE,
val validAfter: LocalDate = baseDate,
val validBefore: LocalDate = maxDate,
val refQuotation :String = IGNORE,
)
data class ProductFilters (
val nameLike :String = IGNORE,
val hsnLike :String = IGNORE,
val uom :UOM = UOM.ALL,
)
data class DocumentFilters (
val nameLike :String = IGNORE,
val typeOfDoc :DocType = DocType.ALL,
)
data class RFQFilters (
val validBefore :LocalDate = maxDate,
val validAfter :LocalDate = baseDate,
val reqForQuoteNumLike :String = IGNORE,
)
data class QuoteFilters (
val quoteNumLike :String = IGNORE,
val validBefore :LocalDate = baseDate,
val validAfter :LocalDate = maxDate,
val totalAmountExceeds :Long = Long.MIN_VALUE,
val totalAmountLessThan :Long = Long.MAX_VALUE,
)
data class VendorFilters (
val nameLike :String = IGNORE,
val msmeLike :String = IGNORE,
val gstNumLike :String = IGNORE,
val addressLike :String = IGNORE,
val ratingExceeds :Double = RATING_MIN,
val ratingLessThan :Double = RATING_MAX,
)
fun<T> applyVendorHelper(q :io.ebean.ExpressionList<T>, vids :List<Long>?) {
if (vids.isNullOrEmpty()) return
q.apply {
q.`in`("vendor", vids)
}
}
fun<T> applySortHelper(q :io.ebean.ExpressionList<T>, sortBy :String, asc :Boolean) {
if(sortBy == IGNORE) return;
val order = if (asc) "ASC" else "DESC"
q.orderBy("$sortBy $order")
}
fun<T> applyCommonFilters(q :io.ebean.ExpressionList<T>, commonFilters: CommonFilters) {
applyVendorHelper<T>(q, commonFilters.vendor)
applySortHelper<T>(q, commonFilters.sortBy, commonFilters.sortAsc)
}
fun searchQuotes(commonFilters: CommonFilters, quoteFilters: QuoteFilters) : List<Quotation> {
val q = database.find(Quotation::class.java)
.where()
.between("quoteDate", commonFilters.fromDate, commonFilters.toDate)
.ilike("quoteNum", quoteFilters.quoteNumLike )
.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) }
}
}
applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc)
return q.findList()
}
fun searchVendors(commonFilters: CommonFilters, vendorFilters: VendorFilters) : List<Vendor> {
val q = database.find(Vendor::class.java)
.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)
applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc)
return q.findList()
}
fun searchDocs(commonFilters: CommonFilters, documentFilters: DocumentFilters) : List<Document> {
val q = database.find(Document::class.java)
.where()
.apply {
if(documentFilters.typeOfDoc != DocType.ALL){
this.eq("docType", documentFilters.typeOfDoc)
}
}
.ilike("name", documentFilters.nameLike )
applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc)
return q.findList()
}
fun searchPos(commonFilters: CommonFilters, poFilters: POFilters) : List<PurchaseOrder> {
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 )
applyVendorHelper(q, commonFilters.vendor)
applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc)
return q.findList()
}
fun searchRFQ(commonFilters: CommonFilters, rfqFilters: RFQFilters) : List<ReqForQuote> {
val q = database.find(ReqForQuote::class.java)
.where()
.between("validTill", rfqFilters.validAfter, rfqFilters.validBefore)
.ilike("reqForQuoteNum", rfqFilters.reqForQuoteNumLike)
applyVendorHelper(q, commonFilters.vendor)
applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc)
return q.findList()
}
fun searchProduct(commonFilters: CommonFilters, productFilters: ProductFilters): List<Product> {
val p = database.find(Product::class.java)
.where()
.ilike("hsnCode", productFilters.hsnLike)
.ilike("Pname", productFilters.nameLike)
applySortHelper(p, commonFilters.sortBy, commonFilters.sortAsc)
return p.findList()
}

View File

@@ -30,6 +30,7 @@ import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
import kotlin.collections.HashMap
import kotlin.jvm.optionals.getOrDefault import kotlin.jvm.optionals.getOrDefault
@@ -137,7 +138,27 @@ object Session {
} }
fun a(){
val a = HashMap<String,String>()
a.put("a", "b");
a.put("a", "b");
a.put("a", "b");
a.put("a", "b");
val b = HashMap<String,String>().apply {
put("a", "b");
put("a", "b");
put("a", "b");
put("a", "b");
}
val c: String? = ""
val x = c?.get(1)
c?.apply {
//will work only when c is not null
}
}
private val sc = DatabaseConfig().apply { private val sc = DatabaseConfig().apply {
loadFromProperties(Properties().apply { loadFromProperties(Properties().apply {
setProperty("datasource.db.username", appConfig.dbUser()) setProperty("datasource.db.username", appConfig.dbUser())
@@ -180,7 +201,7 @@ object Session {
val s = database val s = database
.sqlQuery("SELECT nextval('${seqName(entity)}');") .sqlQuery("SELECT nextval('${seqName(entity)}');")
.findOne()?.getLong("nextval") ?: throw DataNotFoundException .findOne()?.getLong("nextval") ?: throw DataNotFoundException
return String.format("%s-%s", entity, "$s".padStart(10, '0')) return "$entity-${"$s".padStart(10, '0')}"
} }
val redis = JedisPooled(appConfig.redisUri().getOrDefault("redis://localhost:6739/0")) val redis = JedisPooled(appConfig.redisUri().getOrDefault("redis://localhost:6739/0"))

View File

@@ -5,22 +5,15 @@ import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import io.ebean.Model import io.ebean.Model
import io.ebean.annotation.DbArray import io.ebean.annotation.*
import io.ebean.annotation.DbDefault
import io.ebean.annotation.DbJsonB
import io.ebean.annotation.Index import io.ebean.annotation.Index
import io.ebean.annotation.Platform import java.time.LocalDate
import io.ebean.annotation.SoftDelete
import io.ebean.annotation.TenantId
import io.ebean.annotation.WhenCreated
import io.ebean.annotation.WhenModified
import io.ebean.annotation.WhoCreated
import io.ebean.annotation.WhoModified
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.*
import javax.persistence.* import javax.persistence.*
data class Comments(val text: String = "", val by: String = "", val at: LocalDateTime = LocalDateTime.now()) 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 = "")
enum class ApprovalStatus { enum class ApprovalStatus {
PENDING, APPROVED, REJECTED PENDING, APPROVED, REJECTED
} }
@@ -48,7 +41,6 @@ abstract class BaseModel : Model() {
var modifiedAt: LocalDateTime? = null var modifiedAt: LocalDateTime? = null
@WhoCreated @WhoCreated
var createdBy: String = "" var createdBy: String = ""
@@ -61,6 +53,7 @@ abstract class BaseModel : Model() {
@DbDefault("0") @DbDefault("0")
var currentApprovalLevel: Int = 0 var currentApprovalLevel: Int = 0
@DbDefault("0") @DbDefault("0")
var requiredApprovalLevels: Int = 0 var requiredApprovalLevels: Int = 0
@@ -110,11 +103,17 @@ open class AuditLog : BaseTenantModel() {
var uniqueIdentifier: String = "" var uniqueIdentifier: String = ""
@DbJsonB @DbJsonB
@Index(definition = "create index audit_log_values_idx on audit_log using GIN (data)", platforms = [Platform.POSTGRES]) @Index(
definition = "create index audit_log_values_idx on audit_log using GIN (data)",
platforms = [Platform.POSTGRES]
)
var data: Map<String, Any> = hashMapOf() var data: Map<String, Any> = hashMapOf()
@DbJsonB @DbJsonB
@Index(definition = "create index audit_log_changes_idx on audit_log using GIN (changes)", platforms = [Platform.POSTGRES]) @Index(
definition = "create index audit_log_changes_idx on audit_log using GIN (changes)",
platforms = [Platform.POSTGRES]
)
var changes: Map<String, Any> = hashMapOf() var changes: Map<String, Any> = hashMapOf()
} }
@@ -171,7 +170,7 @@ enum class JobType {
@Entity @Entity
@Index(unique = true, name = "sql_unique_id", columnNames = ["entity_name", "sql_id", "tenant_id"]) @Index(unique = true, name = "sql_unique_id", columnNames = ["entity_name", "sql_id", "tenant_id"])
open class SqlModel : BaseTenantModel(){ open class SqlModel : BaseTenantModel() {
@JsonDeserialize(using = SafeStringDeserializer::class) @JsonDeserialize(using = SafeStringDeserializer::class)
var sqlId: String = "" var sqlId: String = ""
@@ -181,6 +180,7 @@ open class SqlModel : BaseTenantModel(){
@Column(columnDefinition = "text") @Column(columnDefinition = "text")
var sql: String = "" var sql: String = ""
} }
@Entity @Entity
open class JobModel : BaseTenantModel() { open class JobModel : BaseTenantModel() {
@Index(unique = true) @Index(unique = true)
@@ -216,6 +216,21 @@ open class DataModel : BaseTenantModel() {
} }
@Entity
@Index(unique = true, name = "unique_session_id", columnNames = ["session_id"])
open class AnonSession : BaseTenantModel() {
var sessionId: String? = null
var ip: String? = null
var firstSeenAt: LocalDateTime? = null
var lastSeenAt: LocalDateTime? = null
@DbJsonB
var headerMap: Map<String, String> = hashMapOf()
}
class SafeStringDeserializer : JsonDeserializer<String>() { class SafeStringDeserializer : JsonDeserializer<String>() {
private val regex = Regex("^[a-zA-Z0-9\\-_\\.]+$") private val regex = Regex("^[a-zA-Z0-9\\-_\\.]+$")
@@ -225,5 +240,95 @@ class SafeStringDeserializer : JsonDeserializer<String>() {
if (!regex.matches(text)) throw IllegalArgumentException() if (!regex.matches(text)) throw IllegalArgumentException()
return text return text
} }
}
data class ContactPerson(val name: String, val email: String, val mobile: String)
@Entity
open class Vendor :BaseTenantModel() {
var name :String = ""
var msme :String = ""
var gstNumber :String = ""
var address :String = ""
var rating :Double = 0.0
@DbJsonB
var contacts :List<ContactPerson> = mutableListOf()
}
@Entity
open class PurchaseOrder :BaseTenantModel() {
@DbJsonB
var products :MutableList<POProducts> = mutableListOf()
@ManyToOne
var vendor :Vendor? = null
var referenceQuotation :String = ""
var totalAmount :Int = 0
var poNum: String = ""
var poDate: LocalDate? = null
var validTill: LocalDate? = null
@DbArray
var tnc: List<String> = arrayListOf()
@DbArray
var documents: MutableList<Long> = arrayListOf()
}
enum class UOM {
NOS, LTR, MTR, ALL
}
@Entity
open class Product :BaseTenantModel() {
var id: Int? = null
var name :String = ""
var description :String = ""
var hsnCode :String = ""
@Enumerated(EnumType.STRING)
var uom: UOM? = null
}
@Entity
open class Quotation :BaseTenantModel() {
@DbJsonB
var products :MutableList<POProducts> = mutableListOf()
@ManyToOne
var vendor :Vendor? = null
var totalAmount :Long = 0
var reqForQuoteNum: String = ""
var quoteNum: String = ""
var quoteDate: LocalDate? = null
var validTill: LocalDate? = null
@DbArray
var tnc: List<String> = arrayListOf()
@DbArray
var documents: MutableList<Long> = arrayListOf()
}
enum class DocType{
PO, QUOTE, INVOICE, ALL
}
@Entity
open class Document :BaseTenantModel() {
var name :String = ""
@Enumerated(EnumType.STRING)
var typeOfDoc :DocType? = null
var refId: Long? = null
var description :String = ""
var url :String = ""
}
enum class RFQStatus{
DELIVERED, PO, QUOTE, CANCELLED
}
@Entity
open class ReqForQuote :BaseTenantModel() {
@DbArray
var potentialVendors :List<Long>? = null
@Enumerated(EnumType.STRING)
var status :RFQStatus? = null
@DbArray
var docs :List<Document>? = null
@DbJsonB
var products :List<POProducts>? = null
var reqForQuoteNum: String = ""
var openTill: LocalDate? = null
} }

View File

@@ -0,0 +1,190 @@
package com.restapi.integ
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.restapi.config.AppConfig
import io.minio.MinioClient
import io.minio.UploadObjectArgs
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.utils.URIBuilder
import org.apache.http.impl.client.HttpClients
import org.apache.http.message.BasicNameValuePair
import org.apache.http.util.EntityUtils
import org.slf4j.LoggerFactory
import java.io.File
val logger = LoggerFactory.getLogger("ExternalAPI")
object S3 {
private val s3Url = AppConfig.appConfig.s3Url().orElse("s3.amazonaws.com")
private val minioClient: MinioClient by lazy {
MinioClient.builder()
.endpoint("https://$s3Url")
.credentials(
AppConfig.appConfig.s3AccessKey().orElseThrow(),
AppConfig.appConfig.s3SecretKey().orElseThrow()
).build()
}
fun uploadFilesToS3(f: File, name: String): Result<String> {
val objStore: String = AppConfig.appConfig.s3Bucket().orElseThrow()
logger.warn("Trying to upload Object ${f.absolutePath} -> $objStore")
try {
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(objStore)
.`object`(name)
.filename(f.absolutePath)
.build()
)
return Result.success("https://${objStore}.${s3Url}/$name")
} catch (e: Exception) {
logger.warn("Error in file Upload ${f.absolutePath} ==> ${e.message} => Objstore[${objStore}]")
return Result.failure(e)
}
}
}
data class LoqateFullAddrResponse(
@JsonProperty("Items")
val items: List<Item>
) {
data class Item(
@JsonProperty("AdminAreaCode")
val adminAreaCode: String,
@JsonProperty("AdminAreaName")
val adminAreaName: String,
@JsonProperty("Barcode")
val barcode: String,
@JsonProperty("Block")
val block: String,
@JsonProperty("BuildingName")
val buildingName: String,
@JsonProperty("BuildingNumber")
val buildingNumber: String,
@JsonProperty("City")
val city: String,
@JsonProperty("Company")
val company: String,
@JsonProperty("CountryIso2")
val countryIso2: String,
@JsonProperty("CountryIso3")
val countryIso3: String,
@JsonProperty("CountryIsoNumber")
val countryIsoNumber: String,
@JsonProperty("CountryName")
val countryName: String,
@JsonProperty("DataLevel")
val dataLevel: String,
@JsonProperty("Department")
val department: String,
@JsonProperty("District")
val district: String,
@JsonProperty("DomesticId")
val domesticId: String,
@JsonProperty("Field1")
val field1: String,
@JsonProperty("Field10")
val field10: String,
@JsonProperty("Field11")
val field11: String,
@JsonProperty("Field12")
val field12: String,
@JsonProperty("Field13")
val field13: String,
@JsonProperty("Field14")
val field14: String,
@JsonProperty("Field15")
val field15: String,
@JsonProperty("Field16")
val field16: String,
@JsonProperty("Field17")
val field17: String,
@JsonProperty("Field18")
val field18: String,
@JsonProperty("Field19")
val field19: String,
@JsonProperty("Field2")
val field2: String,
@JsonProperty("Field20")
val field20: String,
@JsonProperty("Field3")
val field3: String,
@JsonProperty("Field4")
val field4: String,
@JsonProperty("Field5")
val field5: String,
@JsonProperty("Field6")
val field6: String,
@JsonProperty("Field7")
val field7: String,
@JsonProperty("Field8")
val field8: String,
@JsonProperty("Field9")
val field9: String,
@JsonProperty("Id")
val id: String,
@JsonProperty("Label")
val label: String,
@JsonProperty("Language")
val language: String,
@JsonProperty("LanguageAlternatives")
val languageAlternatives: String,
@JsonProperty("Line1")
val line1: String,
@JsonProperty("Line2")
val line2: String,
@JsonProperty("Line3")
val line3: String,
@JsonProperty("Line4")
val line4: String,
@JsonProperty("Line5")
val line5: String,
@JsonProperty("Neighbourhood")
val neighbourhood: String,
@JsonProperty("POBoxNumber")
val pOBoxNumber: String,
@JsonProperty("PostalCode")
val postalCode: String,
@JsonProperty("Province")
val province: String,
@JsonProperty("ProvinceCode")
val provinceCode: String,
@JsonProperty("ProvinceName")
val provinceName: String,
@JsonProperty("SecondaryStreet")
val secondaryStreet: String,
@JsonProperty("SortingNumber1")
val sortingNumber1: String,
@JsonProperty("SortingNumber2")
val sortingNumber2: String,
@JsonProperty("Street")
val street: String,
@JsonProperty("SubBuilding")
val subBuilding: String,
@JsonProperty("Type")
val type: String
)
}
object LoqateAPI {
//todo: add search?
fun findFullAddr(id: String): LoqateFullAddrResponse {
//https://api.addressy.com/Capture/Interactive/Retrieve/v1.2/
return HttpClients.createDefault().use { h ->
val key = AppConfig.appConfig.locateApiKey().orElseThrow()
val ub = URIBuilder("https://api.addressy.com/Capture/Interactive/Retrieve/v1.2/json3.ws")
.setParameters(
BasicNameValuePair("Key", key),
BasicNameValuePair("Id", id)
).build()
h.execute(HttpGet(ub)).use {
jacksonObjectMapper().readValue(EntityUtils.toString(it.entity))
}
}
}
}

View File

@@ -0,0 +1,27 @@
-- apply changes
create table anon_session (
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,
first_seen_at timestamp,
last_seen_at timestamp,
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,
session_id varchar(255),
ip varchar(255),
header_map jsonb not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_anon_session_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint unique_session_id unique (session_id),
constraint pk_anon_session primary key (sys_pk)
);

View File

@@ -0,0 +1,140 @@
-- apply changes
create table document (
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,
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,
name varchar(255) not null,
type_of_doc varchar(255) not null,
description varchar(255) not null,
url varchar(255) not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_document_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint pk_document primary key (sys_pk)
);
create table product (
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,
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,
name varchar(255) not null,
description varchar(255) not null,
hsn_code varchar(255) not null,
uom varchar(3),
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_product_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint ck_product_uom check ( uom in ('NOS','LTR','MTR')),
constraint pk_product primary key (sys_pk)
);
create table purchase_order (
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,
vendor_sys_pk bigint,
total_amount integer not null,
po_date date,
valid_till date,
documents bigint[] not null,
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,
products jsonb not null,
reference_quotation varchar(255) not null,
po_num varchar(255) not null,
tnc varchar[] not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_purchase_order_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint pk_purchase_order primary key (sys_pk)
);
create table quotation (
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,
vendor_sys_pk bigint,
total_amount integer not null,
quote_date date,
valid_till date,
documents bigint[] not null,
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,
products jsonb not null,
quote_num varchar(255) not null,
tnc varchar[] not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_quotation_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint pk_quotation primary key (sys_pk)
);
create table vendor (
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,
rating float not null,
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,
name varchar(255) not null,
msme varchar(255) not null,
gst_number varchar(255) not null,
address varchar(255) not null,
contacts jsonb not null,
created_by varchar(255) not null,
modified_by varchar(255) not null,
constraint ck_vendor_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
constraint pk_vendor primary key (sys_pk)
);
-- foreign keys and indices
create index ix_purchase_order_vendor_sys_pk on purchase_order (vendor_sys_pk);
alter table purchase_order add constraint fk_purchase_order_vendor_sys_pk foreign key (vendor_sys_pk) references vendor (sys_pk) on delete restrict on update restrict;
create index ix_quotation_vendor_sys_pk on quotation (vendor_sys_pk);
alter table quotation add constraint fk_quotation_vendor_sys_pk foreign key (vendor_sys_pk) references vendor (sys_pk) on delete restrict on update restrict;

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
<changeSet type="apply">
<createTable name="anon_session" pkName="pk_anon_session">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" defaultValue="0" notnull="true"/>
<column name="required_approval_levels" type="integer" defaultValue="0" notnull="true"/>
<column name="approval_status" type="varchar(8)" defaultValue="'APPROVED'" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_anon_session_approval_status"/>
<column name="tags" type="varchar[]" defaultValue="'{}'" notnull="true"/>
<column name="comments" type="jsonb" defaultValue="'[]'" notnull="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="session_id" type="varchar"/>
<column name="ip" type="varchar"/>
<column name="first_seen_at" type="localdatetime"/>
<column name="last_seen_at" type="localdatetime"/>
<column name="header_map" type="jsonb" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" defaultValue="1" notnull="true"/>
<column name="created_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="modified_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
<uniqueConstraint name="unique_session_id" columnNames="session_id" oneToOne="false" nullableColumns="session_id"/>
</createTable>
</changeSet>
</migration>

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
<changeSet type="apply">
<createTable name="document" pkName="pk_document">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" defaultValue="0" notnull="true"/>
<column name="required_approval_levels" type="integer" defaultValue="0" notnull="true"/>
<column name="approval_status" type="varchar(8)" defaultValue="'APPROVED'" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_document_approval_status"/>
<column name="tags" type="varchar[]" defaultValue="'{}'" notnull="true"/>
<column name="comments" type="jsonb" defaultValue="'[]'" notnull="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="name" type="varchar" notnull="true"/>
<column name="type_of_doc" type="varchar" notnull="true"/>
<column name="description" type="varchar" notnull="true"/>
<column name="url" type="varchar" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" defaultValue="1" notnull="true"/>
<column name="created_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="modified_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
</createTable>
<createTable name="product" pkName="pk_product">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" defaultValue="0" notnull="true"/>
<column name="required_approval_levels" type="integer" defaultValue="0" notnull="true"/>
<column name="approval_status" type="varchar(8)" defaultValue="'APPROVED'" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_product_approval_status"/>
<column name="tags" type="varchar[]" defaultValue="'{}'" notnull="true"/>
<column name="comments" type="jsonb" defaultValue="'[]'" notnull="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="name" type="varchar" notnull="true"/>
<column name="description" type="varchar" notnull="true"/>
<column name="hsn_code" type="varchar" notnull="true"/>
<column name="uom" type="varchar(3)" checkConstraint="check ( uom in ('NOS','LTR','MTR'))" checkConstraintName="ck_product_uom"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" defaultValue="1" notnull="true"/>
<column name="created_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="modified_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
</createTable>
<createTable name="purchase_order" pkName="pk_purchase_order">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" defaultValue="0" notnull="true"/>
<column name="required_approval_levels" type="integer" defaultValue="0" notnull="true"/>
<column name="approval_status" type="varchar(8)" defaultValue="'APPROVED'" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_purchase_order_approval_status"/>
<column name="tags" type="varchar[]" defaultValue="'{}'" notnull="true"/>
<column name="comments" type="jsonb" defaultValue="'[]'" notnull="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="products" type="jsonb" notnull="true"/>
<column name="vendor_sys_pk" type="bigint" references="vendor.sys_pk" foreignKeyName="fk_purchase_order_vendor_sys_pk" foreignKeyIndex="ix_purchase_order_vendor_sys_pk"/>
<column name="reference_quotation" type="varchar" notnull="true"/>
<column name="total_amount" type="integer" notnull="true"/>
<column name="po_num" type="varchar" notnull="true"/>
<column name="po_date" type="date"/>
<column name="valid_till" type="date"/>
<column name="tnc" type="varchar[]" notnull="true"/>
<column name="documents" type="bigint[]" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" defaultValue="1" notnull="true"/>
<column name="created_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="modified_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
</createTable>
<createTable name="quotation" pkName="pk_quotation">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" defaultValue="0" notnull="true"/>
<column name="required_approval_levels" type="integer" defaultValue="0" notnull="true"/>
<column name="approval_status" type="varchar(8)" defaultValue="'APPROVED'" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_quotation_approval_status"/>
<column name="tags" type="varchar[]" defaultValue="'{}'" notnull="true"/>
<column name="comments" type="jsonb" defaultValue="'[]'" notnull="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="products" type="jsonb" notnull="true"/>
<column name="vendor_sys_pk" type="bigint" references="vendor.sys_pk" foreignKeyName="fk_quotation_vendor_sys_pk" foreignKeyIndex="ix_quotation_vendor_sys_pk"/>
<column name="total_amount" type="integer" notnull="true"/>
<column name="quote_num" type="varchar" notnull="true"/>
<column name="quote_date" type="date"/>
<column name="valid_till" type="date"/>
<column name="tnc" type="varchar[]" notnull="true"/>
<column name="documents" type="bigint[]" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" defaultValue="1" notnull="true"/>
<column name="created_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="modified_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
</createTable>
<createTable name="vendor" pkName="pk_vendor">
<column name="sys_pk" type="bigint" primaryKey="true"/>
<column name="deleted_on" type="localdatetime"/>
<column name="deleted_by" type="varchar"/>
<column name="current_approval_level" type="integer" defaultValue="0" notnull="true"/>
<column name="required_approval_levels" type="integer" defaultValue="0" notnull="true"/>
<column name="approval_status" type="varchar(8)" defaultValue="'APPROVED'" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_vendor_approval_status"/>
<column name="tags" type="varchar[]" defaultValue="'{}'" notnull="true"/>
<column name="comments" type="jsonb" defaultValue="'[]'" notnull="true"/>
<column name="tenant_id" type="varchar" notnull="true"/>
<column name="name" type="varchar" notnull="true"/>
<column name="msme" type="varchar" notnull="true"/>
<column name="gst_number" type="varchar" notnull="true"/>
<column name="address" type="varchar" notnull="true"/>
<column name="rating" type="double" notnull="true"/>
<column name="contacts" type="jsonb" notnull="true"/>
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
<column name="version" type="integer" defaultValue="1" notnull="true"/>
<column name="created_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="modified_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
<column name="created_by" type="varchar" notnull="true"/>
<column name="modified_by" type="varchar" notnull="true"/>
</createTable>
</changeSet>
</migration>