From d9dcda072485a4d5ad8dc8c2899e50109906598a Mon Sep 17 00:00:00 2001 From: arsalan Date: Wed, 6 Mar 2024 15:59:14 +0530 Subject: [PATCH] add payments, invoice --- src/main/kotlin/com/restapi/Main.kt | 29 +++++ .../com/restapi/controllers/Entities.kt | 102 +++++++++++++++++- .../kotlin/com/restapi/controllers/Filters.kt | 48 ++++++++- src/main/kotlin/com/restapi/domain/models.kt | 49 +++++++++ src/main/resources/dbmigration/1.12.sql | 64 +++++++++++ .../dbmigration/model/1.12.model.xml | 55 ++++++++++ 6 files changed, 343 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/dbmigration/1.12.sql create mode 100644 src/main/resources/dbmigration/model/1.12.model.xml diff --git a/src/main/kotlin/com/restapi/Main.kt b/src/main/kotlin/com/restapi/Main.kt index 0710df2..4778e2e 100644 --- a/src/main/kotlin/com/restapi/Main.kt +++ b/src/main/kotlin/com/restapi/Main.kt @@ -159,6 +159,35 @@ fun main(args: Array) { Roles(Role.Explicit("ROLE_INVENTORY_CREATE", "ROLE_INVENTORY_VIEW")) ) } + path("/invoice") { + post("", InvoiceCtrl::create, Roles(Role.Explicit("ROLE_INVOICE_CREATE"))) + get("/next", InvoiceCtrl::getNextNum, Roles(Role.Explicit("ROLE_INVOICE_CREATE"))) + get( + "/{id}", + InvoiceCtrl::get, + Roles(Role.Explicit("ROLE_INVOICE_VIEW", "ROLE_INVOICE_CREATE")) + ) + put("/{id}", InvoiceCtrl::update, Roles(Role.Explicit("ROLE_INVOICE_CREATE"))) + post( + "/getAll", + InvoiceCtrl::getAll, + Roles(Role.Explicit("ROLE_INVOICE_CREATE", "ROLE_INVOICE_VIEW")) + ) + } + path("/payment") { + post("", PaymentCtrl::create, Roles(Role.Explicit("ROLE_PAYMENT_CREATE"))) + get( + "/{id}", + PaymentCtrl::get, + Roles(Role.Explicit("ROLE_PAYMENT_VIEW", "ROLE_PAYMENT_CREATE")) + ) + put("/{id}", PaymentCtrl::update, Roles(Role.Explicit("ROLE_PAYMENT_CREATE"))) + post( + "/getAll", + PaymentCtrl::getAll, + Roles(Role.Explicit("ROLE_PAYMENT_CREATE", "ROLE_PAYMENT_VIEW")) + ) + } path("/po") { get("/next", PurchaseOrderCtrl::getNextNum, Roles(Role.Explicit("ROLE_PO_CREATE"))) post("", PurchaseOrderCtrl::create, Roles(Role.Explicit("ROLE_PO_CREATE"))) diff --git a/src/main/kotlin/com/restapi/controllers/Entities.kt b/src/main/kotlin/com/restapi/controllers/Entities.kt index b22c399..41ec164 100644 --- a/src/main/kotlin/com/restapi/controllers/Entities.kt +++ b/src/main/kotlin/com/restapi/controllers/Entities.kt @@ -57,6 +57,7 @@ sealed class QueryParam { return when (this) { is Complex -> getValueComplex() is Simple -> simple + else -> {} } } } @@ -894,7 +895,6 @@ object OutgoingInventoryCtrl { } fun getNextNum(ctx: Context) { - println("inside next num") val prefix = "MDN/" val cnt = database.find(OutgoingInventory::class.java) .findCount() @@ -904,3 +904,103 @@ object OutgoingInventoryCtrl { ctx.json(seq).status(HttpStatus.OK) } } +object PaymentCtrl { + fun create(ctx :Context){ + val pmt = ctx.bodyAsClass(Payment::class.java) + database.save(pmt) + //update the status of invoices pertaining to payment.vendor + + val invcs = searchInvoices(CommonFilters(sortBy = "date", sortAsc = true), InvoiceFilters(status = InvoiceStatus.PAID_NONE)) + val tot: Double = pmt.amount + for(inv in invcs){ + val deduct = Math.min(pmt.amount, inv.totalAmount) + inv.totalAmount -= deduct + pmt.amount -= deduct + database.update(inv) + if(pmt.amount <= 0.0) break + } + if (pmt.amount > 0.0){ + //balance left for this vendor + val v = pmt.vendor?.sysPk?.let { database.find(Vendor::class.java, it) } + v?.apply { + outstanding = outstanding?.minus(tot) + database.update(v) + } + } + ctx.json(pmt).status(HttpStatus.CREATED) + } + fun get(ctx : Context){ + val id = ctx.pathParam("id") + val pmt = database.find(Payment::class.java, id) + ?: throw NotFoundResponse("No payment found for this id") + ctx.json(pmt).status(HttpStatus.OK) + } + data class PMTF(val common : CommonFilters, val paymentFilters: PaymentFilters) + fun getAll(ctx : Context){ + val filters = ctx.bodyAsClass() + val payments = searchPayments(filters.common, filters.paymentFilters) + println(payments) + val excel = ctx.queryParam("excel") + if (excel !== null) { +// exportPayments(payments) + val inputStream = FileInputStream("./excel/Payments.xls") + ctx.result(inputStream).status(HttpStatus.OK) + } else { + ctx.json(payments).status(HttpStatus.OK) + } + } + fun update(ctx : Context){ + val id = ctx.pathParam("id").toLong() + val pmt = + database.find(Payment::class.java, id) ?: throw NotFoundResponse("payment not found for $id") + val updatedPayment = ctx.bodyAsClass() + pmt.patchValues(updatedPayment) + pmt.update() + ctx.json(pmt).status(HttpStatus.OK) + } +} +object InvoiceCtrl { + fun create(ctx : Context){ + val invoice = ctx.bodyAsClass() + database.save(invoice) + ctx.json(invoice).status(HttpStatus.CREATED) + } + fun get(ctx : Context){ + val id = ctx.pathParam("id").toLong() + val invoice = database.find(Invoice::class.java, id) + ?: throw NotFoundResponse("No invoice found with id $id") + ctx.json(invoice).status(HttpStatus.OK) + } + data class INVF(val common: CommonFilters, val invoiceFilters: InvoiceFilters) + fun getAll(ctx : Context){ + val filters = ctx.bodyAsClass() + val invoices = searchInvoices(filters.common, filters.invoiceFilters) + val excel = ctx.queryParam("excel") + if (excel !== null) { +// exportPayments(payments) + val inputStream = FileInputStream("./excel/Invoices.xls") + ctx.result(inputStream).status(HttpStatus.OK) + } else { + ctx.json(invoices).status(HttpStatus.OK) + } + + } + fun update(ctx : Context){ + val id = ctx.pathParam("id").toLong() + val invoice = + database.find(Invoice::class.java, id) ?: throw NotFoundResponse("invoice not found for $id") + val updatedPayment = ctx.bodyAsClass() + invoice.patchValues(updatedPayment) + invoice.update() + ctx.json(invoice).status(HttpStatus.OK) + } + fun getNextNum(ctx : Context){ + val prefix = "INV/" + val cnt = database.find(Invoice::class.java) + .findCount() + .toString() + .padStart(6, '0') + val seq = SequenceNumber(prefix + cnt) + ctx.json(seq).status(HttpStatus.OK) + } +} diff --git a/src/main/kotlin/com/restapi/controllers/Filters.kt b/src/main/kotlin/com/restapi/controllers/Filters.kt index 0852d24..e8473c5 100644 --- a/src/main/kotlin/com/restapi/controllers/Filters.kt +++ b/src/main/kotlin/com/restapi/controllers/Filters.kt @@ -2,6 +2,7 @@ package com.restapi.controllers import com.restapi.domain.* import com.restapi.domain.Session.database +import org.checkerframework.checker.index.qual.LessThan import java.time.LocalDate //constants @@ -83,6 +84,20 @@ data class OutgoingInventoryFilters( val outMode: OutMode = OutMode.ALL ) : CustomFilters +data class InvoiceFilters( + val numLike: String = IGNORE, + val poNumLike: String = IGNORE, + val status: InvoiceStatus = InvoiceStatus.ALL, + val totalAmountExceeds: Double = Double.MIN_VALUE, + val totalAmountLessThan: Double = Double.MAX_VALUE +) : CustomFilters + +data class PaymentFilters( + val refNumberLike: String = IGNORE, + val amountExceeds: Double = Double.MIN_VALUE, + val amountLessThan: Double = Double.MAX_VALUE +) : CustomFilters + fun applyVendorHelper(q: io.ebean.ExpressionList, vids: List?) { if (vids.isNullOrEmpty()) return // q.apply { @@ -216,8 +231,8 @@ fun searchOutgoingInventory( .where() .ilike("mdn", "%" + outgoingInventoryFilters.mdnLike + "%") .ilike("purpose", "%" + outgoingInventoryFilters.purposeLike + "%") - // .ilike("person", "%" + outgoingInventoryFilters.personLike + "%") - //.ilike("vehicle", "%" + outgoingInventoryFilters.vehicleLike + "%") + // .ilike("person", "%" + outgoingInventoryFilters.personLike + "%") + //.ilike("vehicle", "%" + outgoingInventoryFilters.vehicleLike + "%") if (outgoingInventoryFilters.outMode != OutMode.ALL) { q.eq("outMode", outgoingInventoryFilters.outMode) } @@ -225,4 +240,31 @@ fun searchOutgoingInventory( applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) return q.findList() -} \ No newline at end of file +} + +fun searchInvoices(commonFilters: CommonFilters, invoiceFilters: InvoiceFilters): List { + val q = database.find(Invoice::class.java) + .where() + .ilike("number", "%" + invoiceFilters.numLike + "%") + if (invoiceFilters.status != InvoiceStatus.ALL) { + q.eq("status", invoiceFilters.status) + } + applyFromToHelper(q, commonFilters.from, commonFilters.to, "date") + applyVendorHelper(q, commonFilters.vendor) + applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) + return q.findList() +} + +fun searchPayments(commonFilters: CommonFilters, paymentFilters: PaymentFilters): List { + val q = database.find(Payment::class.java) + .where() + .ilike("refNumber", "%" + paymentFilters.refNumberLike + "%") + .ge("amount", paymentFilters.amountExceeds) + .le("amount", paymentFilters.amountLessThan) + applyFromToHelper(q, commonFilters.from, commonFilters.to, "date") + applyVendorHelper(q, commonFilters.vendor) + applySortHelper(q, commonFilters.sortBy, commonFilters.sortAsc) + return q.findList() +} + +//if date is null then fromtoheper drops that/// \ No newline at end of file diff --git a/src/main/kotlin/com/restapi/domain/models.kt b/src/main/kotlin/com/restapi/domain/models.kt index 83860d8..90fc108 100644 --- a/src/main/kotlin/com/restapi/domain/models.kt +++ b/src/main/kotlin/com/restapi/domain/models.kt @@ -259,6 +259,8 @@ open class Vendor : BaseTenantModel() { this.address = updatedVendor.address this.rating = updatedVendor.rating this.contacts = updatedVendor.contacts + this.outstanding = updatedVendor.outstanding + this.asOnWhichDate = updatedVendor.asOnWhichDate } var name: String = "" @@ -266,9 +268,12 @@ open class Vendor : BaseTenantModel() { var gstNumber: String = "" var address: String = "" var rating: Double = 0.0 + var outstanding: Double?=0.0 + var asOnWhichDate: LocalDate?=null @DbJsonB var contacts: List = mutableListOf() + } @Entity @@ -457,4 +462,48 @@ open class OutgoingInventory : BaseTenantModel() { var person: String? = null var vehicle: String? = null +} + +enum class InvoiceStatus{ + PAID_FULL, PAID_SOME, PAID_NONE, ALL +} +@Entity +open class Invoice : BaseTenantModel() { + fun patchValues(updated : Invoice) { + this.date = updated.date + this.number = updated.number + this.totalAmount = updated.totalAmount + this.poNum = updated.poNum + this.products = updated.products + this.vendor = updated.vendor + this.status = updated.status + } + var number: String = "" + var date: LocalDate?=null + var totalAmount : Double=0.0 + var poNum:String?=null + @DbJsonB + var products: List ?= null + @ManyToOne + var vendor: Vendor? = null + @Enumerated(EnumType.STRING) + var status:InvoiceStatus?=null +} + +@Entity +open class Payment : BaseTenantModel() { + fun patchValues(updated : Payment){ + this.refNumber = updated.refNumber + this.amount = updated.amount + this.date = updated.date + this.remark = updated.remark + this.vendor = updated.vendor + } + var refNumber:String="" + var amount:Double=0.0 + var date:LocalDate?=null + var remark:String?= null + + @ManyToOne + var vendor:Vendor?=null } \ No newline at end of file diff --git a/src/main/resources/dbmigration/1.12.sql b/src/main/resources/dbmigration/1.12.sql new file mode 100644 index 0000000..0249480 --- /dev/null +++ b/src/main/resources/dbmigration/1.12.sql @@ -0,0 +1,64 @@ +-- apply changes +create table invoice ( + 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, + date date, + total_amount float not null, + vendor_sys_pk bigint, + 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, + number varchar(255) not null, + po_num varchar(255), + products jsonb, + status varchar(9), + created_by varchar(255) not null, + modified_by varchar(255) not null, + constraint ck_invoice_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')), + constraint ck_invoice_status check ( status in ('PAID_FULL','PAID_SOME','PAID_NONE','ALL')), + constraint pk_invoice primary key (sys_pk) +); + +create table payment ( + 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, + amount float not null, + date date, + vendor_sys_pk bigint, + 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, + ref_number varchar(255) not null, + remark varchar(255), + created_by varchar(255) not null, + modified_by varchar(255) not null, + constraint ck_payment_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')), + constraint pk_payment primary key (sys_pk) +); + +-- apply alter tables +alter table vendor add column if not exists outstanding float; +alter table vendor add column if not exists as_on_which_date date; +-- foreign keys and indices +create index ix_invoice_vendor_sys_pk on invoice (vendor_sys_pk); +alter table invoice add constraint fk_invoice_vendor_sys_pk foreign key (vendor_sys_pk) references vendor (sys_pk) on delete restrict on update restrict; + +create index ix_payment_vendor_sys_pk on payment (vendor_sys_pk); +alter table payment add constraint fk_payment_vendor_sys_pk foreign key (vendor_sys_pk) references vendor (sys_pk) on delete restrict on update restrict; + diff --git a/src/main/resources/dbmigration/model/1.12.model.xml b/src/main/resources/dbmigration/model/1.12.model.xml new file mode 100644 index 0000000..5b5e02c --- /dev/null +++ b/src/main/resources/dbmigration/model/1.12.model.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file