├── src └── main │ ├── resources │ ├── icon.png │ ├── logo.jpg │ ├── nfs.jpg │ ├── jfx-table-view.css │ └── states-and-districts.json │ └── kotlin │ └── com │ └── dashlabs │ └── invoicemanagement │ ├── StateDistrict.kt │ ├── databaseconnection │ ├── AdminTable.kt │ ├── ProductsTable.kt │ ├── TransactionTable.kt │ ├── CustomersTable.kt │ ├── InvoiceTable.kt │ └── Database.kt │ ├── view │ ├── products │ │ ├── Product.kt │ │ ├── ProductsController.kt │ │ └── ProductsView.kt │ ├── customers │ │ ├── OnCustomerSelectedListener.kt │ │ ├── Customer.kt │ │ ├── CustomersController.kt │ │ ├── SearchCustomerView.kt │ │ ├── CustomersView.kt │ │ └── CustomerDetailView.kt │ ├── admin │ │ ├── Admin.kt │ │ ├── ChangePasswordView.kt │ │ ├── AdminModel.kt │ │ └── AdminLoginView.kt │ ├── invoices │ │ ├── Invoice.kt │ │ ├── SearchInvoiceView.kt │ │ ├── InvoicesController.kt │ │ └── InvoicesView.kt │ ├── dashboard │ │ ├── DashboardController.kt │ │ └── DashboardView.kt │ └── TransactionHistoryView.kt │ ├── app │ └── InvoiceApp.kt │ └── InvoiceGenerator.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── vcs.xml ├── misc.xml ├── compiler.xml ├── modules.xml └── modules │ ├── InvoiceBilllingSystem_test.iml │ └── InvoiceBilllingSystem_main.iml ├── InvoiceMgmt.iml ├── InvoiceBilllingSystem.iml ├── .gitignore ├── gradlew.bat └── gradlew /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/InvoiceBilllingSystem/master/src/main/resources/icon.png -------------------------------------------------------------------------------- /src/main/resources/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/InvoiceBilllingSystem/master/src/main/resources/logo.jpg -------------------------------------------------------------------------------- /src/main/resources/nfs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/InvoiceBilllingSystem/master/src/main/resources/nfs.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/InvoiceBilllingSystem/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/StateDistrict.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement 2 | 3 | data class StateDistrict( 4 | val states: List 5 | ) 6 | 7 | data class State( 8 | val districts: List, 9 | val state: String 10 | ) -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Dec 26 21:34:06 IST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/databaseconnection/AdminTable.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.databaseconnection 2 | 3 | import com.j256.ormlite.field.DatabaseField 4 | import com.j256.ormlite.table.DatabaseTable 5 | 6 | @DatabaseTable(tableName = "admin") 7 | class AdminTable { 8 | 9 | @DatabaseField(id = true) 10 | var name: String = "" 11 | 12 | @DatabaseField(canBeNull = false) 13 | var password: String = "" 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/products/Product.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.products 2 | 3 | import tornadofx.* 4 | 5 | class Product { 6 | var name by property() 7 | fun nameProperty() = getProperty(Product::name) 8 | 9 | var search by property() 10 | fun searchProperty() = getProperty(Product::search) 11 | 12 | var amount by property() 13 | fun amountProperty() = getProperty(Product::amount) 14 | 15 | override fun toString() = name 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/customers/OnCustomerSelectedListener.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.customers 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.CustomersTable 4 | import com.dashlabs.invoicemanagement.databaseconnection.ProductsTable 5 | import javafx.collections.ObservableMap 6 | 7 | interface OnCustomerSelectedListener { 8 | fun onCustomerSelected(customersTable: CustomersTable.MeaningfulCustomer) 9 | } 10 | 11 | interface OnProductSelectedListener { 12 | fun onProductSelected(newSelectedProducts: ObservableMap?) 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/admin/Admin.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.admin 2 | 3 | import tornadofx.* 4 | 5 | class Admin { 6 | 7 | constructor(name:String?=null,password:String?=null){ 8 | this.username = name 9 | this.password = password 10 | } 11 | var username by property() 12 | fun nameProperty() = getProperty(Admin::username) 13 | 14 | var password by property() 15 | fun passwordProperty() = getProperty(Admin::password) 16 | 17 | 18 | var newpassword by property() 19 | fun newpasswordProperty() = getProperty(Admin::newpassword) 20 | 21 | override fun toString() = username 22 | } -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /InvoiceMgmt.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /InvoiceBilllingSystem.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/invoices/Invoice.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.invoices 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.CustomersTable 4 | import com.dashlabs.invoicemanagement.databaseconnection.ProductsTable 5 | import javafx.collections.ObservableList 6 | import javafx.collections.ObservableMap 7 | import tornadofx.* 8 | 9 | class Invoice { 10 | var customerId by property() 11 | 12 | var customer by property() 13 | 14 | var productsList by property>() 15 | 16 | var productsPrice by property() 17 | 18 | var creditAmount by property() 19 | 20 | var payableAmount by property() 21 | 22 | override fun toString() = "$customerId" 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/customers/Customer.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.customers 2 | 3 | import tornadofx.* 4 | 5 | class Customer { 6 | var name by property() 7 | fun nameProperty() = getProperty(Customer::name) 8 | 9 | var identity by property() 10 | fun getIdentityProperty() = getProperty(Customer::identity) 11 | 12 | var search by property() 13 | fun searchProperty() = getProperty(Customer::search) 14 | 15 | var address by property() 16 | fun addressProperty() = getProperty(Customer::address) 17 | 18 | var district by property() 19 | fun districtProperty() = getProperty(Customer::district) 20 | 21 | var state by property() 22 | fun stateProperty() = getProperty(Customer::state) 23 | 24 | override fun toString() = name 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/databaseconnection/ProductsTable.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.databaseconnection 2 | 3 | import com.j256.ormlite.field.DatabaseField 4 | import com.j256.ormlite.table.DatabaseTable 5 | 6 | @DatabaseTable(tableName = "products") 7 | class ProductsTable { 8 | 9 | @DatabaseField(generatedId = true) 10 | var productId: Long = 0 11 | 12 | @DatabaseField(canBeNull = false) 13 | var productName: String = "" 14 | 15 | @DatabaseField(canBeNull = false) 16 | var dateCreated: Long = 0L 17 | 18 | @DatabaseField(canBeNull = false) 19 | var dateModified: Long = 0L 20 | 21 | @DatabaseField(canBeNull = false) 22 | var amount: Double = 0.0 23 | 24 | @DatabaseField(defaultValue = "false") 25 | var deleted: Boolean = false 26 | 27 | override fun toString(): String { 28 | return "$productName" 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/databaseconnection/TransactionTable.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.databaseconnection 2 | 3 | import com.j256.ormlite.field.DatabaseField 4 | import com.j256.ormlite.table.DatabaseTable 5 | import java.util.* 6 | 7 | @DatabaseTable(tableName = "transactions") 8 | class TransactionTable { 9 | class MeaningfulTransaction(var transactionDate: String, 10 | var deduction: String) 11 | 12 | @DatabaseField(canBeNull = false) 13 | var customerId: Long = 0 14 | 15 | @DatabaseField(generatedId = true) 16 | var transactionId: Long = 0 17 | 18 | @DatabaseField(canBeNull = false) 19 | var dateCreated: Long = 0L 20 | 21 | @DatabaseField(canBeNull = false) 22 | var deduction: Double = 0.0 23 | 24 | 25 | fun toMeaningfulTransaction(transactionTable: TransactionTable): MeaningfulTransaction { 26 | return MeaningfulTransaction(Date(transactionTable.dateCreated).toString(), 27 | deduction.toString()) 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/dashboard/DashboardController.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.dashboard 2 | 3 | import com.dashlabs.invoicemanagement.view.admin.Admin 4 | import javafx.beans.property.SimpleBooleanProperty 5 | import javafx.beans.property.SimpleStringProperty 6 | import tornadofx.* 7 | 8 | class DashboardController : Controller() { 9 | 10 | val statusProperty = SimpleStringProperty("No Loggedin User") 11 | var status by statusProperty 12 | 13 | 14 | val admingSettingsProperty = SimpleStringProperty("Login (CTRL+L)") 15 | var admingSettings by admingSettingsProperty 16 | 17 | val adminLogin = SimpleBooleanProperty(false) 18 | var isAdminLogin by adminLogin 19 | 20 | lateinit var admin: Admin 21 | 22 | fun adminLoggedin(admin: Admin) { 23 | this.admin = admin 24 | runLater { status = "Welcome ${admin.username} !" } 25 | runLater { admingSettings = "Change Password (CTRL+L)" } 26 | runLater { isAdminLogin = true } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/TransactionHistoryView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.TransactionTable 4 | import com.dashlabs.invoicemanagement.view.invoices.InvoicesController 5 | import javafx.geometry.Insets 6 | import tornadofx.* 7 | 8 | class TransactionHistoryView(customerId: Long) : View("Transaction history") { 9 | 10 | private val invoicesController: InvoicesController by inject() 11 | 12 | override val root = vbox { 13 | invoicesController.getTransactionHistory(customerId) 14 | minWidth = 600.0 15 | minHeight = 600.0 16 | tableview(invoicesController.transactionListObserver) { 17 | vboxConstraints { margin = Insets(20.0) } 18 | stylesheets.add("jfx-table-view.css") 19 | column("Transaction Date", TransactionTable.MeaningfulTransaction::transactionDate) 20 | column("Received Amount", TransactionTable.MeaningfulTransaction::deduction) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/app/InvoiceApp.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.app 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.AdminTable 4 | import com.dashlabs.invoicemanagement.databaseconnection.InvoiceTable 5 | import com.dashlabs.invoicemanagement.view.admin.Admin 6 | import com.dashlabs.invoicemanagement.view.dashboard.DashboardView 7 | import javafx.stage.FileChooser 8 | import tornadofx.* 9 | import java.io.File 10 | import java.nio.file.Files 11 | 12 | class InvoiceApp : App(DashboardView::class) { 13 | var admin: Admin? = null 14 | 15 | fun setUser(it: AdminTable) { 16 | this.admin = Admin(it.name, it.password) 17 | println("Firing event ${it.name}") 18 | this.admin?.let { 19 | fire(AdminLoggedInEvent(it)) 20 | } 21 | } 22 | 23 | class AdminLoggedInEvent(val admin: Admin) : FXEvent(runOn = EventBus.RunOn.ApplicationThread) 24 | } 25 | 26 | fun savePdf(it: InvoiceTable.MeaningfulInvoice, file: File) { 27 | val fileChooser = FileChooser() 28 | fileChooser.initialFileName = "${it.customerId}-${it.invoiceId}-${it.dateModified}.pdf" 29 | val extFilter = FileChooser.ExtensionFilter("PDF files (*.pdf)", "*.pdf") 30 | fileChooser.extensionFilters.add(extFilter) 31 | fileChooser.title = "Save Invoice" 32 | val dest = fileChooser.showSaveDialog(null) 33 | if (dest != null) { 34 | try { 35 | Files.copy(file.toPath(), dest.toPath()) 36 | } catch (ex: Exception) { 37 | 38 | } 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/dictionaries 10 | 11 | # Sensitive or high-churn files: 12 | .idea/**/dataSources/ 13 | .idea/**/dataSources.ids 14 | .idea/**/dataSources.xml 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | 20 | # Gradle: 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # Mongo Explorer plugin: 25 | .idea/**/mongoSettings.xml 26 | 27 | ## File-based project format: 28 | *.iws 29 | 30 | ## Plugin-specific files: 31 | 32 | # IntelliJ 33 | /out/ 34 | 35 | # mpeltonen/sbt-idea plugin 36 | .idea_modules/ 37 | 38 | # JIRA plugin 39 | atlassian-ide-plugin.xml 40 | 41 | # Crashlytics plugin (for Android Studio and IntelliJ) 42 | com_crashlytics_export_strings.xml 43 | crashlytics.properties 44 | crashlytics-build.properties 45 | fabric.properties 46 | ### Gradle template 47 | .gradle 48 | /build/ 49 | 50 | # Ignore Gradle GUI config 51 | gradle-app.setting 52 | 53 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 54 | !gradle-wrapper.jar 55 | 56 | # Cache of project 57 | .gradletasknamecache 58 | 59 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 60 | # gradle/wrapper/gradle-wrapper.properties 61 | 62 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem @rem 3 | @rem Gradle startup script for Windows 4 | @rem 5 | @rem 6 | @rem Set local scope for the variables with windows NT shell 7 | if "%OS%"=="Windows_NT" setlocal 8 | 9 | set DIRNAME=%~dp0 10 | if "%DIRNAME%" == "" set DIRNAME=. 11 | set APP_BASE_NAME=%~n0 12 | set APP_HOME=%DIRNAME% 13 | 14 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 15 | set DEFAULT_JVM_OPTS= 16 | 17 | @rem Find java.exe 18 | if defined JAVA_HOME goto findJavaFromJavaHome 19 | 20 | set JAVA_EXE=java.exe 21 | %JAVA_EXE% -version >NUL 2>&1 22 | if "%ERRORLEVEL%" == "0" goto init 23 | 24 | echo. 25 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 26 | echo. 27 | echo Please set the JAVA_HOME variable in your environment to match the 28 | echo location of your Java installation. 29 | 30 | goto fail 31 | 32 | :findJavaFromJavaHome 33 | set JAVA_HOME=%JAVA_HOME:"=% 34 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 35 | 36 | if exist "%JAVA_EXE%" goto init 37 | 38 | echo. 39 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 40 | echo. 41 | echo Please set the JAVA_HOME variable in your environment to match the 42 | echo location of your Java installation. 43 | 44 | goto fail 45 | 46 | :init 47 | @rem Get command-line arguments, handling Windows variants 48 | 49 | if not "%OS%" == "Windows_NT" goto win9xME_args 50 | 51 | :win9xME_args 52 | @rem Slurp the command line arguments. 53 | set CMD_LINE_ARGS= 54 | set _SKIP=2 55 | 56 | :win9xME_args_slurp 57 | if "x%~1" == "x" goto execute 58 | 59 | set CMD_LINE_ARGS=%* 60 | 61 | :execute 62 | @rem Setup the command line 63 | 64 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 65 | 66 | @rem Execute Gradle 67 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 68 | 69 | :end 70 | @rem End local scope for the variables with windows NT shell 71 | if "%ERRORLEVEL%"=="0" goto mainEnd 72 | 73 | :fail 74 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 75 | rem the _cmd.exe /c_ return code! 76 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 77 | exit /b 1 78 | 79 | :mainEnd 80 | if "%OS%"=="Windows_NT" endlocal 81 | 82 | :omega 83 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/databaseconnection/CustomersTable.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.databaseconnection 2 | 3 | import com.j256.ormlite.field.DatabaseField 4 | import com.j256.ormlite.table.DatabaseTable 5 | 6 | @DatabaseTable(tableName = "customers") 7 | class CustomersTable { 8 | 9 | @DatabaseField(generatedId = true) 10 | var customerId: Long = 0 11 | 12 | @DatabaseField(canBeNull = false) 13 | var customerName: String = "" 14 | 15 | @DatabaseField(canBeNull = false) 16 | var state: String = "" 17 | 18 | @DatabaseField(canBeNull = false) 19 | var district: String = "" 20 | 21 | @DatabaseField(canBeNull = false) 22 | var dateCreated: Long = 0L 23 | 24 | @DatabaseField(canBeNull = false) 25 | var address: String = "" 26 | 27 | @DatabaseField(canBeNull = false) 28 | var dateModified: Long = 0L 29 | 30 | @DatabaseField(defaultValue = "false") 31 | var deleted: Boolean = false 32 | 33 | override fun toString(): String { 34 | return getFormattedCustomer(this) 35 | } 36 | 37 | class MeaningfulCustomer(var customerName: String, var address: String, var state: String, var district: String, var amountDue: String,var customerId: Long){ 38 | override fun toString(): String { 39 | return this.let { 40 | val builder = StringBuilder() 41 | builder.append("Name: ${customerName}") 42 | builder.append("\n") 43 | builder.append("Address: ${address}") 44 | builder.append("\n") 45 | builder.append("State: ${state}") 46 | builder.append("\n") 47 | builder.append("District: ${district}") 48 | return builder.toString() 49 | } 50 | } 51 | } 52 | 53 | private fun getFormattedCustomer(customer: CustomersTable): String { 54 | customer.let { 55 | val builder = StringBuilder() 56 | builder.append("Name: ${customer.customerName}") 57 | builder.append("\n") 58 | builder.append("Address: ${customer.address}") 59 | builder.append("\n") 60 | builder.append("State: ${customer.state}") 61 | builder.append("\n") 62 | builder.append("District: ${customer.district}") 63 | return builder.toString() 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/databaseconnection/InvoiceTable.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.databaseconnection 2 | 3 | import com.j256.ormlite.field.DataType 4 | import com.j256.ormlite.field.DatabaseField 5 | import com.j256.ormlite.table.DatabaseTable 6 | import java.util.* 7 | 8 | @DatabaseTable(tableName = "invoices") 9 | class InvoiceTable { 10 | 11 | @DatabaseField(canBeNull = false) 12 | var customerId: Long = 0 13 | 14 | @DatabaseField(generatedId = true) 15 | var invoiceId: Long = 0 16 | 17 | @DatabaseField(canBeNull = false) 18 | var dateCreated: Long = 0L 19 | 20 | @DatabaseField(canBeNull = false, dataType = DataType.SERIALIZABLE) 21 | var productsPurchased: String = "" 22 | 23 | @DatabaseField(canBeNull = false) 24 | var dateModified: Long = 0L 25 | 26 | @DatabaseField(canBeNull = false) 27 | var amountTotal: Double = 0.0 28 | 29 | @DatabaseField(canBeNull = false) 30 | var outstandingAmount: Double = 0.0 31 | 32 | @DatabaseField(defaultValue = "false") 33 | var deleted: Boolean = false 34 | 35 | override fun toString(): String { 36 | return "$customerId $invoiceId $productsPurchased $dateCreated $dateModified" 37 | } 38 | 39 | fun asMeaningfulInvoice(): MeaningfulInvoice? { 40 | Database.getCustomer(this.customerId)?.let { 41 | return MeaningfulInvoice(this.invoiceId.toString(), 42 | Date(this.dateModified).toString(), 43 | it.customerName, 44 | this.customerId, 45 | this.dateModified, 46 | this.outstandingAmount, 47 | amountTotal, 48 | this.productsPurchased, 49 | this.amountTotal.minus(this.outstandingAmount).toString()) 50 | } ?: kotlin.run { 51 | return null 52 | } 53 | 54 | } 55 | 56 | class MeaningfulInvoice(var invoiceId: String, 57 | var dateCreated: String, 58 | var customerName: String, 59 | var customerId: Long, 60 | var dateModified: Long, 61 | var outstandingAmount: Double, 62 | var amountTotal: Double, 63 | var productsPurchased: String, 64 | var paymentReceived: String) 65 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/admin/ChangePasswordView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.admin 2 | 3 | import io.reactivex.rxjavafx.schedulers.JavaFxScheduler 4 | import io.reactivex.schedulers.Schedulers 5 | import javafx.geometry.Orientation 6 | import javafx.scene.layout.Priority 7 | import tornadofx.* 8 | 9 | class ChangePasswordView(val admin: Admin) : View("ChangePassword") { 10 | 11 | private val adminModel = AdminModel() 12 | 13 | 14 | override val root = form { 15 | fieldset(title, labelPosition = Orientation.VERTICAL) { 16 | field("Current Password") { 17 | textfield(adminModel.username).validator { 18 | if (it.isNullOrBlank()) error("The current password field is required") else null 19 | } 20 | } 21 | field("New Password") { 22 | passwordfield(adminModel.password).validator { 23 | if (it.isNullOrBlank()) error("The password field is required") else null 24 | } 25 | } 26 | 27 | field("Confirm New Password") { 28 | passwordfield(adminModel.newpassword).validator { 29 | if (it.isNullOrBlank()) error("The Confirm new password field is required") else null 30 | } 31 | } 32 | 33 | button("Change Password!") { 34 | hboxConstraints { 35 | marginRight = 20.0 36 | hGrow = Priority.ALWAYS 37 | } 38 | action { 39 | if (adminModel.password.value.equals(adminModel.newpassword.value)) { 40 | changePassword() 41 | } else { 42 | information("New Passwords don't match") 43 | } 44 | } 45 | } 46 | } 47 | 48 | } 49 | 50 | private fun changePassword() { 51 | adminModel.changePassword(admin, adminModel) 52 | .subscribeOn(Schedulers.io()) 53 | .observeOn(JavaFxScheduler.platform()) 54 | .subscribe { t1, t2 -> 55 | t1?.let { 56 | information("Password Changed ${it.name}! Start again!") 57 | System.exit(0) 58 | } 59 | t2?.let { 60 | it.message?.let { it1 -> information(it1) } 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/admin/AdminModel.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.admin 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.AdminTable 4 | import com.dashlabs.invoicemanagement.databaseconnection.Database 5 | import io.reactivex.Single 6 | import tornadofx.* 7 | 8 | class AdminModel : ItemViewModel(Admin()) { 9 | 10 | val username = bind(Admin::nameProperty) 11 | val password = bind(Admin::passwordProperty) 12 | val newpassword = bind(Admin::passwordProperty) 13 | 14 | fun loginUser(): Single { 15 | return Single.create { 16 | try { 17 | commit() 18 | val admin = item 19 | println("Saving ${admin.username}") 20 | val adminTable = Database.checkAdminExists(admin) 21 | adminTable?.let { admin -> 22 | it.onSuccess(admin) 23 | } ?: kotlin.run { 24 | it.onError(Exception("User ${admin.username} doesn't exist")) 25 | } 26 | } catch (ex: Exception) { 27 | it.onError(ex) 28 | } 29 | 30 | } 31 | } 32 | 33 | fun changePassword(adminTable: Admin, adminModel: AdminModel): Single { 34 | return Single.create { 35 | try { 36 | val adminTable = Database.changePassword(adminTable,adminModel) 37 | adminTable?.let { admin -> 38 | it.onSuccess(admin) 39 | } ?: kotlin.run { 40 | it.onError(Exception("User ${adminModel.username} doesn't exist")) 41 | } 42 | } catch (ex: Exception) { 43 | it.onError(ex) 44 | } 45 | 46 | } 47 | } 48 | 49 | fun registerUser(): Single { 50 | return Single.create { 51 | commit() 52 | val admin = item 53 | println("Saving ${admin.username}") 54 | try { 55 | val adminTable = Database.createAdmin(admin) 56 | adminTable?.let { admin -> 57 | it.onSuccess(admin) 58 | } ?: kotlin.run { 59 | it.onError(Exception("User ${admin.username} already exist")) 60 | } 61 | } catch (ex: Exception) { 62 | it.onError(Exception("User ${admin.username} already exist")) 63 | } 64 | 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/products/ProductsController.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.products 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.Database 4 | import com.dashlabs.invoicemanagement.databaseconnection.ProductsTable 5 | import io.reactivex.Single 6 | import io.reactivex.rxjavafx.schedulers.JavaFxScheduler 7 | import io.reactivex.schedulers.Schedulers 8 | import javafx.beans.property.Property 9 | import javafx.beans.property.SimpleListProperty 10 | import javafx.collections.FXCollections 11 | import tornadofx.* 12 | 13 | class ProductsController : Controller() { 14 | 15 | val productsListObserver = SimpleListProperty() 16 | 17 | fun requestForProducts() { 18 | val listOfProducts = Database.listProducts() 19 | runLater { 20 | listOfProducts?.let { 21 | productsListObserver.set(FXCollections.observableArrayList(it)) 22 | } 23 | } 24 | } 25 | 26 | fun searchProduct(search: String) { 27 | Single.create> { 28 | try { 29 | val listOfProducts = Database.listProducts(search = search) 30 | listOfProducts?.let { it1 -> it.onSuccess(it1) } 31 | } catch (ex: Exception) { 32 | it.onError(ex) 33 | } 34 | }.subscribeOn(Schedulers.io()) 35 | .observeOn(JavaFxScheduler.platform()) 36 | .subscribe { t1, t2 -> 37 | t1?.let { 38 | productsListObserver.set(FXCollections.observableArrayList(it)) 39 | } 40 | } 41 | } 42 | 43 | fun addProduct(productName: Property, amountName: Property) { 44 | Single.create { 45 | try { 46 | if (productName.value.isNullOrEmpty() || amountName.value.toDouble() == 0.0) { 47 | it.onError(Exception()) 48 | } else { 49 | val product = Product() 50 | product.name = productName.value 51 | product.amount = amountName.value.toDouble() 52 | Database.createProduct(product)?.let { it1 -> it.onSuccess(it1) } 53 | } 54 | } catch (ex: Exception) { 55 | it.onError(ex) 56 | } 57 | }.subscribeOn(Schedulers.io()) 58 | .observeOn(JavaFxScheduler.platform()) 59 | .subscribe { t1, t2 -> 60 | t1?.let { 61 | productName.value = "" 62 | amountName.value = 0.0 63 | productsListObserver.add(it) 64 | } 65 | } 66 | } 67 | 68 | fun deleteProduct(productId: ProductsTable) { 69 | Single.create { 70 | try { 71 | Database.deleteProduct(productId)?.let { it1 -> it.onSuccess(it1) } 72 | } catch (ex: Exception) { 73 | it.onError(ex) 74 | } 75 | }.subscribeOn(Schedulers.io()) 76 | .observeOn(JavaFxScheduler.platform()) 77 | .subscribe { t1, t2 -> 78 | t1?.let { 79 | requestForProducts() 80 | } 81 | } 82 | } 83 | 84 | } 85 | 86 | class ProductViewModel : ItemViewModel(Product()) { 87 | 88 | val productName = bind(Product::nameProperty) 89 | val amountName = bind(Product::amountProperty) 90 | val searchName = bind(Product::searchProperty) 91 | 92 | } -------------------------------------------------------------------------------- /src/main/resources/jfx-table-view.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | /* 21 | * The following CSS has been adapted from jfx-tree-table-view.css 22 | */ 23 | 24 | /*TODO: Get the scroll bars right.*/ 25 | 26 | .table-view { 27 | -fx-table-color: rgba(82, 100, 174, 0.4); 28 | -fx-table-rippler-color: rgba(82, 100, 174, 0.6); 29 | } 30 | :focused { 31 | -fx-background-color: -fx-table-color, -fx-box-border, -fx-control-inner-background; 32 | -fx-background-insets: -1.4, 0, 1; 33 | -fx-background-radius: 1.4, 0, 0; 34 | /*....*/ 35 | -fx-padding: 1; /* 0.083333em; */ 36 | } 37 | 38 | .table-view:focused .table-row-cell:selected{ 39 | -fx-background-color: -fx-table-color; 40 | -fx-table-cell-border-color: -fx-table-color; 41 | -fx-text-fill: BLACK; 42 | } 43 | 44 | .table-view:focused .table-row-cell:selected .table-cell { 45 | -fx-text-fill: BLACK; 46 | } 47 | 48 | .table-view .column-header, 49 | .table-view .column-header-background, 50 | .table-view .column-header-background .filler { 51 | -fx-background-color: TRANSPARENT; 52 | } 53 | 54 | .table-view .column-header { 55 | -fx-border-width: 0 1 0 1; 56 | -fx-border-color: #F3F3F3; 57 | } 58 | 59 | .table-view .column-header .label { 60 | -fx-text-fill: #949494; 61 | -fx-padding: 16 0 16 0; 62 | } 63 | 64 | .table-view .column-header .arrow, .table-view .column-header .sort-order-dot { 65 | -fx-background-color: #949494; 66 | } 67 | 68 | .table-view .column-header:last-visible { 69 | -fx-border-width: 0 2 0 1; 70 | } 71 | 72 | .table-view .column-header-background { 73 | -fx-border-width: 0 0.0 1 0; 74 | -fx-border-color: #F3F3F3; 75 | } 76 | 77 | .table-view .table-cell { 78 | -fx-border-width: 0 0 0 0; 79 | -fx-padding: 16 0 16 0; 80 | -fx-alignment: top-center; 81 | } 82 | 83 | .table-view .column-overlay { 84 | -fx-background-color: -fx-table-color; 85 | } 86 | 87 | .table-view .column-resize-line, .table-view .column-drag-header { 88 | -fx-background-color: -fx-table-rippler-color; 89 | } 90 | 91 | 92 | .table-row-cell > .disclosure-node > .arrow { 93 | -fx-background-color: -fx-text-fill; 94 | -fx-padding: 0.333333em 0.229em 0.333333em 0.229em; /* 4 */ 95 | -fx-shape: "M 0 -3.5 L 4 0 L 0 3.5 z"; 96 | } 97 | 98 | .table-row-cell .jfx-text-field { 99 | -fx-focus-color: rgb(82, 100, 174); 100 | } 101 | 102 | .table-row-cell .jfx-text-field:error { 103 | -jfx-focus-color: #D34336; 104 | -jfx-unfocus-color: #D34336; 105 | } 106 | 107 | .table-row-cell .jfx-text-field .error-label { 108 | -fx-text-fill: #D34336; 109 | -fx-font-size: 0.75em; 110 | } 111 | 112 | .table-row-cell .jfx-text-field .error-icon { 113 | -fx-fill: #D34336; 114 | -fx-font-size: 1.0em; 115 | } 116 | 117 | .table-row-cell:grouped { 118 | -fx-background-color: rgb(230, 230, 230); 119 | } 120 | 121 | .table-view .menu-item:focused { 122 | -fx-background-color: -fx-table-color; 123 | 124 | } 125 | 126 | .table-view .menu-item .label { 127 | -fx-padding: 5 0 5 0; 128 | } 129 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/customers/CustomersController.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.customers 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.CustomersTable 4 | import com.dashlabs.invoicemanagement.databaseconnection.Database 5 | import com.dashlabs.invoicemanagement.view.invoices.toMeaningFulCustomer 6 | import io.reactivex.Single 7 | import io.reactivex.rxjavafx.schedulers.JavaFxScheduler 8 | import io.reactivex.schedulers.Schedulers 9 | import javafx.beans.property.Property 10 | import javafx.beans.property.SimpleListProperty 11 | import javafx.collections.FXCollections 12 | import tornadofx.* 13 | 14 | class CustomersController : Controller() { 15 | 16 | val customersListObserver = SimpleListProperty() 17 | 18 | fun requestForCustomers() { 19 | val listOfCustomers = Database.listCustomers()?.map { it.toMeaningFulCustomer() } 20 | runLater { 21 | listOfCustomers?.let { 22 | customersListObserver.set(FXCollections.observableArrayList(it)) 23 | } 24 | } 25 | } 26 | 27 | fun searchProduct(search: String) { 28 | Single.create> { 29 | try { 30 | val listOfCustomers = Database.listCustomers(search = search) 31 | listOfCustomers?.let { it1 -> it.onSuccess(it1.map { it.toMeaningFulCustomer() }) } 32 | } catch (ex: Exception) { 33 | it.onError(ex) 34 | } 35 | }.subscribeOn(Schedulers.io()) 36 | .observeOn(JavaFxScheduler.platform()) 37 | .subscribe { t1, t2 -> 38 | t1?.let { 39 | customersListObserver.set(FXCollections.observableArrayList(it)) 40 | } 41 | } 42 | } 43 | 44 | fun addCustomer(customerName: Property, address: Property, state: Property, district: Property) { 45 | Single.create { 46 | try { 47 | if (customerName.value.isNullOrEmpty() || address.value.isNullOrEmpty()|| state.value.isNullOrEmpty() || district.value.isNullOrEmpty()) { 48 | it.onError(Exception()) 49 | } else { 50 | val customer = Customer() 51 | customer.name = customerName.value 52 | customer.address = address.value 53 | customer.state = state.value 54 | customer.district = district.value 55 | Database.createCustomer(customer)?.let { it1 -> it.onSuccess(it1) } 56 | } 57 | } catch (ex: Exception) { 58 | it.onError(ex) 59 | } 60 | }.subscribeOn(Schedulers.io()) 61 | .observeOn(JavaFxScheduler.platform()) 62 | .subscribe { t1, t2 -> 63 | t1?.let { 64 | customersListObserver.add(it.toMeaningFulCustomer()) 65 | print(it) 66 | } 67 | t2?.let { 68 | print(it) 69 | it.message?.let { it1 -> warning(it1).show() } 70 | } 71 | } 72 | } 73 | 74 | fun deleteCustomer(item: CustomersTable.MeaningfulCustomer) { 75 | Single.fromCallable { 76 | try { 77 | Database.deleteCustomer(item.customerId) 78 | } catch (ex: Exception) { 79 | throw ex 80 | } 81 | }.subscribeOn(Schedulers.io()) 82 | .observeOn(JavaFxScheduler.platform()) 83 | .subscribe { t1, t2 -> 84 | t1?.let { 85 | requestForCustomers() 86 | } 87 | t2?.let { 88 | print(it) 89 | it.message?.let { it1 -> warning(it1).show() } 90 | } 91 | } 92 | } 93 | 94 | } 95 | 96 | class CustomerViewModel : ItemViewModel(Customer()) { 97 | val customerName = bind(Customer::nameProperty) 98 | val searchName = bind(Customer::searchProperty) 99 | val address = bind(Customer::address) 100 | val state = bind(Customer::state) 101 | val district = bind(Customer::districtProperty) 102 | } -------------------------------------------------------------------------------- /.idea/modules/InvoiceBilllingSystem_test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 19 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.idea/modules/InvoiceBilllingSystem_main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 20 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/admin/AdminLoginView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.admin 2 | 3 | import com.dashlabs.invoicemanagement.app.InvoiceApp 4 | import com.dashlabs.invoicemanagement.databaseconnection.AdminTable 5 | import com.dashlabs.invoicemanagement.databaseconnection.Database 6 | import io.reactivex.rxjavafx.schedulers.JavaFxScheduler 7 | import io.reactivex.schedulers.Schedulers 8 | import javafx.application.Platform 9 | import javafx.geometry.Orientation 10 | import javafx.scene.Node 11 | import javafx.scene.control.Button 12 | import javafx.scene.input.KeyCode 13 | import javafx.scene.input.KeyCombination 14 | import javafx.scene.layout.HBox 15 | import javafx.scene.layout.Priority 16 | import org.fxmisc.wellbehaved.event.EventPattern 17 | import org.fxmisc.wellbehaved.event.InputMap 18 | import org.fxmisc.wellbehaved.event.Nodes 19 | import tornadofx.* 20 | 21 | class AdminLoginView : View("Admin Login!") { 22 | 23 | private val adminModel = AdminModel() 24 | 25 | private lateinit var registerLayout: HBox 26 | 27 | private lateinit var userName: Node 28 | private lateinit var registerButton: Button 29 | 30 | override fun onDock() { 31 | super.onDock() 32 | Platform.runLater { 33 | repeatFocus(userName) 34 | } 35 | } 36 | 37 | private fun repeatFocus(node: Node) { 38 | Platform.runLater { 39 | if (!node.isFocused()) { 40 | node.requestFocus() 41 | repeatFocus(node) 42 | } 43 | } 44 | } 45 | 46 | override val root = hbox { 47 | Nodes.addInputMap(this, InputMap.sequence( 48 | InputMap.consume(EventPattern.keyPressed(KeyCode.R, KeyCombination.SHIFT_DOWN)) { e -> 49 | registerButton.isVisible = !registerButton.isVisible 50 | } 51 | )) 52 | form { 53 | fieldset(title, labelPosition = Orientation.VERTICAL) { 54 | field("Admin Username") { 55 | textfield(adminModel.username){ 56 | this@AdminLoginView.userName = this 57 | }.validator { 58 | if (it.isNullOrBlank()) error("The username field is required") else null 59 | } 60 | } 61 | field("Admin Password") { 62 | passwordfield(adminModel.password).validator { 63 | if (it.isNullOrBlank()) error("The password field is required") else null 64 | } 65 | this.setOnKeyPressed { 66 | when (it.code) { 67 | KeyCode.ENTER -> { 68 | loginNow() 69 | } 70 | } 71 | } 72 | } 73 | 74 | hbox { 75 | this@AdminLoginView.registerLayout = this 76 | button("Login!") { 77 | hboxConstraints { 78 | marginRight = 20.0 79 | hGrow = Priority.ALWAYS 80 | } 81 | action { 82 | loginNow() 83 | } 84 | } 85 | 86 | button("Register Admin") { 87 | this@AdminLoginView.registerButton = this 88 | isVisible = false 89 | hboxConstraints { 90 | marginRight = 20.0 91 | hGrow = Priority.ALWAYS 92 | } 93 | action { 94 | adminModel.registerUser() 95 | .subscribeOn(Schedulers.io()) 96 | .observeOn(JavaFxScheduler.platform()) 97 | .subscribe { t1, t2 -> 98 | t1?.let { 99 | information("Registered User! ${it.name}") 100 | onLoginDashboard(it) 101 | } 102 | t2?.let { 103 | it.message?.let { it1 -> information(it1) } 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | 114 | private fun loginNow() { 115 | adminModel.loginUser() 116 | .subscribeOn(Schedulers.io()) 117 | .observeOn(JavaFxScheduler.platform()) 118 | .subscribe { t1, t2 -> 119 | t1?.let { 120 | information("Logged In! ${it.name}") 121 | onLoginDashboard(it) 122 | } 123 | t2?.let { 124 | it.message?.let { it1 -> information(it1) } 125 | } 126 | } 127 | } 128 | 129 | private fun onLoginDashboard(it: AdminTable) { 130 | val myApp = app as InvoiceApp 131 | myApp.setUser(it) 132 | find { 133 | this.close() 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/customers/SearchCustomerView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.customers 2 | 3 | import com.dashlabs.invoicemanagement.State 4 | import com.dashlabs.invoicemanagement.StateDistrict 5 | import com.dashlabs.invoicemanagement.databaseconnection.CustomersTable 6 | import com.dashlabs.invoicemanagement.databaseconnection.Database 7 | import com.dashlabs.invoicemanagement.view.invoices.InvoicesController 8 | import com.google.gson.Gson 9 | import io.reactivex.Single 10 | import io.reactivex.rxjavafx.schedulers.JavaFxScheduler 11 | import io.reactivex.schedulers.Schedulers 12 | import javafx.geometry.Insets 13 | import javafx.geometry.Pos 14 | import javafx.scene.control.TableView 15 | import javafx.scene.input.KeyCode 16 | import org.fxmisc.wellbehaved.event.EventPattern 17 | import org.fxmisc.wellbehaved.event.InputMap 18 | import org.fxmisc.wellbehaved.event.Nodes 19 | import tornadofx.* 20 | import javax.json.Json 21 | 22 | class SearchCustomerView : View("Search Customers") { 23 | private val invoicesController: InvoicesController by inject() 24 | private val viewModel = SerchInvoiceViewModel() 25 | val state = getStates() 26 | private var districtView: Field? = null 27 | 28 | private fun getStates(): List { 29 | val stream = javaClass.getResourceAsStream("/states-and-districts.json") 30 | val state = Gson().fromJson(Json.createReader(stream).readObject().toPrettyString(), StateDistrict::class.java) 31 | return state.states 32 | } 33 | 34 | override val root = vbox { 35 | Nodes.addInputMap(this, InputMap.sequence( 36 | InputMap.consume(EventPattern.keyPressed(KeyCode.ENTER)) { e -> 37 | searchCustomers() 38 | } 39 | )) 40 | 41 | 42 | invoicesController.customersListObservable.addListener { observable, oldValue, newValue -> 43 | totalBalanceByRegion() 44 | } 45 | hbox { 46 | // search by region 47 | vbox { 48 | label { 49 | text = "Search for invoice by region" 50 | vboxConstraints { margin = Insets(10.0) } 51 | } 52 | form { 53 | fieldset { 54 | field("State") { 55 | combobox(viewModel.state, state.map { it.state }) { 56 | selectionModel.selectedIndex 57 | }.validator { 58 | if (it.isNullOrBlank()) error("Please select the state") else null 59 | } 60 | } 61 | 62 | viewModel.state.onChange { 63 | this@SearchCustomerView.districtView?.let { 64 | viewModel.district.value = null 65 | it.removeFromParent() 66 | } 67 | getDistrictView(this@fieldset) 68 | } 69 | } 70 | } 71 | 72 | form { 73 | fieldset { 74 | field("Address") { 75 | textfield(viewModel.address) 76 | } 77 | } 78 | } 79 | 80 | button("Search") { 81 | vboxConstraints { margin = Insets(10.0) } 82 | alignment = Pos.BOTTOM_RIGHT 83 | setOnMouseClicked { 84 | searchCustomers() 85 | } 86 | } 87 | 88 | label(viewModel.totalPrice) { 89 | vboxConstraints { margin = Insets(10.0) } 90 | style = "-fx-font-weight: bold" 91 | } 92 | 93 | } 94 | } 95 | 96 | vbox { 97 | this.add(getCustomersView()) 98 | } 99 | } 100 | 101 | private fun searchCustomers() { 102 | viewModel.totalPrice.value = null 103 | invoicesController.searchCustomers(viewModel.state.value, viewModel.district.value, viewModel.address.value 104 | ?: "") 105 | } 106 | 107 | private fun totalBalanceByRegion() { 108 | Single.fromCallable { 109 | var totalPrice = 0.0 110 | invoicesController.customersListObservable.value.map { 111 | Database.listInvoices(it.customerId) 112 | }.forEach { 113 | it?.map { it.outstandingAmount }?.sum()?.let { 114 | totalPrice += it 115 | } 116 | } 117 | totalPrice 118 | }.subscribeOn(Schedulers.io()) 119 | .observeOn(JavaFxScheduler.platform()).subscribe { t1, t2 -> 120 | t1?.let { 121 | viewModel.totalPrice.value = "Total amount for the region $it" 122 | } 123 | } 124 | } 125 | 126 | private fun getCustomersView(): TableView { 127 | return tableview(invoicesController.customersListObservable) { 128 | columnResizePolicy = SmartResize.POLICY 129 | stylesheets.add("jfx-table-view.css") 130 | 131 | vboxConstraints { margin = Insets(20.0) } 132 | column("Customer Name", CustomersTable.MeaningfulCustomer::customerName).remainingWidth() 133 | column("Amount Due", CustomersTable.MeaningfulCustomer::amountDue).remainingWidth() 134 | column("Address", CustomersTable.MeaningfulCustomer::address).remainingWidth() 135 | column("State", CustomersTable.MeaningfulCustomer::state).remainingWidth() 136 | column("District", CustomersTable.MeaningfulCustomer::district).remainingWidth() 137 | onDoubleClick { 138 | this.selectedItem?.let { 139 | CustomerDetailView(it).openWindow() 140 | } 141 | } 142 | } 143 | } 144 | 145 | private fun getDistrictView(it: Fieldset) { 146 | this@SearchCustomerView.districtView = field("District") { 147 | tag = "district" 148 | val state = state.firstOrNull { it.state.equals(viewModel.state.value) } 149 | state?.let { 150 | combobox(viewModel.district, state.districts).validator { 151 | if (it.isNullOrBlank()) error("Please select the District!") else null 152 | } 153 | } 154 | } 155 | this@SearchCustomerView.districtView?.addTo(it) 156 | } 157 | } 158 | 159 | 160 | class SerchInvoiceViewModel : ItemViewModel(Customer()) { 161 | val state = bind(Customer::state) 162 | val district = bind(Customer::districtProperty) 163 | val totalPrice = bind(Customer::search) 164 | val address = bind(Customer::address) 165 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/invoices/SearchInvoiceView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.invoices 2 | 3 | import com.dashlabs.invoicemanagement.InvoiceGenerator 4 | import com.dashlabs.invoicemanagement.app.savePdf 5 | import com.dashlabs.invoicemanagement.databaseconnection.CustomersTable 6 | import com.dashlabs.invoicemanagement.databaseconnection.Database 7 | import com.dashlabs.invoicemanagement.databaseconnection.InvoiceTable 8 | import com.dashlabs.invoicemanagement.databaseconnection.ProductsTable 9 | import com.google.gson.Gson 10 | import com.google.gson.reflect.TypeToken 11 | import io.reactivex.Single 12 | import io.reactivex.rxjavafx.schedulers.JavaFxScheduler 13 | import io.reactivex.schedulers.Schedulers 14 | import javafx.geometry.Insets 15 | import javafx.geometry.Pos 16 | import javafx.scene.control.* 17 | import javafx.scene.input.KeyCode 18 | import tornadofx.* 19 | import java.awt.Desktop 20 | import java.io.File 21 | import java.time.LocalDate 22 | import java.time.LocalTime 23 | import java.util.* 24 | 25 | class SearchInvoiceView : View("Search Invoices") { 26 | private var datePicker: DatePicker? = null 27 | private val invoicesController: InvoicesController by inject() 28 | 29 | 30 | override val root = vbox { 31 | hbox { 32 | // search by date 33 | vbox { 34 | label { 35 | text = "Search for invoice created by date" 36 | vboxConstraints { margin = Insets(10.0) } 37 | } 38 | datepicker { 39 | this@SearchInvoiceView.datePicker = this 40 | vboxConstraints { margin = Insets(10.0) } 41 | value = LocalDate.now() 42 | } 43 | 44 | button("Search") { 45 | vboxConstraints { margin = Insets(10.0) } 46 | alignment = Pos.BOTTOM_RIGHT 47 | setOnMouseClicked { 48 | datePicker?.value?.let { 49 | val startTime = it.atTime(LocalTime.MIN) 50 | val endTime = it.atTime(LocalTime.MAX) 51 | invoicesController.searchInvoice(startTime, endTime) 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | vbox { 59 | this.add(getInvoiceView()) 60 | } 61 | } 62 | 63 | private fun getInvoiceView(): TableView { 64 | return tableview(invoicesController.invoicesListObserver) { 65 | columnResizePolicy = SmartResize.POLICY 66 | stylesheets.add("jfx-table-view.css") 67 | vboxConstraints { margin = Insets(20.0) } 68 | tag = "invoices" 69 | column("Customer name", InvoiceTable.MeaningfulInvoice::customerName).remainingWidth() 70 | column("Bill Date", InvoiceTable.MeaningfulInvoice::dateCreated).remainingWidth() 71 | column("Amount Due", InvoiceTable.MeaningfulInvoice::outstandingAmount).remainingWidth() 72 | column("Bill Amount", InvoiceTable.MeaningfulInvoice::amountTotal).remainingWidth() 73 | onDoubleClick { 74 | showInvoiceDetails(invoicesController.invoicesListObserver.value[this.selectedCell!!.row]) 75 | } 76 | setOnKeyPressed { 77 | this.selectedItem?.let { item -> 78 | when (it.code) { 79 | KeyCode.BACK_SPACE, KeyCode.DELETE -> { 80 | alert(Alert.AlertType.CONFIRMATION, "Delete Invoice with outstanding amounting ${item.outstandingAmount} ?", 81 | "Remove invoice for ${item.customerName} ?", 82 | buttons = *arrayOf(ButtonType.YES, ButtonType.CANCEL), owner = currentWindow, title = "Hey!") { 83 | if (it == ButtonType.YES) { 84 | invoicesController.deleteInvoice(item) 85 | } 86 | } 87 | } 88 | else -> { 89 | 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | 98 | private fun showInvoiceDetails(selectedItem: InvoiceTable.MeaningfulInvoice?) { 99 | selectedItem?.let { 100 | Single.fromCallable { 101 | if (selectedItem.productsPurchased.contains("\"third\"")) { 102 | val list = Gson().fromJson>>( 103 | selectedItem.productsPurchased, 104 | object : TypeToken>>() {}.type) 105 | val file = File("~/invoicedatabase", "temp.pdf") 106 | file.delete() 107 | file.createNewFile() 108 | InvoiceGenerator.makePDF(file, selectedItem, list.map { Triple(it.first, it.second, it.third) }.toMutableList()) 109 | file 110 | } else { 111 | val list = Gson().fromJson>>( 112 | selectedItem.productsPurchased, 113 | object : TypeToken>>() {}.type) 114 | val file = File("~/invoicedatabase", "temp.pdf") 115 | file.delete() 116 | file.createNewFile() 117 | InvoiceGenerator.makePDF(file, selectedItem, list.map { Triple(it.first, 0.0, it.second) }.toMutableList()) 118 | file 119 | } 120 | }.subscribeOn(Schedulers.io()).observeOn(JavaFxScheduler.platform()).subscribe { t1, t2 -> 121 | t1?.let { 122 | alert(Alert.AlertType.CONFIRMATION, "Invoice Information", 123 | "View invoice or Save It", 124 | buttons = *arrayOf(ButtonType("Save", ButtonBar.ButtonData.BACK_PREVIOUS), 125 | ButtonType("Preview", ButtonBar.ButtonData.NEXT_FORWARD), 126 | ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE)), owner = currentWindow, title = "Hey!") { 127 | if (it.buttonData == ButtonBar.ButtonData.NEXT_FORWARD) { 128 | try { 129 | Desktop.getDesktop().browse(t1.toURI()) 130 | } catch (e: Exception) { 131 | e.printStackTrace() 132 | } 133 | } else if (it.buttonData == ButtonBar.ButtonData.BACK_PREVIOUS) { 134 | savePdf(selectedItem, t1) 135 | } else if (ButtonBar.ButtonData.CANCEL_CLOSE == it.buttonData) { 136 | 137 | } 138 | } 139 | } 140 | 141 | } 142 | } 143 | } 144 | 145 | fun requestForInvoices() { 146 | invoicesController.requestForInvoices() 147 | } 148 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/dashboard/DashboardView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.dashboard 2 | 3 | import com.dashlabs.invoicemanagement.app.InvoiceApp 4 | import com.dashlabs.invoicemanagement.view.admin.AdminLoginView 5 | import com.dashlabs.invoicemanagement.view.admin.ChangePasswordView 6 | import com.dashlabs.invoicemanagement.view.customers.CustomersView 7 | import com.dashlabs.invoicemanagement.view.customers.SearchCustomerView 8 | import com.dashlabs.invoicemanagement.view.invoices.InvoicesView 9 | import com.dashlabs.invoicemanagement.view.invoices.SearchInvoiceView 10 | import com.dashlabs.invoicemanagement.view.products.ProductsView 11 | import com.jfoenix.controls.JFXButton 12 | import com.jfoenix.controls.JFXTabPane 13 | import javafx.application.Platform 14 | import javafx.geometry.Insets 15 | import javafx.geometry.Pos 16 | import javafx.scene.control.TabPane 17 | import javafx.scene.input.KeyCode 18 | import javafx.scene.input.KeyCombination 19 | import javafx.scene.layout.HBox 20 | import javafx.scene.layout.Priority 21 | import javafx.scene.layout.VBox 22 | import javafx.stage.Screen 23 | import org.fxmisc.wellbehaved.event.EventPattern 24 | import org.fxmisc.wellbehaved.event.InputMap 25 | import org.fxmisc.wellbehaved.event.Nodes 26 | import tornadofx.* 27 | 28 | 29 | class DashboardView : View("Dashboard") { 30 | 31 | private val dashboardController: DashboardController by inject() 32 | private var tabNames: ArrayList = arrayListOf("Products [CTRL+1]", "Customers [CTRL+2]", "Create Invoice [CTRL+3]", "Invoice Search [CTRL+4]", "Area Wise Customer's Search[CTRL+5]") 33 | private var productsView: ProductsView = ProductsView() 34 | private var customersView: CustomersView = CustomersView() 35 | private var invoicesView = InvoicesView() 36 | private var invoicesSearchView = SearchInvoiceView() 37 | private var customerSearch = SearchCustomerView() 38 | private var carHammeredTimes = 0 39 | 40 | init { 41 | subscribe { 42 | dashboardController.adminLoggedin(it.admin) 43 | productsView.requestForProducts() 44 | customersView.requestForCustomers() 45 | invoicesView.requestForInvoices() 46 | invoicesSearchView.requestForInvoices() 47 | println("User logged in! ${it.admin.username}") 48 | } 49 | } 50 | 51 | override fun onDock() { 52 | super.onDock() 53 | modalStage?.isMaximized = true 54 | } 55 | 56 | override val root = stackpane { 57 | 58 | Nodes.addInputMap(this, InputMap.sequence( 59 | InputMap.consume(EventPattern.keyPressed(KeyCode.L, KeyCombination.CONTROL_DOWN)) { e -> 60 | if (!isAdminLoggedIn()) { 61 | openInternalWindow(AdminLoginView::class) 62 | } else { 63 | ChangePasswordView(dashboardController.admin).openWindow() 64 | } 65 | }, 66 | InputMap.consume(EventPattern.keyPressed(KeyCode.C, KeyCombination.CONTROL_DOWN)) { e -> 67 | if (!isAdminLoggedIn()) { 68 | openInternalWindow(AdminLoginView::class) 69 | } else { 70 | ChangePasswordView(dashboardController.admin).openWindow() 71 | } 72 | }, InputMap.consume(EventPattern.keyPressed(KeyCode.DIGIT1, KeyCombination.CONTROL_DOWN)) { e -> 73 | tabPane.tabs[0].select() 74 | }, 75 | InputMap.consume(EventPattern.keyPressed(KeyCode.DIGIT2, KeyCombination.CONTROL_DOWN)) { e -> 76 | tabPane.tabs[1].select() 77 | }, 78 | InputMap.consume(EventPattern.keyPressed(KeyCode.DIGIT3, KeyCombination.CONTROL_DOWN)) { e -> 79 | tabPane.tabs[2].select() 80 | }, 81 | InputMap.consume(EventPattern.keyPressed(KeyCode.DIGIT4, KeyCombination.CONTROL_DOWN)) { e -> 82 | tabPane.tabs[3].select() 83 | }, 84 | InputMap.consume(EventPattern.keyPressed(KeyCode.DIGIT5, KeyCombination.CONTROL_DOWN)) { e -> 85 | tabPane.tabs[4].select() 86 | }, 87 | InputMap.consume(EventPattern.keyPressed(KeyCode.C, KeyCombination.SHIFT_DOWN)) { e -> 88 | invoicesView.openCustomersView() 89 | }, 90 | InputMap.consume(EventPattern.keyPressed(KeyCode.P, KeyCombination.SHIFT_DOWN)) { e -> 91 | invoicesView.openProductsView() 92 | } 93 | )) 94 | 95 | this.add(getMainView()) 96 | 97 | this.add(imageview("nfs.jpg", lazyload = true) { 98 | Platform.runLater { 99 | this.fitWidthProperty().bind(this@stackpane.scene.widthProperty()) 100 | this.fitHeightProperty().bind(this@stackpane.scene.heightProperty()) 101 | } 102 | 103 | setOnMouseClicked { 104 | carHammeredTimes++ 105 | if (carHammeredTimes > 10) { 106 | this.isVisible = false 107 | } 108 | } 109 | }) 110 | 111 | } 112 | 113 | private lateinit var tabPane: JFXTabPane 114 | 115 | private fun getMainView(): VBox { 116 | return vbox { 117 | Platform.runLater { 118 | this.minHeight = Screen.getPrimary().visualBounds.height 119 | this.minWidth = Screen.getPrimary().visualBounds.width 120 | } 121 | 122 | tag = "mainview" 123 | this.vgrow = Priority.ALWAYS 124 | hbox { 125 | label(dashboardController.statusProperty) { 126 | alignment = Pos.TOP_RIGHT 127 | paddingAll = 10.0 128 | HBox.setMargin(this, Insets(10.0)) 129 | } 130 | 131 | this += JFXButton().apply { 132 | this.text(dashboardController.admingSettingsProperty) { 133 | 134 | } 135 | style = " -jfx-button-type: RAISED;\n" + 136 | " -fx-background-color: #2196f3;\n" + 137 | " -fx-text-fill: white;" 138 | HBox.setMargin(this, Insets(10.0)) 139 | hboxConstraints { 140 | marginRight = 20.0 141 | hGrow = Priority.ALWAYS 142 | } 143 | setOnMouseClicked { 144 | if (!isAdminLoggedIn()) { 145 | openInternalWindow(AdminLoginView::class) 146 | } else { 147 | ChangePasswordView(dashboardController.admin).openWindow() 148 | } 149 | } 150 | } 151 | } 152 | 153 | this += JFXTabPane().apply { 154 | this@DashboardView.tabPane = this 155 | tabClosingPolicy = TabPane.TabClosingPolicy.UNAVAILABLE 156 | enableWhen(dashboardController.adminLogin) 157 | 158 | tab(tabNames[0]) { 159 | this.add(productsView) 160 | } 161 | 162 | tab(tabNames[1]) { 163 | this.add(customersView) 164 | } 165 | 166 | tab(tabNames[2]) { 167 | this.add(invoicesView) 168 | } 169 | tab(tabNames[3]) { 170 | this.add(invoicesSearchView) 171 | } 172 | tab(tabNames[4]) { 173 | this.add(customerSearch) 174 | } 175 | } 176 | 177 | } 178 | } 179 | 180 | private fun isAdminLoggedIn(): Boolean { 181 | return (app as InvoiceApp).admin != null 182 | } 183 | 184 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/invoices/InvoicesController.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.invoices 2 | 3 | import com.dashlabs.invoicemanagement.InvoiceGenerator 4 | import com.dashlabs.invoicemanagement.app.savePdf 5 | import com.dashlabs.invoicemanagement.databaseconnection.* 6 | import com.dashlabs.invoicemanagement.view.customers.CustomersView 7 | import io.reactivex.Single 8 | import io.reactivex.rxjavafx.schedulers.JavaFxScheduler 9 | import io.reactivex.schedulers.Schedulers 10 | import javafx.beans.property.SimpleListProperty 11 | import javafx.collections.FXCollections 12 | import javafx.collections.ObservableList 13 | import tornadofx.* 14 | import java.io.File 15 | import java.time.LocalDateTime 16 | 17 | class InvoicesController : Controller() { 18 | 19 | class ProductsModel(var productsTable: ProductsTable, 20 | var quantity: String, 21 | var totalAmount: String, 22 | var baseAmount: Double) { 23 | var discount: Double = 0.0 24 | } 25 | 26 | 27 | val invoicesListObserver = SimpleListProperty() 28 | val customersListObservable = SimpleListProperty() 29 | 30 | val transactionListObserver = SimpleListProperty() 31 | 32 | val productsQuanityView = FXCollections.observableArrayList() 33 | 34 | fun requestForInvoices() { 35 | val listOfInvoices = Database.listInvoices() 36 | runLater { 37 | listOfInvoices?.let { 38 | invoicesListObserver.set(FXCollections.observableArrayList(it)) 39 | } 40 | } 41 | } 42 | 43 | 44 | fun updateProductsObserver(productsTable: ObservableList?) { 45 | runLater { 46 | productsTable?.let { 47 | productsQuanityView.setAll(productsTable) 48 | } ?: kotlin.run { 49 | this.productsQuanityView.setAll(FXCollections.observableList(listOf())) 50 | } 51 | } 52 | } 53 | 54 | fun searchCustomers(state: String, district: String, address: String) { 55 | Single.create> { emitter -> 56 | try { 57 | val listOfInvoices = Database.listCustomers(state, district, address) 58 | listOfInvoices?.let { 59 | val customers = listOfInvoices.map { it.toMeaningFulCustomer() } 60 | customers.forEach { 61 | var totalPrice = 0.0 62 | Database.listInvoices(it.customerId)?.map { it.outstandingAmount }?.sum()?.let { 63 | totalPrice += it 64 | } 65 | it.amountDue = totalPrice.toString() 66 | } 67 | emitter.onSuccess(customers) 68 | } 69 | } catch (ex: Exception) { 70 | emitter.onError(ex) 71 | } 72 | }.subscribeOn(Schedulers.io()) 73 | .observeOn(JavaFxScheduler.platform()) 74 | .subscribe { t1, t2 -> 75 | t1?.let { 76 | customersListObservable.set(FXCollections.observableArrayList(it)) 77 | } 78 | } 79 | } 80 | 81 | fun searchInvoice(startTime: LocalDateTime, endTime: LocalDateTime) { 82 | Single.create> { 83 | try { 84 | val listOfInvoices = Database.listInvoices(startTime, endTime) 85 | listOfInvoices?.let { it1 -> it.onSuccess(it1) } 86 | } catch (ex: Exception) { 87 | it.onError(ex) 88 | } 89 | }.subscribeOn(Schedulers.io()) 90 | .observeOn(JavaFxScheduler.platform()) 91 | .subscribe { t1, t2 -> 92 | t1?.let { 93 | invoicesListObserver.set(FXCollections.observableArrayList(it)) 94 | } 95 | } 96 | } 97 | 98 | fun addInvoice(invoiceViewModel: InvoiceViewModel): Single { 99 | val subscription = Single.create { 100 | try { 101 | val invoice = Invoice() 102 | invoice.customerId = invoiceViewModel.customerId.value 103 | invoice.productsList = invoiceViewModel.productsList.value 104 | invoice.creditAmount = invoiceViewModel.leftoverAmount.value 105 | invoice.productsPrice = invoiceViewModel.totalPrice.value 106 | Database.createInvoice(invoice)?.let { it1 -> it.onSuccess(it1) } 107 | } catch (ex: Exception) { 108 | ex.printStackTrace() 109 | it.onError(ex) 110 | } 111 | }.subscribeOn(Schedulers.io()) 112 | .observeOn(JavaFxScheduler.platform()) 113 | 114 | subscription.subscribe { t1, t2 -> 115 | t1?.let { 116 | invoicesListObserver.add(it) 117 | print(it) 118 | invoiceViewModel.clearValues() 119 | Single.fromCallable { 120 | val listproducts = productsQuanityView?.map { Triple(it.productsTable, it.discount, it.quantity.toInt()) }?.toMutableList() 121 | val file = File("~/invoicedatabase", "tempinv.pdf") 122 | InvoiceGenerator.makePDF(file, it, listproducts!!) 123 | file 124 | }.subscribeOn(Schedulers.io()).observeOn(JavaFxScheduler.platform()).subscribe { file, t2 -> 125 | updateProductsObserver(null) 126 | 127 | find { 128 | requestForCustomers() 129 | } 130 | 131 | savePdf(it, file) 132 | } 133 | } 134 | t2?.let { 135 | print(it) 136 | } 137 | } 138 | 139 | return subscription 140 | 141 | } 142 | 143 | fun getCustomerById(customerId: Long): Single { 144 | return Single.fromCallable { 145 | Database.getCustomer(customerId) 146 | }.subscribeOn(Schedulers.io()) 147 | .observeOn(JavaFxScheduler.platform()) 148 | } 149 | 150 | fun getInvoicesForCustomer(customerId: Long) { 151 | val listOfInvoices = Database.listInvoices(customerId) 152 | runLater { 153 | listOfInvoices?.let { 154 | invoicesListObserver.set(FXCollections.observableArrayList(it)) 155 | } 156 | } 157 | } 158 | 159 | fun getTransactionHistory(customerId: Long) { 160 | val listOfInvoices = Database.listTransactions(customerId) 161 | runLater { 162 | listOfInvoices?.let { 163 | transactionListObserver.set(FXCollections.observableArrayList(it)) 164 | } 165 | } 166 | } 167 | 168 | fun deleteInvoice(item: InvoiceTable.MeaningfulInvoice) { 169 | Single.fromCallable { 170 | try { 171 | Database.deleteInvoice(item.invoiceId.toLong()) 172 | } catch (ex: Exception) { 173 | throw ex 174 | } 175 | }.subscribeOn(Schedulers.io()) 176 | .observeOn(JavaFxScheduler.platform()) 177 | .subscribe { t1, t2 -> 178 | t1?.let { 179 | requestForInvoices() 180 | } 181 | t2?.let { 182 | print(it) 183 | it.message?.let { it1 -> warning(it1).show() } 184 | } 185 | } 186 | } 187 | 188 | } 189 | 190 | fun CustomersTable.toMeaningFulCustomer(): CustomersTable.MeaningfulCustomer { 191 | return CustomersTable.MeaningfulCustomer(this.customerName, this.address, this.state, this.district, "", this.customerId) 192 | } 193 | 194 | class InvoiceViewModel : ItemViewModel(Invoice()) { 195 | fun clearValues() { 196 | customerId.value = null 197 | productsList.value = null 198 | customer.value = null 199 | payingAmount.value = null 200 | totalPrice.value = null 201 | leftoverAmount.value = null 202 | } 203 | 204 | val customerId = bind(Invoice::customerId) 205 | val productsList = bind(Invoice::productsList) 206 | var customer = bind(Invoice::customer) 207 | var totalPrice = bind(Invoice::productsPrice) 208 | var payingAmount = bind(Invoice::creditAmount) 209 | var leftoverAmount = bind(Invoice::payableAmount) 210 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/products/ProductsView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.products 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.ProductsTable 4 | import com.dashlabs.invoicemanagement.view.customers.OnProductSelectedListener 5 | import com.jfoenix.controls.JFXButton 6 | import javafx.application.Platform 7 | import javafx.collections.FXCollections 8 | import javafx.geometry.Insets 9 | import javafx.geometry.Pos 10 | import javafx.scene.Node 11 | import javafx.scene.control.Alert 12 | import javafx.scene.control.ButtonType 13 | import javafx.scene.control.TableView 14 | import javafx.scene.control.TextField 15 | import javafx.scene.input.KeyCode 16 | import javafx.scene.layout.VBox 17 | import javafx.stage.Screen 18 | import tornadofx.* 19 | 20 | 21 | class ProductsView(private val onProductSelectedListener: OnProductSelectedListener? = null) : View("Products View") { 22 | 23 | private val productViewModel = ProductViewModel() 24 | private val productsController: ProductsController by inject() 25 | private lateinit var prodSearch: TextField 26 | 27 | override fun onDock() { 28 | super.onDock() 29 | repeatFocus(prodSearch) 30 | } 31 | 32 | 33 | private fun repeatFocus(node: Node) { 34 | Platform.runLater { 35 | if (!node.isFocused) { 36 | node.requestFocus() 37 | repeatFocus(node) 38 | } 39 | } 40 | } 41 | 42 | override val root = hbox { 43 | this.add(getProductsView()) 44 | this.add(getProductsList()) 45 | } 46 | 47 | private fun getProductsList(): VBox { 48 | return vbox { 49 | onProductSelectedListener?.let { 50 | 51 | } ?: kotlin.run { 52 | Platform.runLater { 53 | this.minWidth = Screen.getPrimary().visualBounds.width.div(3) 54 | } 55 | } 56 | onProductSelectedListener?.let { 57 | val button = JFXButton("Select Products").apply { 58 | style = " -jfx-button-type: RAISED;\n" + 59 | " -fx-background-color: #2196f3;\n" + 60 | " -fx-text-fill: white;" 61 | vboxConstraints { 62 | margin = Insets(20.0) 63 | } 64 | setOnMouseClicked { 65 | sendSelectedProducts() 66 | } 67 | } 68 | 69 | 70 | this.add(button) 71 | } 72 | 73 | tableview(productsController.productsListObserver) { 74 | columnResizePolicy = SmartResize.POLICY 75 | stylesheets.add("jfx-table-view.css") 76 | vboxConstraints { margin = Insets(20.0) } 77 | column("ID", ProductsTable::productId).remainingWidth() 78 | column("Product Name", ProductsTable::productName).remainingWidth() 79 | column("Amount", ProductsTable::amount).remainingWidth() 80 | onProductSelectedListener?.let { 81 | multiSelect(enable = true) 82 | this@ProductsView.selectionModel = selectionModel 83 | } 84 | 85 | this.setOnKeyPressed { 86 | when (it.code) { 87 | KeyCode.DELETE, KeyCode.BACK_SPACE -> { 88 | selectedCell?.row?.let { position -> 89 | alert(Alert.AlertType.CONFIRMATION, "Delete Product ?", 90 | "Remove ${selectedItem?.productName} ?", 91 | buttons = *arrayOf(ButtonType.OK, ButtonType.CANCEL), owner = currentWindow, title = "Hey!") { 92 | if (it == ButtonType.OK) { 93 | productsController.deleteProduct(selectedItem!!) 94 | } 95 | } 96 | } 97 | } 98 | KeyCode.ENTER -> { 99 | sendSelectedProducts() 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | private fun sendSelectedProducts() { 108 | selectionModel?.selectedItems.let { 109 | val productsMap = FXCollections.observableHashMap() 110 | it?.forEach { 111 | productsMap[it] = 1 112 | } 113 | onProductSelectedListener?.onProductSelected(productsMap) 114 | } 115 | //currentStage?.close() 116 | } 117 | 118 | private var selectionModel: TableView.TableViewSelectionModel? = null 119 | 120 | private fun getProductsView(): VBox { 121 | return vbox { 122 | hboxConstraints { margin = Insets(20.0) } 123 | vbox { 124 | this.add(getSearchProductForm()) 125 | this.add(getAddProductView()) 126 | } 127 | } 128 | } 129 | 130 | private fun getAddProductView(): VBox { 131 | return vbox { 132 | form { 133 | fieldset { 134 | field("Product Name") { 135 | textfield(productViewModel.productName).validator { 136 | if (it.isNullOrBlank()) error("Please enter product name!") else null 137 | } 138 | } 139 | 140 | field("Amount") { 141 | textfield(productViewModel.amountName) { 142 | this.filterInput { it.controlNewText.isDouble() } 143 | }.validator { 144 | if (it.isNullOrBlank()) error("Please specify amount!") else null 145 | } 146 | 147 | this.setOnKeyPressed { 148 | when (it.code) { 149 | KeyCode.ENTER -> { 150 | productsController.addProduct(productViewModel.productName, productViewModel.amountName) 151 | } 152 | } 153 | } 154 | } 155 | } 156 | 157 | 158 | this += JFXButton("Add Product").apply { 159 | style = " -jfx-button-type: RAISED;\n" + 160 | " -fx-background-color: #2196f3;\n" + 161 | " -fx-text-fill: white;" 162 | setOnMouseClicked { 163 | productsController.addProduct(productViewModel.productName, productViewModel.amountName) 164 | } 165 | } 166 | } 167 | 168 | } 169 | } 170 | 171 | private fun getSearchProductForm(): VBox { 172 | return vbox { 173 | form { 174 | fieldset { 175 | field("Search Products") { 176 | textfield(productViewModel.searchName) { 177 | this@ProductsView.prodSearch = this 178 | }.validator { 179 | if (it.isNullOrBlank()) error("Please enter search Query!") else null 180 | } 181 | 182 | productViewModel.searchName.onChange { 183 | it?.let { 184 | productsController.searchProduct(it) 185 | } ?: run { 186 | requestForProducts() 187 | } 188 | } 189 | } 190 | 191 | this.setOnKeyPressed { 192 | when (it.code) { 193 | KeyCode.ENTER -> { 194 | productsController.searchProduct(productViewModel.searchName.value) 195 | } 196 | } 197 | } 198 | } 199 | 200 | this += JFXButton("Search Product").apply { 201 | style = " -jfx-button-type: RAISED;\n" + 202 | " -fx-background-color: #2196f3;\n" + 203 | " -fx-text-fill: white;" 204 | alignment = Pos.BOTTOM_RIGHT 205 | setOnMouseClicked { 206 | productsController.searchProduct(productViewModel.searchName.value) 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | fun requestForProducts() { 214 | productsController.requestForProducts() 215 | } 216 | 217 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/customers/CustomersView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.customers 2 | 3 | import com.dashlabs.invoicemanagement.State 4 | import com.dashlabs.invoicemanagement.StateDistrict 5 | import com.dashlabs.invoicemanagement.databaseconnection.CustomersTable 6 | import com.google.gson.Gson 7 | import javafx.application.Platform 8 | import javafx.geometry.Insets 9 | import javafx.geometry.Pos 10 | import javafx.scene.Node 11 | import javafx.scene.control.Alert 12 | import javafx.scene.control.ButtonType 13 | import javafx.scene.control.TextField 14 | import javafx.scene.input.KeyCode 15 | import javafx.scene.input.KeyCombination 16 | import javafx.scene.layout.VBox 17 | import javafx.stage.Screen 18 | import org.fxmisc.wellbehaved.event.EventPattern 19 | import org.fxmisc.wellbehaved.event.InputMap 20 | import org.fxmisc.wellbehaved.event.Nodes 21 | import tornadofx.* 22 | import javax.json.Json 23 | 24 | class CustomersView(private val onCustomerSelectedListener: OnCustomerSelectedListener? = null) : View("Customers View") { 25 | 26 | private val customersViewModel = CustomerViewModel() 27 | private val customersController: CustomersController by inject() 28 | val state = getStates() 29 | private var districtView: Field? = null 30 | private lateinit var customerName: TextField 31 | 32 | override fun onDock() { 33 | super.onDock() 34 | repeatFocus(customerName) 35 | } 36 | 37 | private fun repeatFocus(node: Node) { 38 | Platform.runLater { 39 | if (!node.isFocused) { 40 | node.requestFocus() 41 | repeatFocus(node) 42 | } 43 | } 44 | } 45 | 46 | override val root = hbox { 47 | Nodes.addInputMap(this, InputMap.sequence( 48 | InputMap.consume(EventPattern.keyPressed(KeyCode.A, KeyCombination.CONTROL_DOWN)) { e -> 49 | customersController.addCustomer(customersViewModel.customerName, 50 | customersViewModel.address, 51 | customersViewModel.state, 52 | customersViewModel.district) 53 | } 54 | )) 55 | 56 | 57 | this.add(getCustomersView()) 58 | this.add(getCustomersList()) 59 | } 60 | 61 | private fun getCustomersList(): VBox { 62 | return vbox { 63 | onCustomerSelectedListener?.let { 64 | 65 | } ?: kotlin.run { 66 | Platform.runLater { 67 | this.minWidth = Screen.getPrimary().visualBounds.width.div(3) 68 | } 69 | } 70 | 71 | tableview(customersController.customersListObserver) { 72 | columnResizePolicy = SmartResize.POLICY 73 | vboxConstraints { margin = Insets(20.0) } 74 | stylesheets.add("jfx-table-view.css") 75 | 76 | column("Customer Name", CustomersTable.MeaningfulCustomer::customerName) 77 | column("Address", CustomersTable.MeaningfulCustomer::address) 78 | column("State", CustomersTable.MeaningfulCustomer::state) 79 | column("District", CustomersTable.MeaningfulCustomer::district) 80 | 81 | onDoubleClick { 82 | onCustomerSelectedListener?.let { 83 | this.selectedItem?.let { customer -> 84 | it.onCustomerSelected(customer) 85 | currentStage?.close() 86 | } 87 | } ?: kotlin.run { 88 | this.selectedItem?.let { 89 | CustomerDetailView(it).openWindow() 90 | } 91 | } 92 | } 93 | 94 | this.setOnKeyPressed { 95 | this.selectedItem?.let { item -> 96 | when (it.code) { 97 | KeyCode.ENTER -> { 98 | onCustomerSelectedListener?.let { 99 | it.onCustomerSelected(this.selectedItem!!) 100 | currentStage?.close() 101 | } ?: kotlin.run { 102 | CustomerDetailView(this.selectedItem!!).openWindow() 103 | } 104 | } 105 | KeyCode.DELETE, KeyCode.BACK_SPACE -> { 106 | alert(Alert.AlertType.CONFIRMATION, "Delete Customer ${this.selectedItem!!.customerName} ?", 107 | "Remove ${item.customerName} ?", 108 | buttons = *arrayOf(ButtonType.YES, ButtonType.CANCEL), owner = currentWindow, title = "Hey!") { 109 | if (it == ButtonType.YES) { 110 | customersController.deleteCustomer(item) 111 | } 112 | } 113 | } 114 | else -> { 115 | 116 | } 117 | } 118 | } 119 | 120 | } 121 | } 122 | } 123 | } 124 | 125 | private fun getCustomersView(): VBox { 126 | return vbox { 127 | this.add(getSearchProductForm()) 128 | this.add(getAddProductView()) 129 | } 130 | } 131 | 132 | 133 | private fun getAddProductView(): VBox { 134 | return vbox { 135 | form { 136 | fieldset { 137 | field("Customer Name") { 138 | textfield(customersViewModel.customerName) { 139 | tag = "customer" 140 | }.validator { 141 | if (it.isNullOrBlank()) error("Please enter customer name!") else null 142 | } 143 | } 144 | 145 | field("Address") { 146 | textfield(customersViewModel.address) { 147 | }.validator { 148 | if (it.isNullOrBlank()) error("Please enter address!") else null 149 | } 150 | } 151 | 152 | field("State") { 153 | combobox(customersViewModel.state, state.map { it.state }) { 154 | selectionModel.selectedIndex 155 | }.validator { 156 | if (it.isNullOrBlank()) error("Please select the state") else null 157 | } 158 | } 159 | 160 | customersViewModel.state.onChange { 161 | this@CustomersView.districtView?.let { 162 | customersViewModel.district.value = null 163 | it.removeFromParent() 164 | } 165 | getDistrictView(this@fieldset) 166 | } 167 | } 168 | 169 | 170 | 171 | button("Add Customer (CTRL+A)") { 172 | setOnMouseClicked { 173 | customersController.addCustomer(customersViewModel.customerName, customersViewModel.address, customersViewModel.state, customersViewModel.district) 174 | } 175 | } 176 | } 177 | 178 | } 179 | } 180 | 181 | private fun getDistrictView(it: Fieldset) { 182 | this@CustomersView.districtView = field("District") { 183 | tag = "district" 184 | val state = state.firstOrNull { it.state.equals(customersViewModel.state.value) } 185 | state?.let { 186 | combobox(customersViewModel.district, state.districts).validator { 187 | if (it.isNullOrBlank()) error("Please select the District!") else null 188 | } 189 | } 190 | } 191 | this@CustomersView.districtView?.addTo(it) 192 | } 193 | 194 | private fun getStates(): List { 195 | val stream = javaClass.getResourceAsStream("/states-and-districts.json") 196 | val state = Gson().fromJson(Json.createReader(stream).readObject().toPrettyString(), StateDistrict::class.java) 197 | return state.states 198 | } 199 | 200 | private fun getSearchProductForm(): VBox { 201 | return vbox { 202 | vboxConstraints { margin = Insets(20.0, 0.0, 20.0, 0.0) } 203 | form { 204 | fieldset { 205 | field("Search Customers") { 206 | textfield(customersViewModel.searchName) { 207 | this@CustomersView.customerName = this 208 | }.validator { 209 | if (it.isNullOrBlank()) error("Please enter search Query!") else null 210 | } 211 | 212 | customersViewModel.searchName.onChange { 213 | it?.let { 214 | customersController.searchProduct(it) 215 | } ?: run { 216 | requestForCustomers() 217 | } 218 | } 219 | } 220 | } 221 | 222 | button("Search Customer") { 223 | alignment = Pos.BOTTOM_RIGHT 224 | setOnMouseClicked { 225 | customersController.searchProduct(customersViewModel.searchName.value) 226 | } 227 | } 228 | } 229 | 230 | } 231 | } 232 | 233 | fun requestForCustomers() { 234 | customersController.requestForCustomers() 235 | } 236 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/InvoiceGenerator.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.Database 4 | import com.dashlabs.invoicemanagement.databaseconnection.InvoiceTable 5 | import com.dashlabs.invoicemanagement.databaseconnection.ProductsTable 6 | import com.dashlabs.invoicemanagement.databaseconnection.twoDecimalFormatted 7 | import com.itextpdf.text.* 8 | import com.itextpdf.text.pdf.FontSelector 9 | import com.itextpdf.text.pdf.PdfPCell 10 | import com.itextpdf.text.pdf.PdfPTable 11 | import com.itextpdf.text.pdf.PdfWriter 12 | import java.io.File 13 | import java.io.FileOutputStream 14 | import java.text.DecimalFormat 15 | import java.text.SimpleDateFormat 16 | import java.util.* 17 | 18 | object InvoiceGenerator { 19 | 20 | private val df2 = DecimalFormat("#.##") 21 | 22 | 23 | fun makePDF(fileName: File, data: InvoiceTable.MeaningfulInvoice, listproducts: MutableList>): Boolean { 24 | val curDate = Date() 25 | val format = SimpleDateFormat("dd-MMM-yyy") 26 | val date = format.format(curDate) 27 | try { 28 | 29 | val file = FileOutputStream(fileName) 30 | val document = Document() 31 | PdfWriter.getInstance(document, file) 32 | 33 | val irdTable = PdfPTable(2) 34 | irdTable.addCell(getIRDCell("Quotation No")) 35 | irdTable.addCell(getIRDCell("Quotation Date")) 36 | irdTable.addCell(getIRDCell(data.invoiceId)) // pass invoice number 37 | irdTable.addCell(getIRDCell(date)) // pass invoice date 38 | 39 | val irhTable = PdfPTable(3) 40 | irhTable.widthPercentage = 100F 41 | 42 | irhTable.addCell(getIRHCell("", PdfPCell.ALIGN_RIGHT)) 43 | irhTable.addCell(getIRHCell("", PdfPCell.ALIGN_RIGHT)) 44 | irhTable.addCell(getIRHCell("Quotation", PdfPCell.ALIGN_RIGHT)) 45 | irhTable.addCell(getIRHCell("", PdfPCell.ALIGN_RIGHT)) 46 | irhTable.addCell(getIRHCell("", PdfPCell.ALIGN_RIGHT)) 47 | 48 | val invoiceTable = PdfPCell(irdTable) 49 | invoiceTable.setBorder(0) 50 | irhTable.addCell(invoiceTable) 51 | 52 | val fs = FontSelector() 53 | val font = FontFactory.getFont(FontFactory.TIMES_ROMAN, 10F, Font.BOLD) 54 | fs.addFont(font) 55 | val bill = fs.process("") // customer information 56 | var customer = Database.getCustomer(data.customerId) 57 | val name = Paragraph(customer?.customerName) 58 | name.indentationLeft = 20F 59 | val contact = Paragraph(customer?.toString()) 60 | contact.setIndentationLeft(20F) 61 | 62 | val billTable = PdfPTable(6) 63 | billTable.setWidthPercentage(100F) 64 | billTable.setWidths(floatArrayOf(1f, 2f, 2f, 2f, 1f, 2f)) 65 | billTable.setSpacingBefore(30.0f) 66 | billTable.addCell(getBillHeaderCell("Index")) 67 | billTable.addCell(getBillHeaderCell("Item")) 68 | billTable.addCell(getBillHeaderCell("Unit Price")) 69 | billTable.addCell(getBillHeaderCell("Qty")) 70 | billTable.addCell(getBillHeaderCell("Discount")) 71 | billTable.addCell(getBillHeaderCell("Amount")) 72 | 73 | fun getAmountFor(baseAmount: Double, discount: Double, quantity: Int): Double? { 74 | var newPrice = baseAmount.times(quantity).minus(discount) 75 | newPrice = df2.format(newPrice).toDouble() 76 | return newPrice.twoDecimalFormatted() 77 | } 78 | 79 | 80 | for (i in listproducts.indices) { 81 | billTable.addCell(getBillRowCell((i + 1).toString())) 82 | billTable.addCell(getBillRowCell(listproducts[i].first.productName)) 83 | billTable.addCell(getBillRowCell(listproducts[i].first.amount.toString())) 84 | billTable.addCell(getBillRowCell(listproducts[i].third.toString())) 85 | billTable.addCell(getBillRowCell(listproducts[i].second.twoDecimalFormatted().toString())) 86 | billTable.addCell(getBillRowCell(getAmountFor(listproducts[i].first.amount, 87 | listproducts[i].second, 88 | listproducts[i].third).toString())) 89 | } 90 | for (j in 0 until 15 - listproducts.size) { 91 | billTable.addCell(getBillRowCell(" ")) 92 | billTable.addCell(getBillRowCell("")) 93 | billTable.addCell(getBillRowCell("")) 94 | billTable.addCell(getBillRowCell("")) 95 | billTable.addCell(getBillRowCell("")) 96 | billTable.addCell(getBillRowCell("")) 97 | } 98 | 99 | val validity = PdfPTable(1) 100 | validity.widthPercentage = 100F 101 | validity.addCell(getValidityCell(" ")) 102 | validity.addCell(getValidityCell("")) 103 | validity.addCell(getValidityCell("")) 104 | validity.addCell(getValidityCell("")) 105 | val summaryL = PdfPCell(validity) 106 | summaryL.colspan = 3 107 | summaryL.setPadding(1.0f) 108 | billTable.addCell(summaryL) 109 | 110 | val accounts = PdfPTable(2) 111 | accounts.widthPercentage = 100F 112 | accounts.addCell(getAccountsCell("Subtotal")) 113 | accounts.addCell(getAccountsCellR(data.amountTotal.twoDecimalFormatted().toString())) 114 | 115 | accounts.addCell(getAccountsCell("Outstanding")) 116 | accounts.addCell(getAccountsCellR(data.outstandingAmount.twoDecimalFormatted().toString())) 117 | 118 | val summaryR = PdfPCell(accounts) 119 | summaryR.colspan = 3 120 | billTable.addCell(summaryR) 121 | 122 | val describer = PdfPTable(1) 123 | describer.widthPercentage = 100F 124 | describer.addCell(getdescCell(" ")) 125 | describer.addCell(getdescCell(" ")) 126 | 127 | document.open()//PDF document opened........ 128 | 129 | document.add(irhTable) 130 | document.add(bill) 131 | document.add(name) 132 | document.add(contact) 133 | document.add(billTable) 134 | document.add(describer) 135 | 136 | document.close() 137 | 138 | file.close() 139 | 140 | return true 141 | 142 | } catch (e: Exception) { 143 | e.printStackTrace() 144 | return false 145 | } 146 | 147 | 148 | } 149 | 150 | 151 | fun getIRHCell(text: String, alignment: Int): PdfPCell { 152 | val fs = FontSelector() 153 | val font = FontFactory.getFont(FontFactory.HELVETICA, 14F) 154 | /* font.setColor(BaseColor.GRAY);*/ 155 | fs.addFont(font) 156 | val phrase = fs.process(text) 157 | val cell = PdfPCell(phrase) 158 | cell.setPadding(5F) 159 | cell.setHorizontalAlignment(alignment) 160 | cell.setBorder(PdfPCell.NO_BORDER) 161 | return cell 162 | } 163 | 164 | fun getIRDCell(text: String): PdfPCell { 165 | val cell = PdfPCell(Paragraph(text)) 166 | cell.setHorizontalAlignment(Element.ALIGN_CENTER) 167 | cell.setPadding(5.0f) 168 | cell.setBorderColor(BaseColor.LIGHT_GRAY) 169 | return cell 170 | } 171 | 172 | fun getBillHeaderCell(text: String): PdfPCell { 173 | val fs = FontSelector() 174 | val font = FontFactory.getFont(FontFactory.HELVETICA, 11F) 175 | font.setColor(BaseColor.GRAY) 176 | fs.addFont(font) 177 | val phrase = fs.process(text) 178 | val cell = PdfPCell(phrase) 179 | cell.setHorizontalAlignment(Element.ALIGN_CENTER) 180 | cell.setPadding(5.0f) 181 | return cell 182 | } 183 | 184 | fun getBillRowCell(text: String): PdfPCell { 185 | val cell = PdfPCell(Paragraph(text)) 186 | cell.horizontalAlignment = Element.ALIGN_CENTER 187 | cell.setPadding(5.0f) 188 | cell.borderWidthBottom = 0F 189 | cell.borderWidthTop = 0F 190 | return cell 191 | } 192 | 193 | fun getBillFooterCell(text: String): PdfPCell { 194 | val cell = PdfPCell(Paragraph(text)) 195 | cell.horizontalAlignment = Element.ALIGN_CENTER 196 | cell.setPadding(5.0f) 197 | cell.borderWidthBottom = 0F 198 | cell.borderWidthTop = 0F 199 | return cell 200 | } 201 | 202 | fun getValidityCell(text: String): PdfPCell { 203 | val fs = FontSelector() 204 | val font = FontFactory.getFont(FontFactory.HELVETICA, 10F) 205 | font.setColor(BaseColor.GRAY) 206 | fs.addFont(font) 207 | val phrase = fs.process(text) 208 | val cell = PdfPCell(phrase) 209 | cell.setBorder(0) 210 | return cell 211 | } 212 | 213 | fun getAccountsCell(text: String): PdfPCell { 214 | val fs = FontSelector() 215 | val font = FontFactory.getFont(FontFactory.HELVETICA, 10F) 216 | fs.addFont(font) 217 | val phrase = fs.process(text) 218 | val cell = PdfPCell(phrase) 219 | cell.setBorderWidthRight(0F) 220 | cell.setBorderWidthTop(0F) 221 | cell.setPadding(5.0f) 222 | return cell 223 | } 224 | 225 | fun getAccountsCellR(text: String): PdfPCell { 226 | val fs = FontSelector() 227 | val font = FontFactory.getFont(FontFactory.HELVETICA, 10F) 228 | fs.addFont(font) 229 | val phrase = fs.process(text) 230 | val cell = PdfPCell(phrase) 231 | cell.setBorderWidthLeft(0F) 232 | cell.setBorderWidthTop(0F) 233 | cell.setHorizontalAlignment(Element.ALIGN_RIGHT) 234 | cell.setPadding(5.0f) 235 | cell.setPaddingRight(20.0f) 236 | return cell 237 | } 238 | 239 | fun getdescCell(text: String): PdfPCell { 240 | val fs = FontSelector() 241 | val font = FontFactory.getFont(FontFactory.HELVETICA, 10F) 242 | font.setColor(BaseColor.GRAY) 243 | fs.addFont(font) 244 | val phrase = fs.process(text) 245 | val cell = PdfPCell(phrase) 246 | cell.setHorizontalAlignment(Element.ALIGN_CENTER) 247 | cell.setBorder(0) 248 | return cell 249 | } 250 | 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/invoices/InvoicesView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.invoices 2 | 3 | import com.dashlabs.invoicemanagement.databaseconnection.CustomersTable 4 | import com.dashlabs.invoicemanagement.databaseconnection.ProductsTable 5 | import com.dashlabs.invoicemanagement.databaseconnection.twoDecimalFormatted 6 | import com.dashlabs.invoicemanagement.view.customers.CustomersView 7 | import com.dashlabs.invoicemanagement.view.customers.OnCustomerSelectedListener 8 | import com.dashlabs.invoicemanagement.view.customers.OnProductSelectedListener 9 | import com.dashlabs.invoicemanagement.view.products.ProductsView 10 | import javafx.application.Platform 11 | import javafx.collections.FXCollections 12 | import javafx.collections.ObservableList 13 | import javafx.collections.ObservableMap 14 | import javafx.geometry.Insets 15 | import javafx.scene.control.Alert 16 | import javafx.scene.control.ButtonType 17 | import javafx.scene.control.TableView 18 | import javafx.scene.input.KeyCode 19 | import tornadofx.* 20 | import java.text.DecimalFormat 21 | 22 | 23 | class InvoicesView : View("Invoices View"), OnProductSelectedListener, OnCustomerSelectedListener { 24 | 25 | private val invoiceViewModel = InvoiceViewModel() 26 | private val invoicesController: InvoicesController by inject() 27 | private var productsTableView: TableView? = null 28 | private val df2 = DecimalFormat("#.##") 29 | 30 | override val root = vbox { 31 | hbox { 32 | vboxConstraints { margin = Insets(20.0) } 33 | 34 | vbox { 35 | hboxConstraints { margin = Insets(20.0) } 36 | 37 | label(invoiceViewModel.customer) { 38 | vboxConstraints { margin = Insets(10.0, 0.0, 0.0, 0.0) } 39 | } 40 | 41 | button("Select Customer (Shift+C)") { 42 | vboxConstraints { margin = Insets(10.0, 0.0, 0.0, 0.0) } 43 | action { 44 | CustomersView(onCustomerSelectedListener = this@InvoicesView).openWindow() 45 | } 46 | } 47 | 48 | button("Select Products (Shift+P)") { 49 | vboxConstraints { margin = Insets(10.0, 0.0, 0.0, 00.0) } 50 | action { 51 | ProductsView(onProductSelectedListener = this@InvoicesView).openWindow() 52 | } 53 | } 54 | } 55 | 56 | vbox { 57 | hboxConstraints { margin = Insets(20.0) } 58 | /*vbox { 59 | isVisible = false 60 | vboxConstraints { margin = Insets(0.0, 0.0, 0.0, 10.0) } 61 | label("Total Amount: ") { 62 | vboxConstraints { margin = Insets(10.0) } 63 | } 64 | 65 | textfield(invoiceViewModel.totalPrice) { 66 | this.isEditable = false 67 | this.filterInput { it.controlNewText.isDouble() } 68 | } 69 | }*/ 70 | 71 | vbox { 72 | vboxConstraints { margin = Insets(0.0, 0.0, 0.0, 10.0) } 73 | 74 | label("Paying Amount: ") { 75 | vboxConstraints { margin = Insets(10.0) } 76 | } 77 | 78 | textfield(invoiceViewModel.payingAmount) { 79 | this.filterInput { 80 | it.controlNewText.isDouble() && it.controlNewText.toDouble() <= invoiceViewModel.totalPrice.value.toDouble() 81 | } 82 | }.textProperty().addListener { observable, oldValue, newValue -> 83 | try { 84 | newValue.takeIf { !it.isNullOrEmpty() }?.let { 85 | val bal = invoiceViewModel.totalPrice.value.toDouble().minus(it.toDouble()) 86 | invoiceViewModel.leftoverAmount.value = bal 87 | } ?: kotlin.run { 88 | val bal = invoiceViewModel.totalPrice.value.toDouble() 89 | invoiceViewModel.leftoverAmount.value = bal 90 | } 91 | } catch (ex: Exception) { 92 | 93 | } 94 | } 95 | 96 | } 97 | 98 | vbox { 99 | vboxConstraints { margin = Insets(0.0, 0.0, 0.0, 10.0) } 100 | 101 | label("Amount Due: ") { 102 | vboxConstraints { margin = Insets(10.0) } 103 | } 104 | 105 | textfield(invoiceViewModel.leftoverAmount) { 106 | this.isEditable = false 107 | } 108 | 109 | } 110 | 111 | button("Create Invoice") { 112 | vboxConstraints { margin = Insets(10.0) } 113 | setOnMouseClicked { 114 | if (invoiceViewModel.customerId.value == 0L || invoicesController.productsQuanityView.isEmpty()) { 115 | warning("Select products and customer first!").show() 116 | return@setOnMouseClicked 117 | } else { 118 | if (invoiceViewModel.leftoverAmount.value != null && invoiceViewModel.leftoverAmount.value.toDouble() > 0) { 119 | alert(Alert.AlertType.CONFIRMATION, "Credit Amount", 120 | "You are not paying in full and amount ${invoiceViewModel.leftoverAmount.value} will be added to your credits", 121 | buttons = *arrayOf(ButtonType.OK, ButtonType.CANCEL), owner = currentWindow, title = "Hey!") { 122 | if (it == ButtonType.OK) { 123 | invoicesController.addInvoice(invoiceViewModel) 124 | } 125 | } 126 | } else { 127 | invoicesController.addInvoice(invoiceViewModel) 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | 135 | } 136 | 137 | vbox { 138 | vboxConstraints { margin = Insets(20.0) } 139 | tableview(invoicesController.productsQuanityView) { 140 | isEditable = true 141 | stylesheets.add("jfx-table-view.css") 142 | this@InvoicesView.productsTableView = this 143 | column("Product Name", InvoicesController.ProductsModel::productsTable).remainingWidth() 144 | column("Quantity", InvoicesController.ProductsModel::quantity).remainingWidth().makeEditable().setOnEditCommit { 145 | if (it.newValue.toInt() > 0) { 146 | invoicesController.productsQuanityView[it.tablePosition.row].quantity = it.newValue 147 | getAmountFor(invoicesController.productsQuanityView[it.tablePosition.row])?.let { newPrice -> 148 | invoicesController.productsQuanityView[it.tablePosition.row].totalAmount = newPrice.toString() 149 | } 150 | } 151 | updateTotalAmount() 152 | refresh() 153 | } 154 | column("Discount", InvoicesController.ProductsModel::discount).remainingWidth().makeEditable().setOnEditCommit { 155 | invoicesController.productsQuanityView[it.tablePosition.row].discount = it.newValue.twoDecimalFormatted() 156 | getAmountFor(invoicesController.productsQuanityView[it.tablePosition.row])?.let { newPrice -> 157 | invoicesController.productsQuanityView[it.tablePosition.row].totalAmount = newPrice.toString() 158 | } 159 | updateTotalAmount() 160 | refresh() 161 | } 162 | column("Amount", InvoicesController.ProductsModel::totalAmount).remainingWidth() 163 | columnResizePolicy = SmartResize.POLICY 164 | 165 | this.setOnKeyPressed { 166 | when (it.code) { 167 | KeyCode.DELETE, KeyCode.BACK_SPACE -> { 168 | selectedCell?.row?.let { position -> 169 | alert(Alert.AlertType.CONFIRMATION, "Remove Items ?", 170 | "Remove ${selectedItem?.productsTable?.productName} ?", 171 | buttons = *arrayOf(ButtonType.OK, ButtonType.CANCEL), owner = currentWindow, title = "Hey!") { 172 | if (it == ButtonType.OK) { 173 | invoicesController.productsQuanityView.removeAt(position) 174 | updateTotalAmount() 175 | refresh() 176 | } 177 | } 178 | } 179 | } 180 | else -> { 181 | 182 | } 183 | } 184 | } 185 | } 186 | } 187 | } 188 | 189 | private fun getAmountFor(productsModel: InvoicesController.ProductsModel): Double? { 190 | val baseAmount = productsModel.baseAmount 191 | var newPrice = baseAmount.times(productsModel.quantity.toInt()).minus(productsModel.discount) 192 | newPrice = df2.format(newPrice).toDouble() 193 | return newPrice 194 | } 195 | 196 | override fun onCustomerSelected(customersTable: CustomersTable.MeaningfulCustomer) { 197 | invoiceViewModel.customerId.value = customersTable.customerId 198 | invoiceViewModel.customer.value = customersTable 199 | } 200 | 201 | override fun onProductSelected(newSelectedProducts: ObservableMap?) { 202 | val currentList = FXCollections.observableArrayList() 203 | invoicesController.productsQuanityView?.let { 204 | currentList.setAll(it) 205 | } 206 | newSelectedProducts?.forEach { item -> 207 | item.key?.let { 208 | val product = currentList.containsProduct(item.key) 209 | if (product != -1) { 210 | currentList[product].quantity = currentList[product].quantity.toInt().plus(1).toString() 211 | } else { 212 | currentList.add(InvoicesController.ProductsModel(item.key, "1", item.key.amount.toString(), item.key.amount)) 213 | } 214 | } 215 | } 216 | invoicesController.updateProductsObserver(currentList) 217 | 218 | invoiceViewModel.productsList.value = currentList 219 | invoicesController.productsQuanityView.setAll(currentList) 220 | val totalAmount = invoicesController.productsQuanityView.map { it.productsTable.amount * it.quantity.toInt() }.sum() 221 | invoiceViewModel.totalPrice.value = totalAmount 222 | invoiceViewModel.leftoverAmount.value = totalAmount 223 | 224 | Platform.runLater { 225 | productsTableView?.refresh() 226 | } 227 | } 228 | 229 | 230 | private fun updateTotalAmount() { 231 | Platform.runLater { 232 | val totalAmount = invoicesController.productsQuanityView.map { it.totalAmount.toDouble() }.sum() 233 | invoiceViewModel.totalPrice.value = totalAmount 234 | invoiceViewModel.leftoverAmount.value = totalAmount 235 | } 236 | } 237 | 238 | fun requestForInvoices() { 239 | invoicesController.requestForInvoices() 240 | } 241 | 242 | fun openCustomersView() { 243 | CustomersView(onCustomerSelectedListener = this@InvoicesView).openWindow() 244 | } 245 | 246 | fun openProductsView() { 247 | ProductsView(onProductSelectedListener = this@InvoicesView).openWindow() 248 | } 249 | } 250 | 251 | private fun ObservableList.containsProduct(key: ProductsTable): Int { 252 | var index = -1 253 | for (e in this) { 254 | index++ 255 | if (e.productsTable.productId == key.productId) { 256 | return index 257 | } 258 | } 259 | return -1 260 | } 261 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/view/customers/CustomerDetailView.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.view.customers 2 | 3 | import com.dashlabs.invoicemanagement.InvoiceGenerator 4 | import com.dashlabs.invoicemanagement.app.savePdf 5 | import com.dashlabs.invoicemanagement.databaseconnection.* 6 | import com.dashlabs.invoicemanagement.view.TransactionHistoryView 7 | import com.dashlabs.invoicemanagement.view.invoices.InvoiceViewModel 8 | import com.dashlabs.invoicemanagement.view.invoices.InvoicesController 9 | import com.google.gson.Gson 10 | import com.google.gson.reflect.TypeToken 11 | import io.reactivex.Single 12 | import io.reactivex.rxjavafx.schedulers.JavaFxScheduler 13 | import io.reactivex.schedulers.Schedulers 14 | import javafx.collections.FXCollections 15 | import javafx.collections.ObservableList 16 | import javafx.geometry.Insets 17 | import javafx.scene.control.Alert 18 | import javafx.scene.control.ButtonBar 19 | import javafx.scene.control.ButtonType 20 | import javafx.scene.layout.VBox 21 | import tornadofx.* 22 | import java.awt.Desktop 23 | import java.io.File 24 | import java.util.* 25 | 26 | class CustomerDetailView(private val customerData: CustomersTable.MeaningfulCustomer) : View("${customerData.customerName} Details") { 27 | private var deductValue: Double = 0.0 28 | private var newInvoiceValue: Double = 0.0 29 | private var descriptionValue = "Old Balance Adjustment" 30 | private val invoicesController: InvoicesController by inject() 31 | private var balanceVbox: VBox? = null 32 | 33 | init { 34 | invoicesController.getInvoicesForCustomer(customerData.customerId) 35 | } 36 | 37 | override val root = vbox { 38 | minWidth = 600.0 39 | 40 | hbox { 41 | vbox { 42 | hboxConstraints { margin = Insets(10.0) } 43 | button { 44 | vboxConstraints { margin = Insets(10.0) } 45 | text = "Transaction History" 46 | setOnMouseClicked { 47 | TransactionHistoryView(customerData.customerId).openWindow() 48 | } 49 | } 50 | 51 | label(customerData.toString()) { 52 | vboxConstraints { margin = Insets(10.0) } 53 | } 54 | } 55 | vbox { 56 | hboxConstraints { margin = Insets(10.0) } 57 | this.add(getCreateDummyInvoiceBox()) 58 | 59 | balanceVbox = this@vbox 60 | 61 | } 62 | } 63 | 64 | invoicesController.invoicesListObserver.addListener { observable, oldValue, newValue -> 65 | getOutstandingView() 66 | } 67 | 68 | tableview(invoicesController.invoicesListObserver) { 69 | columnResizePolicy = SmartResize.POLICY 70 | maxHeight = 300.0 71 | stylesheets.add("jfx-table-view.css") 72 | 73 | vboxConstraints { margin = Insets(20.0) } 74 | tag = "invoices" 75 | column("Bill Date", InvoiceTable.MeaningfulInvoice::dateCreated) 76 | column("Bill Amount", InvoiceTable.MeaningfulInvoice::amountTotal) 77 | column("Due Amount", InvoiceTable.MeaningfulInvoice::outstandingAmount) 78 | column("Received Payment", InvoiceTable.MeaningfulInvoice::paymentReceived) 79 | onDoubleClick { 80 | showInvoiceDetails(invoicesController.invoicesListObserver.value[this.selectedCell!!.row]) 81 | } 82 | } 83 | 84 | } 85 | 86 | private var balanceBox: VBox? = null 87 | 88 | private fun getOutstandingView() { 89 | try { 90 | val outstanding = invoicesController.invoicesListObserver.map { 91 | it.outstandingAmount 92 | }.sum() 93 | 94 | this@CustomerDetailView.balanceBox?.let { 95 | it.removeFromParent() 96 | } 97 | 98 | if (outstanding > 0) { 99 | val balanceBox = getBalanceBox(outstanding) 100 | this@CustomerDetailView.balanceBox = balanceBox 101 | balanceVbox?.add(balanceBox) 102 | } 103 | 104 | 105 | } catch (ex: Exception) { 106 | ex.printStackTrace() 107 | } 108 | } 109 | 110 | private fun getCreateDummyInvoiceBox(): VBox { 111 | return vbox { 112 | tag = "balance" 113 | 114 | label { 115 | text = "This will create an invoice with some amount!" 116 | vboxConstraints { margin = Insets(10.0) } 117 | } 118 | 119 | label { 120 | text = "Please enter due amount for old data!" 121 | vboxConstraints { margin = Insets(10.0) } 122 | } 123 | 124 | hbox { 125 | textfield(descriptionValue) { 126 | hboxConstraints { margin = Insets(10.0) } 127 | }.textProperty().addListener { observable, oldValue, newValue -> 128 | newValue.takeIf { !it.isNullOrEmpty() }?.let { 129 | descriptionValue = newValue 130 | } ?: kotlin.run { 131 | descriptionValue = "" 132 | } 133 | } 134 | 135 | textfield(newInvoiceValue.toString()) { 136 | hboxConstraints { margin = Insets(10.0) } 137 | this.filterInput { 138 | it.controlNewText.isDouble() 139 | } 140 | }.textProperty().addListener { observable, oldValue, newValue -> 141 | newValue.takeIf { !it.isNullOrEmpty() }?.let { 142 | newInvoiceValue = newValue.toDouble() 143 | } ?: kotlin.run { 144 | newInvoiceValue = 0.0 145 | } 146 | } 147 | 148 | button { 149 | hboxConstraints { margin = Insets(10.0) } 150 | text = "Add as Invoice Now!" 151 | setOnMouseClicked { 152 | if (newInvoiceValue > 0) { 153 | val model = InvoiceViewModel() 154 | model.customerId.value = customerData.customerId 155 | model.customer.value = customerData 156 | model.leftoverAmount.value = newInvoiceValue 157 | model.payingAmount.value = 0 158 | model.totalPrice.value = newInvoiceValue 159 | model.productsList.value = generateProductWith(newInvoiceValue, descriptionValue) 160 | invoicesController.addInvoice(model) 161 | } 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | private fun generateProductWith(newInvoiceValue: Double, description: String): ObservableList { 169 | val currentList = FXCollections.observableArrayList() 170 | val productsTable = ProductsTable() 171 | productsTable.productName = description 172 | productsTable.amount = newInvoiceValue 173 | productsTable.dateCreated = System.currentTimeMillis() 174 | productsTable.dateModified = System.currentTimeMillis() 175 | val model = InvoicesController.ProductsModel(productsTable, "1", newInvoiceValue.toString(), newInvoiceValue) 176 | currentList.add(model) 177 | return currentList 178 | } 179 | 180 | private fun getBalanceBox(outstanding: Double): VBox { 181 | return vbox { 182 | tag = "balance" 183 | deductValue = outstanding 184 | 185 | label { 186 | text = "Total Amount due is $outstanding!" 187 | vboxConstraints { margin = Insets(10.0) } 188 | } 189 | 190 | label { 191 | text = "Please enter due amount to pay!" 192 | vboxConstraints { margin = Insets(10.0) } 193 | } 194 | 195 | hbox { 196 | textfield(outstanding.toString()) { 197 | hboxConstraints { margin = Insets(10.0) } 198 | this.filterInput { 199 | it.controlNewText.isDouble() && it.controlNewText.toDouble() <= outstanding 200 | } 201 | }.textProperty().addListener { observable, oldValue, newValue -> 202 | newValue.takeIf { !it.isNullOrEmpty() }?.let { 203 | deductValue = newValue.toDouble() 204 | } ?: kotlin.run { 205 | deductValue = 0.0 206 | } 207 | } 208 | 209 | button { 210 | hboxConstraints { margin = Insets(10.0) } 211 | text = "Pay Now!" 212 | setOnMouseClicked { 213 | if (deductValue > 0) { 214 | performBalanceReduction(customerData, deductValue) 215 | } 216 | } 217 | } 218 | } 219 | } 220 | } 221 | 222 | private fun showInvoiceDetails(selectedItem: InvoiceTable.MeaningfulInvoice?) { 223 | selectedItem?.let { 224 | generateInvoice(selectedItem) 225 | .subscribeOn(Schedulers.io()) 226 | .observeOn(JavaFxScheduler.platform()) 227 | .subscribe { t1, t2 -> 228 | alertUser(t1, selectedItem) 229 | } 230 | } 231 | } 232 | 233 | private fun alertUser(t1: File, selectedItem: InvoiceTable.MeaningfulInvoice) { 234 | alert(Alert.AlertType.CONFIRMATION, "Invoice Information", 235 | "View invoice or Save It", 236 | buttons = *arrayOf(ButtonType("Save", ButtonBar.ButtonData.BACK_PREVIOUS), 237 | ButtonType("Preview", ButtonBar.ButtonData.NEXT_FORWARD), ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE)), title = "Hey!") { 238 | when { 239 | it.buttonData == ButtonBar.ButtonData.NEXT_FORWARD -> try { 240 | Desktop.getDesktop().browse(t1.toURI()) 241 | } catch (e: Exception) { 242 | e.printStackTrace() 243 | } 244 | it.buttonData == ButtonBar.ButtonData.BACK_PREVIOUS -> savePdf(selectedItem, t1) 245 | } 246 | } 247 | } 248 | 249 | private fun generateInvoice(selectedItem: InvoiceTable.MeaningfulInvoice): Single { 250 | return Single.fromCallable { 251 | if (selectedItem.productsPurchased.contains("\"third\"")) { 252 | val list = Gson().fromJson>>( 253 | selectedItem.productsPurchased, 254 | object : TypeToken>>() {}.type) 255 | val file = File("~/invoicedatabase", "temp.pdf") 256 | file.delete() 257 | file.createNewFile() 258 | InvoiceGenerator.makePDF(file, selectedItem, list.map { Triple(it.first, it.second, it.third) }.toMutableList()) 259 | file 260 | } else { 261 | val list = Gson().fromJson>>( 262 | selectedItem.productsPurchased, 263 | object : TypeToken>>() {}.type) 264 | val file = File("~/invoicedatabase", "temp.pdf") 265 | file.delete() 266 | file.createNewFile() 267 | InvoiceGenerator.makePDF(file, selectedItem, list.map { Triple(it.first, 0.0, it.second) }.toMutableList()) 268 | file 269 | } 270 | 271 | } 272 | } 273 | 274 | 275 | private fun performBalanceReduction(selectedItem: CustomersTable.MeaningfulCustomer, deductValue: Double) { 276 | Single.fromCallable { 277 | val customer = Database.getCustomer(selectedItem.customerId) 278 | customer?.let { 279 | Database.updateCustomer(customer, deductValue) 280 | 281 | val transactionTable = TransactionTable() 282 | transactionTable.dateCreated = System.currentTimeMillis() 283 | transactionTable.customerId = selectedItem.customerId 284 | transactionTable.deduction = deductValue 285 | Database.createTransaction(transactionTable) 286 | } 287 | }.subscribeOn(Schedulers.io()) 288 | .observeOn(JavaFxScheduler.platform()).subscribe { t1, t2 -> 289 | t1?.let { 290 | find { 291 | requestForCustomers() 292 | } 293 | } 294 | t2?.let { 295 | it.message?.let { it1 -> warning(it1).show() } 296 | } 297 | invoicesController.getInvoicesForCustomer(selectedItem.customerId) 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/main/kotlin/com/dashlabs/invoicemanagement/databaseconnection/Database.kt: -------------------------------------------------------------------------------- 1 | package com.dashlabs.invoicemanagement.databaseconnection 2 | 3 | import com.dashlabs.invoicemanagement.view.admin.Admin 4 | import com.dashlabs.invoicemanagement.view.admin.AdminModel 5 | import com.dashlabs.invoicemanagement.view.customers.Customer 6 | import com.dashlabs.invoicemanagement.view.invoices.Invoice 7 | import com.dashlabs.invoicemanagement.view.products.Product 8 | import com.google.gson.Gson 9 | import com.j256.ormlite.dao.Dao 10 | import com.j256.ormlite.dao.DaoManager 11 | import com.j256.ormlite.jdbc.JdbcConnectionSource 12 | import com.j256.ormlite.support.DatabaseConnection.DEFAULT_RESULT_FLAGS 13 | import com.j256.ormlite.table.TableUtils 14 | import java.io.File 15 | import java.text.DecimalFormat 16 | import java.time.LocalDateTime 17 | import java.time.OffsetDateTime 18 | 19 | 20 | object Database { 21 | 22 | private var connectionSource: JdbcConnectionSource 23 | 24 | private var accountDao: Dao? 25 | 26 | private var productsDao: Dao? 27 | 28 | private var customerDao: Dao? 29 | 30 | private var invoicesDao: Dao? 31 | 32 | private var transactionsDao: Dao? 33 | 34 | init { 35 | connectionSource = getDatabaseConnection() 36 | 37 | TableUtils.createTableIfNotExists(connectionSource, AdminTable::class.java) 38 | TableUtils.createTableIfNotExists(connectionSource, TransactionTable::class.java) 39 | TableUtils.createTableIfNotExists(connectionSource, ProductsTable::class.java) 40 | TableUtils.createTableIfNotExists(connectionSource, CustomersTable::class.java) 41 | TableUtils.createTableIfNotExists(connectionSource, InvoiceTable::class.java) 42 | 43 | try { 44 | connectionSource.readOnlyConnection.executeStatement("ALTER TABLE products ADD deleted INT default 0", DEFAULT_RESULT_FLAGS) 45 | connectionSource.readOnlyConnection.executeStatement("ALTER TABLE customers ADD deleted INT default 0", DEFAULT_RESULT_FLAGS) 46 | connectionSource.readOnlyConnection.executeStatement("ALTER TABLE invoices ADD deleted INT default 0", DEFAULT_RESULT_FLAGS) 47 | } catch (ex: Exception) { 48 | 49 | } 50 | 51 | // instantiate the DAO to handle Account with String id 52 | accountDao = DaoManager.createDao(connectionSource, AdminTable::class.java) 53 | productsDao = DaoManager.createDao(connectionSource, ProductsTable::class.java) 54 | customerDao = DaoManager.createDao(connectionSource, CustomersTable::class.java) 55 | invoicesDao = DaoManager.createDao(connectionSource, InvoiceTable::class.java) 56 | transactionsDao = DaoManager.createDao(connectionSource, TransactionTable::class.java) 57 | 58 | } 59 | 60 | fun createAdmin(admin: Admin): AdminTable? { 61 | 62 | // create an instance of Account 63 | val adminTable = AdminTable() 64 | adminTable.name = admin.username 65 | adminTable.password = admin.password 66 | 67 | // persist the account object to the database 68 | val id = accountDao?.create(adminTable) 69 | connectionSource.close() 70 | 71 | id?.let { 72 | return if (it > 0) { 73 | adminTable 74 | } else { 75 | null 76 | } 77 | } ?: kotlin.run { 78 | return null 79 | } 80 | 81 | } 82 | 83 | fun changePassword(admin: Admin, adminModel: AdminModel): AdminTable? { 84 | checkAdminExists(admin)?.let { 85 | // persist the account object to the database 86 | // create an instance of Account 87 | val adminTable = AdminTable() 88 | adminTable.name = admin.username 89 | adminTable.password = adminModel.newpassword.value 90 | 91 | val id = accountDao?.update(adminTable) 92 | connectionSource.close() 93 | 94 | id?.let { 95 | return if (it > 0) { 96 | adminTable 97 | } else { 98 | null 99 | } 100 | } ?: kotlin.run { 101 | return null 102 | } 103 | } ?: kotlin.run { 104 | return null 105 | } 106 | } 107 | 108 | private fun getDatabaseConnection(): JdbcConnectionSource { 109 | Class.forName("org.sqlite.JDBC") 110 | val directory = File("~/invoicedatabase") 111 | directory.mkdirs() 112 | val dbConnectionString = "jdbc:sqlite:~/invoicedatabase/database.db" 113 | return JdbcConnectionSource(dbConnectionString) 114 | } 115 | 116 | fun checkAdminExists(admin: Admin): AdminTable? { 117 | try { 118 | val adminTable = AdminTable() 119 | adminTable.name = admin.username 120 | adminTable.password = admin.password 121 | var list = accountDao?.queryForMatchingArgs(adminTable) 122 | connectionSource.close() 123 | return list?.let { 124 | return if (it.size > 0) { 125 | it.first() 126 | } else { 127 | null 128 | } 129 | } ?: kotlin.run { 130 | return null 131 | } 132 | } catch (ex: Exception) { 133 | return null 134 | } 135 | 136 | } 137 | 138 | fun createTransaction(transactionTable: TransactionTable): TransactionTable? { 139 | val id = transactionsDao?.create(transactionTable) 140 | connectionSource?.close() 141 | 142 | id?.let { 143 | return if (it > 0) { 144 | transactionTable 145 | } else { 146 | null 147 | } 148 | } ?: kotlin.run { 149 | return null 150 | } 151 | } 152 | 153 | fun createProduct(product: Product): ProductsTable? { 154 | // create an instance of product 155 | val productsTable = ProductsTable() 156 | productsTable.productName = product.name 157 | productsTable.dateCreated = System.currentTimeMillis() 158 | productsTable.dateModified = System.currentTimeMillis() 159 | productsTable.amount = product.amount.toDouble().twoDecimalFormatted() 160 | // persist the account object to the database 161 | val id = productsDao?.create(productsTable) 162 | connectionSource.close() 163 | 164 | id?.let { 165 | return if (it > 0) { 166 | productsTable 167 | } else { 168 | null 169 | } 170 | } ?: kotlin.run { 171 | return null 172 | } 173 | 174 | } 175 | 176 | fun createCustomer(customer: Customer): CustomersTable? { 177 | // create an instance of product 178 | val customersTable = CustomersTable() 179 | customersTable.customerName = customer.name 180 | customersTable.dateCreated = System.currentTimeMillis() 181 | customersTable.address = customer.address.toString() 182 | customersTable.dateModified = System.currentTimeMillis() 183 | customersTable.district = customer.district 184 | customersTable.state = customer.state 185 | // persist the account object to the database 186 | val id = customerDao?.create(customersTable) 187 | connectionSource.close() 188 | 189 | id?.let { 190 | return if (it > 0) { 191 | customersTable 192 | } else { 193 | null 194 | } 195 | } ?: kotlin.run { 196 | return null 197 | } 198 | } 199 | 200 | fun listProducts(search: String = ""): List? { 201 | return if (search.isEmpty()) { 202 | productsDao?.queryBuilder()?.where()?.like(ProductsTable::deleted.name, false)?.query() 203 | } else { 204 | productsDao?.queryBuilder()?.where()?.like(ProductsTable::productName.name, "%$search%")?.and()?.like(ProductsTable::deleted.name, false)?.query() 205 | } 206 | } 207 | 208 | fun listCustomers(search: String = ""): List? { 209 | return if (search.isEmpty()) { 210 | customerDao?.queryBuilder()?.where()?.like(CustomersTable::deleted.name, false)?.query() 211 | } else { 212 | customerDao?.queryBuilder()?.where()?.like(CustomersTable::customerName.name, "%$search%")?.and()?.like(CustomersTable::deleted.name, false)?.query() 213 | } 214 | } 215 | 216 | fun listCustomers(state: String, district: String, address: String): List? { 217 | return when { 218 | !address.isEmpty() -> customerDao?.queryBuilder() 219 | ?.where() 220 | ?.like(CustomersTable::state.name, state)?.and() 221 | ?.like(CustomersTable::district.name, district)?.and() 222 | ?.like(CustomersTable::address.name, "%$address%") 223 | ?.query() 224 | else -> customerDao?.queryBuilder() 225 | ?.where() 226 | ?.like(CustomersTable::state.name, state)?.and() 227 | ?.like(CustomersTable::district.name, district) 228 | ?.query() 229 | } 230 | } 231 | 232 | fun listInvoices(): List? { 233 | val invoices = invoicesDao?.queryBuilder()?.where()?.like(InvoiceTable::deleted.name, false)?.query() 234 | return invoices?.sortedByDescending { it.dateModified }?.map { it.asMeaningfulInvoice() }?.filterNotNull() 235 | } 236 | 237 | fun listTransactions(customerId: Long): List? { 238 | val invoices = transactionsDao?.queryBuilder()?.where() 239 | ?.like(CustomersTable::customerId.name, customerId) 240 | ?.query() 241 | return invoices?.sortedByDescending { it.dateCreated }?.map { it.toMeaningfulTransaction(it) } 242 | } 243 | 244 | fun listInvoices(customerId: Long): List? { 245 | val invoices = invoicesDao?.queryBuilder()?.where()?.like(CustomersTable::customerId.name, customerId)?.and()?.like(InvoiceTable::deleted.name, false)?.query() 246 | return invoices?.sortedByDescending { it.dateModified }?.map { it.asMeaningfulInvoice() }?.filterNotNull() 247 | } 248 | 249 | fun listInvoicesSimple(customerId: Long): List? { 250 | val invoices = invoicesDao?.queryBuilder()?.where()?.like(CustomersTable::customerId.name, customerId)?.and()?.like(InvoiceTable::deleted.name, false)?.query() 251 | return invoices?.sortedByDescending { it.dateModified } 252 | } 253 | 254 | fun listInvoices(startTime: LocalDateTime, endTime: LocalDateTime): List? { 255 | val invoices = invoicesDao?.queryBuilder()?.where()?.like(InvoiceTable::deleted.name, false)?.and()?.between(InvoiceTable::dateModified.name, startTime.toEpochSecond(OffsetDateTime.now().offset).times(1000), endTime.toEpochSecond(OffsetDateTime.now().offset).times(1000))?.query() 256 | return invoices?.sortedByDescending { it.dateModified }?.map { it.asMeaningfulInvoice() }?.filterNotNull() 257 | } 258 | 259 | fun createInvoice(invoice: Invoice): InvoiceTable.MeaningfulInvoice? { 260 | val invoiceTable = InvoiceTable() 261 | invoiceTable.customerId = invoice.customerId 262 | invoiceTable.dateCreated = System.currentTimeMillis() 263 | invoiceTable.dateModified = System.currentTimeMillis() 264 | invoiceTable.amountTotal = invoice.productsPrice.toDouble().twoDecimalFormatted() 265 | 266 | invoice.creditAmount?.let { 267 | if (it.toDouble() > 0) { 268 | invoiceTable.outstandingAmount = invoice.creditAmount.toDouble().twoDecimalFormatted() 269 | } 270 | } 271 | 272 | invoiceTable.productsPurchased = Gson().toJson(invoice.productsList.map { Triple(it.productsTable, it.discount, it.quantity.toInt()) }.toMutableList()) 273 | // persist the account object to the database 274 | val id = invoicesDao?.create(invoiceTable) 275 | connectionSource.close() 276 | 277 | id?.let { 278 | return if (it > 0) { 279 | invoiceTable.asMeaningfulInvoice() 280 | } else { 281 | null 282 | } 283 | } ?: kotlin.run { 284 | return null 285 | } 286 | } 287 | 288 | fun getCustomer(customerId: Long): CustomersTable? { 289 | return customerDao?.queryBuilder()?.where() 290 | ?.like(CustomersTable::customerId.name, customerId) 291 | ?.and() 292 | ?.like(CustomersTable::deleted.name, false)?.query() 293 | ?.firstOrNull() 294 | } 295 | 296 | 297 | fun updateCustomer(customer: CustomersTable, deductValue: Double): Boolean { 298 | var reductionValue = deductValue 299 | 300 | listInvoicesSimple(customer.customerId)?.let { 301 | val total = it.map { it.outstandingAmount.twoDecimalFormatted() }.sum() 302 | if (total == deductValue) { 303 | it.forEach { 304 | it.outstandingAmount = 0.0 305 | invoicesDao?.update(it) 306 | } 307 | } else { 308 | val invoicesWithOut = it.filter { 309 | it.outstandingAmount > 0 310 | } 311 | 312 | for (it in invoicesWithOut) { 313 | if (it.outstandingAmount > reductionValue) { 314 | it.outstandingAmount -= reductionValue 315 | reductionValue = 0.0 316 | invoicesDao?.update(it) 317 | break 318 | } else { 319 | if (reductionValue == 0.0) { 320 | break 321 | } 322 | // the deduction amount is greater than current invoice 323 | // take the less out of reduction value 324 | val deductableAmount = Math.min(it.outstandingAmount, reductionValue).twoDecimalFormatted() 325 | // deduct it from current outstanding 326 | it.outstandingAmount -= deductableAmount 327 | reductionValue -= deductableAmount 328 | invoicesDao?.update(it) 329 | } 330 | } 331 | } 332 | } 333 | return true 334 | } 335 | 336 | fun deleteCustomer(customerId: Long): Boolean { 337 | customerDao?.queryBuilder()?.where()?.like(CustomersTable::customerId.name, customerId)?.queryForFirst()?.let { 338 | customerDao?.update(it.apply { 339 | this.deleted = true 340 | }) 341 | return true 342 | } 343 | return false 344 | } 345 | 346 | fun deleteProduct(productId: ProductsTable): Int? { 347 | productsDao?.update(productId.apply { 348 | this.deleted = true 349 | }) 350 | return 0 351 | } 352 | 353 | fun deleteInvoice(invoiceId: Long): Boolean { 354 | invoicesDao?.queryBuilder()?.where()?.like(InvoiceTable::invoiceId.name, invoiceId)?.queryForFirst()?.let { 355 | invoicesDao?.update(it.apply { 356 | this.deleted = true 357 | }) 358 | return true 359 | } 360 | return false 361 | } 362 | 363 | } 364 | 365 | fun Double.twoDecimalFormatted(): Double { 366 | return String.format("%.2f", this).toDouble() 367 | } 368 | -------------------------------------------------------------------------------- /src/main/resources/states-and-districts.json: -------------------------------------------------------------------------------- 1 | { 2 | "states":[ 3 | { 4 | "state":"Andhra Pradesh", 5 | "districts":[ 6 | "Anantapur", 7 | "Chittoor", 8 | "East Godavari", 9 | "Guntur", 10 | "Krishna", 11 | "Kurnool", 12 | "Nellore", 13 | "Prakasam", 14 | "Srikakulam", 15 | "Visakhapatnam", 16 | "Vizianagaram", 17 | "West Godavari", 18 | "YSR Kadapa" 19 | ] 20 | }, 21 | { 22 | "state":"Arunachal Pradesh", 23 | "districts":[ 24 | "Tawang", 25 | "West Kameng", 26 | "East Kameng", 27 | "Papum Pare", 28 | "Kurung Kumey", 29 | "Kra Daadi", 30 | "Lower Subansiri", 31 | "Upper Subansiri", 32 | "West Siang", 33 | "East Siang", 34 | "Siang", 35 | "Upper Siang", 36 | "Lower Siang", 37 | "Lower Dibang Valley", 38 | "Dibang Valley", 39 | "Anjaw", 40 | "Lohit", 41 | "Namsai", 42 | "Changlang", 43 | "Tirap", 44 | "Longding" 45 | ] 46 | }, 47 | { 48 | "state":"Assam", 49 | "districts":[ 50 | "Baksa", 51 | "Barpeta", 52 | "Biswanath", 53 | "Bongaigaon", 54 | "Cachar", 55 | "Charaideo", 56 | "Chirang", 57 | "Darrang", 58 | "Dhemaji", 59 | "Dhubri", 60 | "Dibrugarh", 61 | "Goalpara", 62 | "Golaghat", 63 | "Hailakandi", 64 | "Hojai", 65 | "Jorhat", 66 | "Kamrup Metropolitan", 67 | "Kamrup", 68 | "Karbi Anglong", 69 | "Karimganj", 70 | "Kokrajhar", 71 | "Lakhimpur", 72 | "Majuli", 73 | "Morigaon", 74 | "Nagaon", 75 | "Nalbari", 76 | "Dima Hasao", 77 | "Sivasagar", 78 | "Sonitpur", 79 | "South Salmara-Mankachar", 80 | "Tinsukia", 81 | "Udalguri", 82 | "West Karbi Anglong" 83 | ] 84 | }, 85 | { 86 | "state":"Bihar", 87 | "districts":[ 88 | "Araria", 89 | "Arwal", 90 | "Aurangabad", 91 | "Banka", 92 | "Begusarai", 93 | "Bhagalpur", 94 | "Bhojpur", 95 | "Buxar", 96 | "Darbhanga", 97 | "East Champaran (Motihari)", 98 | "Gaya", 99 | "Gopalganj", 100 | "Jamui", 101 | "Jehanabad", 102 | "Kaimur (Bhabua)", 103 | "Katihar", 104 | "Khagaria", 105 | "Kishanganj", 106 | "Lakhisarai", 107 | "Madhepura", 108 | "Madhubani", 109 | "Munger (Monghyr)", 110 | "Muzaffarpur", 111 | "Nalanda", 112 | "Nawada", 113 | "Patna", 114 | "Purnia (Purnea)", 115 | "Rohtas", 116 | "Saharsa", 117 | "Samastipur", 118 | "Saran", 119 | "Sheikhpura", 120 | "Sheohar", 121 | "Sitamarhi", 122 | "Siwan", 123 | "Supaul", 124 | "Vaishali", 125 | "West Champaran" 126 | ] 127 | }, 128 | { 129 | "state":"Chandigarh (UT)", 130 | "districts":[ 131 | "Chandigarh" 132 | ] 133 | }, 134 | { 135 | "state":"Chhattisgarh", 136 | "districts":[ 137 | "Balod", 138 | "Baloda Bazar", 139 | "Balrampur", 140 | "Bastar", 141 | "Bemetara", 142 | "Bijapur", 143 | "Bilaspur", 144 | "Dantewada (South Bastar)", 145 | "Dhamtari", 146 | "Durg", 147 | "Gariyaband", 148 | "Janjgir-Champa", 149 | "Jashpur", 150 | "Kabirdham (Kawardha)", 151 | "Kanker (North Bastar)", 152 | "Kondagaon", 153 | "Korba", 154 | "Korea (Koriya)", 155 | "Mahasamund", 156 | "Mungeli", 157 | "Narayanpur", 158 | "Raigarh", 159 | "Raipur", 160 | "Rajnandgaon", 161 | "Sukma", 162 | "Surajpur ", 163 | "Surguja" 164 | ] 165 | }, 166 | { 167 | "state":"Dadra and Nagar Haveli (UT)", 168 | "districts":[ 169 | "Dadra & Nagar Haveli" 170 | ] 171 | }, 172 | { 173 | "state":"Daman and Diu (UT)", 174 | "districts":[ 175 | "Daman", 176 | "Diu" 177 | ] 178 | }, 179 | { 180 | "state":"Delhi (NCT)", 181 | "districts":[ 182 | "Central Delhi", 183 | "East Delhi", 184 | "New Delhi", 185 | "North Delhi", 186 | "North East Delhi", 187 | "North West Delhi", 188 | "Shahdara", 189 | "South Delhi", 190 | "South East Delhi", 191 | "South West Delhi", 192 | "West Delhi" 193 | ] 194 | }, 195 | { 196 | "state":"Goa", 197 | "districts":[ 198 | "North Goa", 199 | "South Goa" 200 | ] 201 | }, 202 | { 203 | "state":"Gujarat", 204 | "districts":[ 205 | "Ahmedabad", 206 | "Amreli", 207 | "Anand", 208 | "Aravalli", 209 | "Banaskantha (Palanpur)", 210 | "Bharuch", 211 | "Bhavnagar", 212 | "Botad", 213 | "Chhota Udepur", 214 | "Dahod", 215 | "Dangs (Ahwa)", 216 | "Devbhoomi Dwarka", 217 | "Gandhinagar", 218 | "Gir Somnath", 219 | "Jamnagar", 220 | "Junagadh", 221 | "Kachchh", 222 | "Kheda (Nadiad)", 223 | "Mahisagar", 224 | "Mehsana", 225 | "Morbi", 226 | "Narmada (Rajpipla)", 227 | "Navsari", 228 | "Panchmahal (Godhra)", 229 | "Patan", 230 | "Porbandar", 231 | "Rajkot", 232 | "Sabarkantha (Himmatnagar)", 233 | "Surat", 234 | "Surendranagar", 235 | "Tapi (Vyara)", 236 | "Vadodara", 237 | "Valsad" 238 | ] 239 | }, 240 | { 241 | "state":"Haryana", 242 | "districts":[ 243 | "Ambala", 244 | "Bhiwani", 245 | "Charkhi Dadri", 246 | "Faridabad", 247 | "Fatehabad", 248 | "Gurgaon", 249 | "Hisar", 250 | "Jhajjar", 251 | "Jind", 252 | "Kaithal", 253 | "Karnal", 254 | "Kurukshetra", 255 | "Mahendragarh", 256 | "Mewat", 257 | "Palwal", 258 | "Panchkula", 259 | "Panipat", 260 | "Rewari", 261 | "Rohtak", 262 | "Sirsa", 263 | "Sonipat", 264 | "Yamunanagar" 265 | ] 266 | }, 267 | { 268 | "state":"Himachal Pradesh", 269 | "districts":[ 270 | "Bilaspur", 271 | "Chamba", 272 | "Hamirpur", 273 | "Kangra", 274 | "Kinnaur", 275 | "Kullu", 276 | "Lahaul & Spiti", 277 | "Mandi", 278 | "Shimla", 279 | "Sirmaur (Sirmour)", 280 | "Solan", 281 | "Una" 282 | ] 283 | }, 284 | { 285 | "state":"Jammu and Kashmir", 286 | "districts":[ 287 | "Anantnag", 288 | "Bandipore", 289 | "Baramulla", 290 | "Budgam", 291 | "Doda", 292 | "Ganderbal", 293 | "Jammu", 294 | "Kargil", 295 | "Kathua", 296 | "Kishtwar", 297 | "Kulgam", 298 | "Kupwara", 299 | "Leh", 300 | "Poonch", 301 | "Pulwama", 302 | "Rajouri", 303 | "Ramban", 304 | "Reasi", 305 | "Samba", 306 | "Shopian", 307 | "Srinagar", 308 | "Udhampur" 309 | ] 310 | }, 311 | { 312 | "state":"Jharkhand", 313 | "districts":[ 314 | "Bokaro", 315 | "Chatra", 316 | "Deoghar", 317 | "Dhanbad", 318 | "Dumka", 319 | "East Singhbhum", 320 | "Garhwa", 321 | "Giridih", 322 | "Godda", 323 | "Gumla", 324 | "Hazaribag", 325 | "Jamtara", 326 | "Khunti", 327 | "Koderma", 328 | "Latehar", 329 | "Lohardaga", 330 | "Pakur", 331 | "Palamu", 332 | "Ramgarh", 333 | "Ranchi", 334 | "Sahibganj", 335 | "Seraikela-Kharsawan", 336 | "Simdega", 337 | "West Singhbhum" 338 | ] 339 | }, 340 | { 341 | "state":"Karnataka", 342 | "districts":[ 343 | "Bagalkot", 344 | "Ballari (Bellary)", 345 | "Belagavi (Belgaum)", 346 | "Bengaluru (Bangalore) Rural", 347 | "Bengaluru (Bangalore) Urban", 348 | "Bidar", 349 | "Chamarajanagar", 350 | "Chikballapur", 351 | "Chikkamagaluru (Chikmagalur)", 352 | "Chitradurga", 353 | "Dakshina Kannada", 354 | "Davangere", 355 | "Dharwad", 356 | "Gadag", 357 | "Hassan", 358 | "Haveri", 359 | "Kalaburagi (Gulbarga)", 360 | "Kodagu", 361 | "Kolar", 362 | "Koppal", 363 | "Mandya", 364 | "Mysuru (Mysore)", 365 | "Raichur", 366 | "Ramanagara", 367 | "Shivamogga (Shimoga)", 368 | "Tumakuru (Tumkur)", 369 | "Udupi", 370 | "Uttara Kannada (Karwar)", 371 | "Vijayapura (Bijapur)", 372 | "Yadgir" 373 | ] 374 | }, 375 | { 376 | "state":"Kerala", 377 | "districts":[ 378 | "Alappuzha", 379 | "Ernakulam", 380 | "Idukki", 381 | "Kannur", 382 | "Kasaragod", 383 | "Kollam", 384 | "Kottayam", 385 | "Kozhikode", 386 | "Malappuram", 387 | "Palakkad", 388 | "Pathanamthitta", 389 | "Thiruvananthapuram", 390 | "Thrissur", 391 | "Wayanad" 392 | ] 393 | }, 394 | { 395 | "state":"Lakshadweep (UT)", 396 | "districts":[ 397 | "Agatti", 398 | "Amini", 399 | "Androth", 400 | "Bithra", 401 | "Chethlath", 402 | "Kavaratti", 403 | "Kadmath", 404 | "Kalpeni", 405 | "Kilthan", 406 | "Minicoy" 407 | ] 408 | }, 409 | { 410 | "state":"Madhya Pradesh", 411 | "districts":[ 412 | "Agar Malwa", 413 | "Alirajpur", 414 | "Anuppur", 415 | "Ashoknagar", 416 | "Balaghat", 417 | "Barwani", 418 | "Betul", 419 | "Bhind", 420 | "Bhopal", 421 | "Burhanpur", 422 | "Chhatarpur", 423 | "Chhindwara", 424 | "Damoh", 425 | "Datia", 426 | "Dewas", 427 | "Dhar", 428 | "Dindori", 429 | "Guna", 430 | "Gwalior", 431 | "Harda", 432 | "Hoshangabad", 433 | "Indore", 434 | "Jabalpur", 435 | "Jhabua", 436 | "Katni", 437 | "Khandwa", 438 | "Khargone", 439 | "Mandla", 440 | "Mandsaur", 441 | "Morena", 442 | "Narsinghpur", 443 | "Neemuch", 444 | "Panna", 445 | "Raisen", 446 | "Rajgarh", 447 | "Ratlam", 448 | "Rewa", 449 | "Sagar", 450 | "Satna", 451 | "Sehore", 452 | "Seoni", 453 | "Shahdol", 454 | "Shajapur", 455 | "Sheopur", 456 | "Shivpuri", 457 | "Sidhi", 458 | "Singrauli", 459 | "Tikamgarh", 460 | "Ujjain", 461 | "Umaria", 462 | "Vidisha" 463 | ] 464 | }, 465 | { 466 | "state":"Maharashtra", 467 | "districts":[ 468 | "Ahmednagar", 469 | "Akola", 470 | "Amravati", 471 | "Aurangabad", 472 | "Beed", 473 | "Bhandara", 474 | "Buldhana", 475 | "Chandrapur", 476 | "Dhule", 477 | "Gadchiroli", 478 | "Gondia", 479 | "Hingoli", 480 | "Jalgaon", 481 | "Jalna", 482 | "Kolhapur", 483 | "Latur", 484 | "Mumbai City", 485 | "Mumbai Suburban", 486 | "Nagpur", 487 | "Nanded", 488 | "Nandurbar", 489 | "Nashik", 490 | "Osmanabad", 491 | "Palghar", 492 | "Parbhani", 493 | "Pune", 494 | "Raigad", 495 | "Ratnagiri", 496 | "Sangli", 497 | "Satara", 498 | "Sindhudurg", 499 | "Solapur", 500 | "Thane", 501 | "Wardha", 502 | "Washim", 503 | "Yavatmal" 504 | ] 505 | }, 506 | { 507 | "state":"Manipur", 508 | "districts":[ 509 | "Bishnupur", 510 | "Chandel", 511 | "Churachandpur", 512 | "Imphal East", 513 | "Imphal West", 514 | "Jiribam", 515 | "Kakching", 516 | "Kamjong", 517 | "Kangpokpi", 518 | "Noney", 519 | "Pherzawl", 520 | "Senapati", 521 | "Tamenglong", 522 | "Tengnoupal", 523 | "Thoubal", 524 | "Ukhrul" 525 | ] 526 | }, 527 | { 528 | "state":"Meghalaya", 529 | "districts":[ 530 | "East Garo Hills", 531 | "East Jaintia Hills", 532 | "East Khasi Hills", 533 | "North Garo Hills", 534 | "Ri Bhoi", 535 | "South Garo Hills", 536 | "South West Garo Hills ", 537 | "South West Khasi Hills", 538 | "West Garo Hills", 539 | "West Jaintia Hills", 540 | "West Khasi Hills" 541 | ] 542 | }, 543 | { 544 | "state":"Mizoram", 545 | "districts":[ 546 | "Aizawl", 547 | "Champhai", 548 | "Kolasib", 549 | "Lawngtlai", 550 | "Lunglei", 551 | "Mamit", 552 | "Saiha", 553 | "Serchhip" 554 | ] 555 | }, 556 | { 557 | "state":"Nagaland", 558 | "districts":[ 559 | "Dimapur", 560 | "Kiphire", 561 | "Kohima", 562 | "Longleng", 563 | "Mokokchung", 564 | "Mon", 565 | "Peren", 566 | "Phek", 567 | "Tuensang", 568 | "Wokha", 569 | "Zunheboto" 570 | ] 571 | }, 572 | { 573 | "state":"Odisha", 574 | "districts":[ 575 | "Angul", 576 | "Balangir", 577 | "Balasore", 578 | "Bargarh", 579 | "Bhadrak", 580 | "Boudh", 581 | "Cuttack", 582 | "Deogarh", 583 | "Dhenkanal", 584 | "Gajapati", 585 | "Ganjam", 586 | "Jagatsinghapur", 587 | "Jajpur", 588 | "Jharsuguda", 589 | "Kalahandi", 590 | "Kandhamal", 591 | "Kendrapara", 592 | "Kendujhar (Keonjhar)", 593 | "Khordha", 594 | "Koraput", 595 | "Malkangiri", 596 | "Mayurbhanj", 597 | "Nabarangpur", 598 | "Nayagarh", 599 | "Nuapada", 600 | "Puri", 601 | "Rayagada", 602 | "Sambalpur", 603 | "Sonepur", 604 | "Sundargarh" 605 | ] 606 | }, 607 | { 608 | "state":"Puducherry (UT)", 609 | "districts":[ 610 | "Karaikal", 611 | "Mahe", 612 | "Pondicherry", 613 | "Yanam" 614 | ] 615 | }, 616 | { 617 | "state":"Punjab", 618 | "districts":[ 619 | "Amritsar", 620 | "Barnala", 621 | "Bathinda", 622 | "Faridkot", 623 | "Fatehgarh Sahib", 624 | "Fazilka", 625 | "Ferozepur", 626 | "Gurdaspur", 627 | "Hoshiarpur", 628 | "Jalandhar", 629 | "Kapurthala", 630 | "Ludhiana", 631 | "Mansa", 632 | "Moga", 633 | "Muktsar", 634 | "Nawanshahr (Shahid Bhagat Singh Nagar)", 635 | "Pathankot", 636 | "Patiala", 637 | "Rupnagar", 638 | "Sahibzada Ajit Singh Nagar (Mohali)", 639 | "Sangrur", 640 | "Tarn Taran" 641 | ] 642 | }, 643 | { 644 | "state":"Rajasthan", 645 | "districts":[ 646 | "Ajmer", 647 | "Alwar", 648 | "Banswara", 649 | "Baran", 650 | "Barmer", 651 | "Bharatpur", 652 | "Bhilwara", 653 | "Bikaner", 654 | "Bundi", 655 | "Chittorgarh", 656 | "Churu", 657 | "Dausa", 658 | "Dholpur", 659 | "Dungarpur", 660 | "Hanumangarh", 661 | "Jaipur", 662 | "Jaisalmer", 663 | "Jalore", 664 | "Jhalawar", 665 | "Jhunjhunu", 666 | "Jodhpur", 667 | "Karauli", 668 | "Kota", 669 | "Nagaur", 670 | "Pali", 671 | "Pratapgarh", 672 | "Rajsamand", 673 | "Sawai Madhopur", 674 | "Sikar", 675 | "Sirohi", 676 | "Sri Ganganagar", 677 | "Tonk", 678 | "Udaipur" 679 | ] 680 | }, 681 | { 682 | "state":"Sikkim", 683 | "districts":[ 684 | "East Sikkim", 685 | "North Sikkim", 686 | "South Sikkim", 687 | "West Sikkim" 688 | ] 689 | }, 690 | { 691 | "state":"Tamil Nadu", 692 | "districts":[ 693 | "Ariyalur", 694 | "Chennai", 695 | "Coimbatore", 696 | "Cuddalore", 697 | "Dharmapuri", 698 | "Dindigul", 699 | "Erode", 700 | "Kanchipuram", 701 | "Kanyakumari", 702 | "Karur", 703 | "Krishnagiri", 704 | "Madurai", 705 | "Nagapattinam", 706 | "Namakkal", 707 | "Nilgiris", 708 | "Perambalur", 709 | "Pudukkottai", 710 | "Ramanathapuram", 711 | "Salem", 712 | "Sivaganga", 713 | "Thanjavur", 714 | "Theni", 715 | "Thoothukudi (Tuticorin)", 716 | "Tiruchirappalli", 717 | "Tirunelveli", 718 | "Tiruppur", 719 | "Tiruvallur", 720 | "Tiruvannamalai", 721 | "Tiruvarur", 722 | "Vellore", 723 | "Viluppuram", 724 | "Virudhunagar" 725 | ] 726 | }, 727 | { 728 | "state":"Telangana", 729 | "districts":[ 730 | "Adilabad", 731 | "Bhadradri Kothagudem", 732 | "Hyderabad", 733 | "Jagtial", 734 | "Jangaon", 735 | "Jayashankar Bhoopalpally", 736 | "Jogulamba Gadwal", 737 | "Kamareddy", 738 | "Karimnagar", 739 | "Khammam", 740 | "Komaram Bheem Asifabad", 741 | "Mahabubabad", 742 | "Mahabubnagar", 743 | "Mancherial", 744 | "Medak", 745 | "Medchal", 746 | "Nagarkurnool", 747 | "Nalgonda", 748 | "Nirmal", 749 | "Nizamabad", 750 | "Peddapalli", 751 | "Rajanna Sircilla", 752 | "Rangareddy", 753 | "Sangareddy", 754 | "Siddipet", 755 | "Suryapet", 756 | "Vikarabad", 757 | "Wanaparthy", 758 | "Warangal (Rural)", 759 | "Warangal (Urban)", 760 | "Yadadri Bhuvanagiri" 761 | ] 762 | }, 763 | { 764 | "state":"Tripura", 765 | "districts":[ 766 | "Dhalai", 767 | "Gomati", 768 | "Khowai", 769 | "North Tripura", 770 | "Sepahijala", 771 | "South Tripura", 772 | "Unakoti", 773 | "West Tripura" 774 | ] 775 | }, 776 | { 777 | "state":"Uttarakhand", 778 | "districts":[ 779 | "Almora", 780 | "Bageshwar", 781 | "Chamoli", 782 | "Champawat", 783 | "Dehradun", 784 | "Haridwar", 785 | "Nainital", 786 | "Pauri Garhwal", 787 | "Pithoragarh", 788 | "Rudraprayag", 789 | "Tehri Garhwal", 790 | "Udham Singh Nagar", 791 | "Uttarkashi" 792 | ] 793 | }, 794 | { 795 | "state":"Uttar Pradesh", 796 | "districts":[ 797 | "Agra", 798 | "Aligarh", 799 | "Allahabad", 800 | "Ambedkar Nagar", 801 | "Amethi (Chatrapati Sahuji Mahraj Nagar)", 802 | "Amroha (J.P. Nagar)", 803 | "Auraiya", 804 | "Azamgarh", 805 | "Baghpat", 806 | "Bahraich", 807 | "Ballia", 808 | "Balrampur", 809 | "Banda", 810 | "Barabanki", 811 | "Bareilly", 812 | "Basti", 813 | "Bhadohi", 814 | "Bijnor", 815 | "Budaun", 816 | "Bulandshahr", 817 | "Chandauli", 818 | "Chitrakoot", 819 | "Deoria", 820 | "Etah", 821 | "Etawah", 822 | "Faizabad", 823 | "Farrukhabad", 824 | "Fatehpur", 825 | "Firozabad", 826 | "Gautam Buddha Nagar", 827 | "Ghaziabad", 828 | "Ghazipur", 829 | "Gonda", 830 | "Gorakhpur", 831 | "Hamirpur", 832 | "Hapur (Panchsheel Nagar)", 833 | "Hardoi", 834 | "Hathras", 835 | "Jalaun", 836 | "Jaunpur", 837 | "Jhansi", 838 | "Kannauj", 839 | "Kanpur Dehat", 840 | "Kanpur Nagar", 841 | "Kanshiram Nagar (Kasganj)", 842 | "Kaushambi", 843 | "Kushinagar (Padrauna)", 844 | "Lakhimpur - Kheri", 845 | "Lalitpur", 846 | "Lucknow", 847 | "Maharajganj", 848 | "Mahoba", 849 | "Mainpuri", 850 | "Mathura", 851 | "Mau", 852 | "Meerut", 853 | "Mirzapur", 854 | "Moradabad", 855 | "Muzaffarnagar", 856 | "Pilibhit", 857 | "Pratapgarh", 858 | "RaeBareli", 859 | "Rampur", 860 | "Saharanpur", 861 | "Sambhal (Bhim Nagar)", 862 | "Sant Kabir Nagar", 863 | "Shahjahanpur", 864 | "Shamali (Prabuddh Nagar)", 865 | "Shravasti", 866 | "Siddharth Nagar", 867 | "Sitapur", 868 | "Sonbhadra", 869 | "Sultanpur", 870 | "Unnao", 871 | "Varanasi" 872 | ] 873 | }, 874 | { 875 | "state":"West Bengal", 876 | "districts":[ 877 | "Alipurduar", 878 | "Bankura", 879 | "Birbhum", 880 | "Burdwan (Bardhaman)", 881 | "Cooch Behar", 882 | "Dakshin Dinajpur (South Dinajpur)", 883 | "Darjeeling", 884 | "Hooghly", 885 | "Howrah", 886 | "Jalpaiguri", 887 | "Kalimpong", 888 | "Kolkata", 889 | "Malda", 890 | "Murshidabad", 891 | "Nadia", 892 | "North 24 Parganas", 893 | "Paschim Medinipur (West Medinipur)", 894 | "Purba Medinipur (East Medinipur)", 895 | "Purulia", 896 | "South 24 Parganas", 897 | "Uttar Dinajpur (North Dinajpur)" 898 | ] 899 | } 900 | ] 901 | } 902 | --------------------------------------------------------------------------------