├── 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 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
22 |
23 |
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 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
23 |
24 |
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 |
--------------------------------------------------------------------------------