diff --git a/README.md b/README.md index 6975285..c211ae9 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ SQLite storage and PDF ledger reports. - **Ledger reports** — generate buyer, supplier, and transport ledgers as PDFs (via JasperReports), including agewise-outstanding variants. - **Local-first storage** — data is kept in a local SQLite database - (`database.db`), with helper scripts to configure periodic backups to a + (`database.db`), with helper scripts to configure backups to a private Git repository (see [Backups](#backups)). ## Tech stack @@ -40,7 +40,12 @@ SQLite storage and PDF ledger reports. ### Prerequisites -- JDK 17+ +- JDK 17, with `java` available on your `PATH`. +- A graphical desktop environment to run the application, and a PDF viewer + to open generated reports. + +The repository includes the Gradle wrapper; a separate Gradle installation +is not required. Gradle resolves JavaFX dependencies when running from source. ### Run from source @@ -48,22 +53,55 @@ SQLite storage and PDF ledger reports. ./gradlew run ``` +Run commands from the repository root. On Windows, use `gradlew.bat` in +place of `./gradlew`. + ### Build a runnable JAR ```shell ./gradlew shadowJar ``` -This produces `build/libs/OpenConsignment.jar`. Since the shadow JAR does not -bundle the JavaFX native modules, run it with a JavaFX SDK on the module path -— `scripts/start.sh` (macOS/Linux) and `scripts/start.bat` (Windows) do this -for you, defaulting to `~/javafx-sdk-17.0.19` (override via the -`JAVAFX_MODULE_PATH` environment variable): +This produces `build/libs/OpenConsignment.jar` and copies the launch scripts, +backup configuration script, and backup templates into `build/libs/`. +The launch scripts require a separate JavaFX 17 SDK matching your operating +system and CPU architecture. + +On macOS/Linux, the launcher defaults to `~/javafx-sdk-17.0.19/lib`: + +```shell +bash build/libs/start.sh +``` + +To use a different SDK location, set `JAVAFX_MODULE_PATH` to its `lib` directory: ```shell -./scripts/start.sh +JAVAFX_MODULE_PATH="/path/to/javafx-sdk-17/lib" bash build/libs/start.sh ``` +On Windows, edit `build/libs/start.bat` to match your local Java and JavaFX +installations, then run it from `build/libs/`: + +```bat +cd build\libs +start.bat +``` + +The batch script currently sets Java to `C:\Program Files\Java\jdk-17` +and the JavaFX module path to `%USERPROFILE%\javafx-sdk-17.0.18\lib`. +Keep the JAR and launch script together when moving the application. + +## Data and reports + +- The application creates and initializes `database.db` in its working + directory using `src/main/resources/sql/init.sql`. +- Running `./gradlew run` uses the repository root; the packaged launch + commands above use `build/libs/`. Each location has its own database. +- Reports are written to `report.pdf` in the working directory, replacing + the previous report, and opened with the system's default PDF viewer. +- The report organization name is configured in + `src/main/java/openconsignment/common/Constants.java`. + ## Testing ```shell @@ -75,10 +113,25 @@ for you, defaulting to `~/javafx-sdk-17.0.19` (override via the static `Utils.generatePdf` calls) and the repository layer (real in-memory SQLite, schema-initialized from `src/main/resources/sql/init.sql`). - **Functional tests** (`src/test/java/functional`) use TestFX to smoke-test - the JavaFX UI. + the JavaFX UI and check bill-entry layout. + +Tests run headlessly by default. To debug with visible JavaFX windows: + +```shell +./gradlew test -PheadlessTests=false +``` + +To run a specific group of tests: + +```shell +./gradlew test --tests 'openconsignment.service.*' +./gradlew test --tests 'openconsignment.repository.*' +./gradlew test --tests 'functional.*' +``` Every `test` run also produces a JaCoCo coverage report at -`build/reports/jacoco/test/html/index.html`. +`build/reports/jacoco/test/html/index.html`. The test results are available at +`build/reports/tests/test/index.html`. ## Code style @@ -105,19 +158,46 @@ src/main/java/openconsignment/ src/main/resources/ ├── fxml/ JavaFX view definitions ├── jrxml/ JasperReports report templates +├── css/ Shared and feature-specific stylesheets +├── icons/ Application and toolbar icons └── sql/ Database schema (init.sql) +src/test/java/ +├── openconsignment/ Service, repository, and dependency-injection tests +└── functional/ JavaFX UI tests +scripts/ Launchers and financial-year backup helpers +gradle/libs.versions.toml Dependency and plugin versions ``` ## Backups `scripts/configure-backup.py` generates year-scoped `upload`/`download` scripts (from `scripts/templates`) for backing up `database.db` to a -private, per-financial-year Git repository. Run it once per financial year: +private, per-financial-year Git repository. It requires Python 3, Git, and +SSH access to the backup repository. The repository must already exist; +the script clones it rather than creating it. + +The repository owner is currently hard-coded as `drcbabu81` in +`configure-backup.py`, with repository names such as +`Satyam-Database-2026-27`. Update the owner for your deployment. + +After building the JAR, run the configuration script from `build/libs/` +so it can find `templates/` and create the helpers beside `database.db`: ```shell -python3 scripts/configure-backup.py +cd build/libs +python3 configure-backup.py ``` +Enter a financial year such as `2026-27` when prompted. On Windows, use +`python configure-backup.py` from the same directory. The script generates +`upload.sh`/`download.sh` on macOS/Linux or `upload.bat`/`download.bat` on +Windows. Run the generated helpers from that directory. + +Review the templates before use: the shell upload template currently uses +a Windows-style separator in its database copy destination, which needs +adjusting for macOS/Linux. The download helper replaces the local +`database.db` with the repository copy. Backup scheduling is managed separately. + ## CI/CD GitHub Actions builds and tests every push/PR (`.github/workflows/build.yml`). diff --git a/build.gradle.kts b/build.gradle.kts index 5bfae65..e5b3c9e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,7 +54,10 @@ tasks.test { useJUnitPlatform() finalizedBy(tasks.jacocoTestReport) - if (System.getenv("CI") == "true") { + // Keep UI tests independent of desktop focus and native mouse permissions. + // Use -PheadlessTests=false when debugging tests with visible windows. + val headlessTests = providers.gradleProperty("headlessTests").map { it.toBoolean() }.getOrElse(true) + if (headlessTests) { jvmArgs( "-Dtestfx.robot=glass", "-Dtestfx.headless=true", diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..6a6d2ec --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +# Gradle 7.6's Groovy/Ant integration uses reflection to clean Java preferences listeners. +# This runs in the Gradle daemon (for example during jacocoTestReport), not the test JVM. +org.gradle.jvmargs=-Xmx512m -XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED diff --git a/src/main/java/openconsignment/common/Utils.java b/src/main/java/openconsignment/common/Utils.java index c249bcd..eb89baf 100644 --- a/src/main/java/openconsignment/common/Utils.java +++ b/src/main/java/openconsignment/common/Utils.java @@ -122,4 +122,18 @@ public static void generatePdf(String jrxmlFileName, Map payload throw ex; } } + + public static String nextVoucherNumber(String lastVoucher) { + if (null == lastVoucher) { + return "0"; + } + int paddingLength = lastVoucher.length(); + String format = "%0" + paddingLength + "d"; + try { + int number = Integer.parseInt(lastVoucher); + return String.format(format, number + 1); + } catch (NumberFormatException ex) { + return "0"; + } + } } diff --git a/src/main/java/openconsignment/controller/entry/bill/AddBillController.java b/src/main/java/openconsignment/controller/entry/BillEntryController.java similarity index 66% rename from src/main/java/openconsignment/controller/entry/bill/AddBillController.java rename to src/main/java/openconsignment/controller/entry/BillEntryController.java index 84336bd..656a386 100644 --- a/src/main/java/openconsignment/controller/entry/bill/AddBillController.java +++ b/src/main/java/openconsignment/controller/entry/BillEntryController.java @@ -1,14 +1,16 @@ -package openconsignment.controller.entry.bill; +package openconsignment.controller.entry; import static openconsignment.common.Utils.formatDate; import java.net.URL; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -19,10 +21,14 @@ import openconsignment.entity.BillEntity; import openconsignment.entity.LREntity; import openconsignment.model.LR; -import openconsignment.service.*; +import openconsignment.service.BillService; +import openconsignment.service.BuyerService; +import openconsignment.service.SupplierService; +import openconsignment.service.TransportService; import org.controlsfx.control.textfield.TextFields; -public class AddBillController implements Initializable { +@SuppressWarnings("unused") +public class BillEntryController implements Initializable { @FXML private TextField supplier_field; @@ -75,7 +81,7 @@ public class AddBillController implements Initializable { @FXML private Button delete_btn; - final ObservableList lrList = FXCollections.observableArrayList(); + private List lrList; private final BillService billService; private final SupplierService supplierService; @@ -83,7 +89,7 @@ public class AddBillController implements Initializable { private final TransportService transportService; @Inject - public AddBillController( + public BillEntryController( BillService billService, SupplierService supplierService, BuyerService buyerService, @@ -104,6 +110,8 @@ public void initialize(URL url, ResourceBundle rb) { lr_no_col.setCellValueFactory(new PropertyValueFactory<>("lrNo")); pm_col.setCellValueFactory(new PropertyValueFactory<>("pm")); + lrList = new ArrayList<>(); + } catch (Exception ex) { Utils.showAlert(ex.toString()); Logger.getLogger(BillEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); @@ -119,7 +127,7 @@ private void addToList(ActionEvent event) { return; } lrList.add(new LR(bill_no_field.getText(), lr_field.getText(), pm_field.getText())); - lrTable.setItems(lrList); + lrTable.setItems(FXCollections.observableList(lrList)); lr_field.clear(); pm_field.clear(); } @@ -174,6 +182,7 @@ private void clearAllFields() { pm_field.clear(); lr_field.clear(); lrList.clear(); + lrTable.setItems(FXCollections.observableList(lrList)); } @FXML @@ -193,6 +202,7 @@ private void replaceInList(ActionEvent event) { new LR(bill_no_field.getText(), lr_field.getText(), pm_field.getText())); lr_field.clear(); pm_field.clear(); + lrTable.setItems(FXCollections.observableList(lrList)); } @FXML @@ -202,5 +212,68 @@ private void deleteFromList(ActionEvent event) { return; } lrList.remove(lrTable.getSelectionModel().getSelectedIndex()); + lrTable.setItems(FXCollections.observableList(lrList)); + } + + @FXML + private void fetchBill(ActionEvent event) { + try { + BillEntity billEntity = billService.getBill(bill_no_field.getText()); + + if (null == billEntity) { + Utils.showAlert("Record not found. Please try again later.", 2); + return; + } + + supplier_field.setText(billEntity.getSupplierName()); + buyer_name_field.setText(billEntity.getBuyerName()); + date_field.setValue(Utils.parseDate(billEntity.getBillDate())); + transport_field.setText(billEntity.getTransport()); + lr_date.setValue(Utils.parseDate(billEntity.getLrDate())); + bill_amount_field.setText(billEntity.getBillAmount()); + + lrList = billEntity.getLrEntities().stream() + .map(i -> new LR(i.getBillNo(), i.getLrNo(), i.getPm())) + .collect(Collectors.toCollection(ArrayList::new)); + + lrTable.setItems(FXCollections.observableArrayList(lrList)); + // delete_entry_btn.setDisable(false); + + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + Logger.getLogger(BillEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); + } + } + + @FXML + private void deleteEntry(ActionEvent event) { + Alert alert = new Alert( + Alert.AlertType.CONFIRMATION, + "Are you sure that you want delete " + bill_no_field.getText() + " ?", + ButtonType.YES, + ButtonType.NO, + ButtonType.CANCEL); + alert.showAndWait(); + + if (alert.getResult() != ButtonType.YES) { + return; + } + + try { + billService.deleteBill(bill_no_field.getText()); + + supplier_field.setText(""); + buyer_name_field.setText(""); + date_field.setValue(null); + transport_field.setText(""); + lr_date.setValue(null); + bill_amount_field.setText(""); + lrTable.setItems(FXCollections.observableArrayList()); + + Utils.showAlert(bill_no_field.getText().toUpperCase() + " Entry was successfully deleted.", 1); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + Logger.getLogger(BillEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); + } } } diff --git a/src/main/java/openconsignment/controller/entry/collection/AddCollectionController.java b/src/main/java/openconsignment/controller/entry/CollectionEntryController.java similarity index 71% rename from src/main/java/openconsignment/controller/entry/collection/AddCollectionController.java rename to src/main/java/openconsignment/controller/entry/CollectionEntryController.java index 885d583..89f6eed 100644 --- a/src/main/java/openconsignment/controller/entry/collection/AddCollectionController.java +++ b/src/main/java/openconsignment/controller/entry/CollectionEntryController.java @@ -1,11 +1,14 @@ -package openconsignment.controller.entry.collection; +package openconsignment.controller.entry; import static openconsignment.common.Utils.formatDate; +import java.io.IOException; import java.net.URL; import java.sql.SQLException; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; import javafx.collections.FXCollections; @@ -15,6 +18,7 @@ import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; import javax.inject.Inject; +import net.sf.jasperreports.engine.JRException; import openconsignment.common.Constants; import openconsignment.common.Utils; import openconsignment.entity.BillEntity; @@ -25,13 +29,14 @@ import openconsignment.service.BuyerService; import openconsignment.service.CollectionService; -public class AddCollectionController implements Initializable { +public class CollectionEntryController implements Initializable { + private List collectionItemList; private List buyerNameComboList; private List billNoComboList; private int previouslyDue; - private static final Logger logger = Logger.getLogger(AddCollectionController.class.getName()); + private static final Logger logger = Logger.getLogger(CollectionEntryController.class.getName()); @FXML private TextField dd_no_field; @@ -54,6 +59,12 @@ public class AddCollectionController implements Initializable { @FXML private TableColumn bill_amt_col; + @FXML + private TableColumn bill_date_col; + + @FXML + private TableColumn due_col; + @FXML private TableColumn supplier_col; @@ -104,7 +115,7 @@ public class AddCollectionController implements Initializable { private final CollectionService collectionService; @Inject - public AddCollectionController( + public CollectionEntryController( BillService billService, BuyerService buyerService, CollectionService collectionService) { this.billService = billService; this.buyerService = buyerService; @@ -118,6 +129,8 @@ public void initialize(URL url, ResourceBundle rb) { billNoComboList = new ArrayList<>(); bill_no_col.setCellValueFactory(new PropertyValueFactory<>("billNo")); bill_amt_col.setCellValueFactory(new PropertyValueFactory<>("billAmount")); + bill_date_col.setCellValueFactory(new PropertyValueFactory<>("billDate")); + due_col.setCellValueFactory(new PropertyValueFactory<>("due")); supplier_col.setCellValueFactory(new PropertyValueFactory<>("supplierName")); amount_collection_col.setCellValueFactory(new PropertyValueFactory<>("amountCollected")); dd_no_col.setCellValueFactory(new PropertyValueFactory<>("ddNo")); @@ -144,6 +157,9 @@ private void fillBuyerNameCombo() { @FXML private void fillBillNoCombo() { + if (buyer_name.getValue() == null) { + return; + } try { billNoComboList = collectionService.fetchPendingBillNosForBuyer(buyer_name.getValue()); bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); @@ -284,14 +300,111 @@ private void deleteCollection(ActionEvent ignoredEvent) { @FXML private void clearAllFields() { clearRepeatingFields(); + previouslyDue = 0; + collectionItemList.clear(); + refreshCollectionTableView(); + updateTotalAmount(); voucher_no_field.setText(""); voucher_date_field.setValue(null); buyer_name.setDisable(false); buyer_name.getSelectionModel().clearSelection(); - billNoComboList.clear(); + billNoComboList = new ArrayList<>(); bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); } + @FXML + private void fetchCollectionVoucher() { + String voucherNo = voucher_no_field.getText().trim(); + if (voucherNo.isEmpty()) { + Utils.showAlert("Please enter a voucher number.", 2); + return; + } + try { + CollectionEntity voucher = collectionService.getCollection(voucherNo); + if (voucher == null) { + Utils.showAlert("Collection voucher not found", 2); + return; + } + List items = new ArrayList<>(); + for (CollectionItemEntity item : voucher.getItems()) { + items.add(CollectionItem.builder() + .billNo(item.getBillNo()) + .billDate(item.getBillDate()) + .billAmount(item.getBillAmount()) + .supplierName(item.getSupplierName()) + .due(item.getDue()) + .amountCollected(item.getAmountCollected()) + .bank(item.getBank()) + .ddNo(item.getDdNo()) + .ddDate(item.getDdDate()) + .build()); + } + clearRepeatingFields(); + previouslyDue = 0; + bill_no_combo.getSelectionModel().clearSelection(); + voucher_no_field.setText(voucherNo); + voucher_date_field.setValue(Utils.parseDate(voucher.getVoucherDate())); + buyer_name.setValue(voucher.getBuyerName()); + collectionItemList = items; + refreshCollectionTableView(); + total_amount_field.setText(voucher.getTotalAmount()); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, "Failed to fetch collection voucher", ex); + } + } + + @FXML + private void printCollectionVoucher() { + String voucherNo = voucher_no_field.getText().trim(); + if (voucherNo.isEmpty()) { + Utils.showAlert("Please enter a voucher number.", 2); + return; + } + try { + if (collectionService.getCollection(voucherNo) == null) { + Utils.showAlert("Collection voucher not found", 2); + return; + } + collectionService.generatePdf(voucherNo); + Utils.launchPdf(Constants.REPORT_FILE_NAME); + Utils.showAlert("Report Successfully Generated", 1); + } catch (IOException | JRException | SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, "Failed to print collection voucher", ex); + } + } + + @FXML + private void deleteCollectionVoucher() { + String voucherNo = voucher_no_field.getText().trim(); + if (voucherNo.isEmpty()) { + Utils.showAlert("Please enter a voucher number.", 2); + return; + } + try { + if (collectionService.getCollection(voucherNo) == null) { + Utils.showAlert("Collection voucher not found", 2); + return; + } + Alert alert = new Alert( + Alert.AlertType.CONFIRMATION, + "Are you sure that you want to delete collection voucher " + voucherNo + "?", + ButtonType.YES, + ButtonType.NO); + if (alert.showAndWait().orElse(ButtonType.NO) != ButtonType.YES) { + return; + } + collectionService.deleteCollection(voucherNo); + clearAllFields(); + updateLastVoucher(); + Utils.showAlert(voucherNo + " was successfully deleted.", 1); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, "Failed to delete collection voucher", ex); + } + } + @FXML private void saveData(ActionEvent ignoredEvent) { Alert alert = new Alert( @@ -339,18 +452,19 @@ private void saveData(ActionEvent ignoredEvent) { } catch (SQLException ex) { Utils.showAlert(ex.toString()); logger.log(Level.SEVERE, ex.toString(), ex); + return; } clearAllFields(); - collectionItemList.clear(); - refreshCollectionTableView(); updateLastVoucher(); } private void updateLastVoucher() { try { - String answer = collectionService.getLastVoucher(); - last_voucher_field.setText("Last Voucher No. : " + answer); + String lastVoucher = collectionService.getLastVoucher(); + last_voucher_field.setText("Last Voucher No. : " + lastVoucher); + String upcomingVoucherNo = Utils.nextVoucherNumber(lastVoucher); + voucher_no_field.setText(upcomingVoucherNo); } catch (SQLException ex) { Utils.showAlert(ex.toString()); logger.log(Level.SEVERE, ex.toString(), ex); diff --git a/src/main/java/openconsignment/controller/entry/EntryController.java b/src/main/java/openconsignment/controller/entry/EntryController.java index 3643812..70fe17e 100644 --- a/src/main/java/openconsignment/controller/entry/EntryController.java +++ b/src/main/java/openconsignment/controller/entry/EntryController.java @@ -10,13 +10,14 @@ import javax.inject.Inject; import openconsignment.common.ViewLoader; +@SuppressWarnings("unused") public class EntryController implements Initializable { @FXML private Button bill_entry_btn; @FXML - private Button collection_entry_brn; + private Button collection_entry_btn; @FXML private Button payment_entry_btn; @@ -36,16 +37,16 @@ public void initialize(URL url, ResourceBundle rb) {} @FXML private void showBillEntryScreen(ActionEvent event) { - viewLoader.loadRoot("/fxml/entry/bill/BillEntry.fxml", root); + viewLoader.loadRoot("/fxml/entry/BillEntry.fxml", root); } @FXML private void showCollectionEntryScreen(ActionEvent event) { - viewLoader.loadRoot("/fxml/entry/collection/CollectionEntry.fxml", root); + viewLoader.loadRoot("/fxml/entry/CollectionEntry.fxml", root); } @FXML private void showPaymentEntryScreen(ActionEvent event) { - viewLoader.loadRoot("/fxml/entry/payment/PaymentEntry.fxml", root); + viewLoader.loadRoot("/fxml/entry/PaymentEntry.fxml", root); } } diff --git a/src/main/java/openconsignment/controller/entry/payment/AddPaymentController.java b/src/main/java/openconsignment/controller/entry/PaymentEntryController.java similarity index 76% rename from src/main/java/openconsignment/controller/entry/payment/AddPaymentController.java rename to src/main/java/openconsignment/controller/entry/PaymentEntryController.java index 5b9a12b..5101e33 100644 --- a/src/main/java/openconsignment/controller/entry/payment/AddPaymentController.java +++ b/src/main/java/openconsignment/controller/entry/PaymentEntryController.java @@ -1,13 +1,15 @@ -package openconsignment.controller.entry.payment; +package openconsignment.controller.entry; import static openconsignment.common.Utils.formatDate; +import java.io.IOException; import java.net.URL; import java.sql.SQLException; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javafx.collections.FXCollections; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -16,6 +18,7 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.text.Text; import javax.inject.Inject; +import net.sf.jasperreports.engine.JRException; import openconsignment.common.Constants; import openconsignment.common.Utils; import openconsignment.entity.BillEntity; @@ -26,14 +29,15 @@ import openconsignment.service.PaymentService; import openconsignment.service.SupplierService; -public class AddPaymentController implements Initializable { +@SuppressWarnings("unused") +public class PaymentEntryController implements Initializable { private List paymentItems; private List supplierNameComboList; private List billNoComboList; private int previouslyDue; - private static final Logger logger = Logger.getLogger(AddPaymentController.class.getName()); + private static final Logger logger = Logger.getLogger(PaymentEntryController.class.getName()); @FXML private TextField dd_no_field; @@ -127,7 +131,7 @@ public class AddPaymentController implements Initializable { private final SupplierService supplierService; @Inject - public AddPaymentController( + public PaymentEntryController( BillService billService, PaymentService paymentService, SupplierService supplierService) { this.billService = billService; this.paymentService = paymentService; @@ -277,6 +281,7 @@ private void clearAllFields() { supplier_name_combo.getSelectionModel().clearSelection(); billNoComboList.clear(); bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); + total_amount_paid_field.setText(""); } @FXML @@ -307,7 +312,7 @@ private void saveData(ActionEvent ignoredEvent) { .billNo(it.getBillNo()) .billDate(formatDate(it.getBillDate())) .billAmount(it.getBillAmount()) - .dueAmount(it.getDue()) + .dueAmount(null == it.getDue() ? "0" : it.getDue()) .amountPaid(it.getAmountPaid()) .bank(it.getBank()) .ddNo(it.getDdNo()) @@ -340,8 +345,10 @@ private void saveData(ActionEvent ignoredEvent) { private void updateLastVoucher() { try { - String answer = paymentService.getLastVoucher(); - last_voucher_field.setText("Last Voucher No. : " + answer); + String lastVoucher = paymentService.getLastVoucher(); + last_voucher_field.setText("Last Voucher No. : " + lastVoucher); + String upcomingVoucherNo = Utils.nextVoucherNumber(lastVoucher); + voucher_no_field.setText(upcomingVoucherNo); } catch (SQLException ex) { Utils.showAlert(ex.toString()); logger.log(Level.SEVERE, ex.toString(), ex); @@ -370,7 +377,7 @@ private void fillBillNoCombo(ActionEvent ignoredEvent) { } @FXML - private void fetchData(ActionEvent ignoredEvent) { + private void fetchBillNos(ActionEvent ignoredEvent) { if (null == bill_no_combo.getValue()) { return; } @@ -397,4 +404,78 @@ private void updateDueAmount() { } due_amount_field.setText(Integer.toString(previouslyDue - amountPaid)); } + + public void fetchPaymentVoucher() { + if (voucher_no_field.getText().isEmpty()) { + Utils.showAlert("Voucher No. Field is kept empty. Please fill the voucher no."); + return; + } + + try { + PaymentEntity paymentEntity = paymentService.getPayment(voucher_no_field.getText()); + + if (null == paymentEntity) { + Utils.showAlert("Payment voucher not found"); + return; + } + + paymentItems = paymentEntity.getItems().stream() + .map(it -> PaymentItem.builder() + .billNo(it.getBillNo()) + .billAmount(it.getBillAmount()) + .billDate(it.getBillDate()) + .buyerName(it.getBuyerName()) + .amountPaid(it.getAmountPaid()) + .bank(it.getBank()) + .ddNo(it.getDdNo()) + .ddDate(it.getDdDate()) + .build()) + .collect(Collectors.toCollection(ArrayList::new)); + + voucher_date_field.setValue(Utils.parseDate(paymentEntity.getVoucherDate())); + supplier_name_combo.setValue(paymentEntity.getSupplierName()); + total_amount_paid_field.setText(paymentEntity.getTotalAmount()); + // display_board_label.setText(paymentEntity.getSupplierName()); + + payment_tableview.setItems(FXCollections.observableArrayList(paymentItems)); + // delete_entry_btn.setDisable(false); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + Logger.getLogger(PaymentEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); + } + } + + public void printPaymentVoucher() { + try { + paymentService.generatePdf(voucher_no_field.getText()); + Utils.launchPdf(Constants.REPORT_FILE_NAME); + Utils.showAlert("Report Successfully Generated", 1); + } catch (IOException | JRException | SQLException ex) { + Utils.showAlert(ex.toString()); + Logger.getLogger(PaymentEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); + } + } + + public void deletePaymentVoucher() { + Alert alert = new Alert( + Alert.AlertType.CONFIRMATION, + "Are you sure that you want delete " + voucher_no_field.getText() + " ?", + ButtonType.YES, + ButtonType.NO, + ButtonType.CANCEL); + alert.showAndWait(); + + if (alert.getResult() != ButtonType.YES) { + return; + } + + try { + paymentService.deletePayment(voucher_no_field.getText()); + + Utils.showAlert(voucher_no_field.getText().toUpperCase() + " Entry was successfully deleted.", 1); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + Logger.getLogger(PaymentEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); + } + } } diff --git a/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java b/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java deleted file mode 100644 index 6d93efa..0000000 --- a/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java +++ /dev/null @@ -1,15 +0,0 @@ -package openconsignment.controller.entry.bill; - -import java.net.URL; -import java.util.ResourceBundle; -import javafx.fxml.Initializable; -import javax.inject.Inject; - -public class BillEntryController implements Initializable { - - @Inject - public BillEntryController() {} - - @Override - public void initialize(URL location, ResourceBundle resources) {} -} diff --git a/src/main/java/openconsignment/controller/entry/bill/ViewDeleteBillController.java b/src/main/java/openconsignment/controller/entry/bill/ViewDeleteBillController.java deleted file mode 100644 index e0910ad..0000000 --- a/src/main/java/openconsignment/controller/entry/bill/ViewDeleteBillController.java +++ /dev/null @@ -1,151 +0,0 @@ -package openconsignment.controller.entry.bill; - -import java.net.URL; -import java.sql.SQLException; -import java.util.ResourceBundle; -import java.util.logging.Level; -import java.util.logging.Logger; -import javafx.collections.FXCollections; -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.*; -import javafx.scene.control.cell.PropertyValueFactory; -import javax.inject.Inject; -import openconsignment.common.Utils; -import openconsignment.entity.BillEntity; -import openconsignment.entity.LREntity; -import openconsignment.service.BillService; - -public class ViewDeleteBillController implements Initializable { - - private static final Logger logger = Logger.getLogger(ViewDeleteBillController.class.getName()); - - @FXML - private TextField supplier_field; - - @FXML - private TableView lr_table; - - @FXML - private TableColumn lr_no_col; - - @FXML - private TableColumn pm_col; - - @FXML - private TextField buyer_name_field; - - @FXML - private TextField bill_date_field; - - @FXML - private TextField transport_field; - - @FXML - private TextField bill_amount_field; - - @FXML - private TextField lr_date_field; - - @FXML - private Button get_details_btn; - - @FXML - private Button delete_entry_btn; - - @FXML - private TextField bill_no_field; - - private final BillService billService; - - @Inject - public ViewDeleteBillController(BillService billService) { - this.billService = billService; - } - - @Override - public void initialize(URL url, ResourceBundle rb) { - lr_no_col.setCellValueFactory(new PropertyValueFactory<>("lrNo")); - pm_col.setCellValueFactory(new PropertyValueFactory<>("pm")); - } - - @FXML - private void getDetails(ActionEvent event) { - try { - BillEntity billEntity = billService.getBill(bill_no_field.getText()); - - if (null == billEntity) { - Utils.showAlert("Record not found. Please try again later.", 2); - return; - } - - supplier_field.setText(billEntity.getSupplierName()); - buyer_name_field.setText(billEntity.getBuyerName()); - bill_date_field.setText(billEntity.getBillDate()); - transport_field.setText(billEntity.getTransport()); - lr_date_field.setText(billEntity.getLrDate()); - bill_amount_field.setText(billEntity.getBillAmount()); - - lr_table.setItems(FXCollections.observableArrayList(billEntity.getLrEntities())); - delete_entry_btn.setDisable(false); - - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, null, ex); - } - } - - @FXML - private void deleteEntry(ActionEvent event) { - Alert alert = new Alert( - Alert.AlertType.CONFIRMATION, - "Are you sure that you want delete " + bill_no_field.getText() + " ?", - ButtonType.YES, - ButtonType.NO, - ButtonType.CANCEL); - alert.showAndWait(); - - if (alert.getResult() != ButtonType.YES) { - return; - } - - try { - billService.deleteBill(bill_no_field.getText()); - - supplier_field.setText(""); - buyer_name_field.setText(""); - bill_date_field.setText(""); - transport_field.setText(""); - lr_date_field.setText(""); - bill_amount_field.setText(""); - lr_table.setItems(FXCollections.observableArrayList()); - - Utils.showAlert(bill_no_field.getText().toUpperCase() + " Entry was successfully deleted.", 1); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - public void updateBill(ActionEvent actionEvent) { - - BillEntity billEntity = BillEntity.builder() - .billNo(bill_no_field.getText()) - .supplierName(supplier_field.getText()) - .buyerName(buyer_name_field.getText()) - .billDate(bill_date_field.getText()) - .transport(transport_field.getText()) - .lrDate(lr_date_field.getText()) - .billAmount(bill_amount_field.getText()) - .build(); - - try { - billService.saveBill(billEntity); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, ex.toString(), ex); - } - } -} diff --git a/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java b/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java deleted file mode 100644 index 271fe83..0000000 --- a/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java +++ /dev/null @@ -1,15 +0,0 @@ -package openconsignment.controller.entry.collection; - -import java.net.URL; -import java.util.ResourceBundle; -import javafx.fxml.Initializable; -import javax.inject.Inject; - -public class CollectionEntryController implements Initializable { - - @Inject - public CollectionEntryController() {} - - @Override - public void initialize(URL location, ResourceBundle resources) {} -} diff --git a/src/main/java/openconsignment/controller/entry/collection/ViewDeleteCollectionController.java b/src/main/java/openconsignment/controller/entry/collection/ViewDeleteCollectionController.java deleted file mode 100644 index 9957866..0000000 --- a/src/main/java/openconsignment/controller/entry/collection/ViewDeleteCollectionController.java +++ /dev/null @@ -1,183 +0,0 @@ -package openconsignment.controller.entry.collection; - -import java.io.IOException; -import java.net.URL; -import java.sql.SQLException; -import java.util.List; -import java.util.ResourceBundle; -import java.util.logging.Level; -import java.util.logging.Logger; -import javafx.collections.FXCollections; -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.*; -import javafx.scene.control.cell.PropertyValueFactory; -import javax.inject.Inject; -import net.sf.jasperreports.engine.*; -import openconsignment.common.Constants; -import openconsignment.common.Utils; -import openconsignment.entity.CollectionEntity; -import openconsignment.model.CollectionItem; -import openconsignment.service.CollectionService; - -public class ViewDeleteCollectionController implements Initializable { - private List collectionItemList; - - @FXML - private TextField voucher_no_field; - - @FXML - private TableView collection_tableview; - - @FXML - private TableColumn bill_no_col; - - @FXML - private TableColumn bill_amt_col; - - @FXML - private TableColumn supplier_col; - - @FXML - private TableColumn amount_collection_col; - - @FXML - private TableColumn bank_col; - - @FXML - private TableColumn dd_no_col; - - @FXML - private TableColumn dd_date_col; - - @FXML - private Label display_board_label; - - @FXML - private TextField total_amount_field; - - @FXML - private Button get_details_btn; - - @FXML - private Button delete_entry_btn; - - @FXML - private TableColumn bill_date_col; - - @FXML - private TextField voucher_date; - - @FXML - private TextField buyer_name; - - @FXML - private Button print_btn; - - private final CollectionService collectionService; - - @Inject - public ViewDeleteCollectionController(CollectionService collectionService) { - this.collectionService = collectionService; - } - - @Override - public void initialize(URL url, ResourceBundle rb) { - collectionItemList = FXCollections.observableArrayList(); - - bill_no_col.setCellValueFactory(new PropertyValueFactory<>("billNo")); - bill_amt_col.setCellValueFactory(new PropertyValueFactory<>("billAmount")); - supplier_col.setCellValueFactory(new PropertyValueFactory<>("supplierName")); - bill_date_col.setCellValueFactory(new PropertyValueFactory<>("billDate")); - amount_collection_col.setCellValueFactory(new PropertyValueFactory<>("amountCollected")); - dd_no_col.setCellValueFactory(new PropertyValueFactory<>("ddNo")); - bank_col.setCellValueFactory(new PropertyValueFactory<>("bank")); - dd_date_col.setCellValueFactory(new PropertyValueFactory<>("ddDate")); - } - - @FXML - private void getDetails(ActionEvent event) { - - if (voucher_no_field.getText().isEmpty()) { - Utils.showAlert("Voucher No. Field is kept empty. Please fill the voucher no."); - return; - } - - try { - CollectionEntity collectionEntity = collectionService.getCollection(voucher_no_field.getText()); - - if (null == collectionEntity) { - Utils.showAlert("Collection voucher not found"); - return; - } - - collectionItemList = collectionEntity.getItems().stream() - .map(it -> CollectionItem.builder() - .billNo(it.getBillNo()) - .billAmount(it.getBillAmount()) - .billDate(it.getBillDate()) - .supplierName(it.getSupplierName()) - .amountCollected(it.getAmountCollected()) - .bank(it.getBank()) - .ddNo(it.getDdNo()) - .ddDate(it.getDdDate()) - .build()) - .toList(); - - voucher_date.setText(collectionEntity.getVoucherDate()); - buyer_name.setText(collectionEntity.getBuyerName()); - total_amount_field.setText(collectionEntity.getTotalAmount()); - display_board_label.setText(collectionEntity.getBuyerName()); - - collection_tableview.setItems(FXCollections.observableArrayList(collectionItemList)); - delete_entry_btn.setDisable(false); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - Logger.getLogger(ViewDeleteCollectionController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void deleteEntry(ActionEvent event) { - Alert alert = new Alert( - Alert.AlertType.CONFIRMATION, - "Are you sure that you want delete " + voucher_no_field.getText() + " ?", - ButtonType.YES, - ButtonType.NO, - ButtonType.CANCEL); - alert.showAndWait(); - - if (alert.getResult() != ButtonType.YES) { - return; - } - - try { - collectionService.deleteCollection(voucher_no_field.getText()); - - collectionItemList.clear(); - collection_tableview.setItems(FXCollections.observableArrayList()); - - voucher_date.setText(""); - buyer_name.setText(""); - display_board_label.setText(""); - total_amount_field.setText(""); - Utils.showAlert(voucher_no_field.getText().toUpperCase() + " Entry was successfully deleted.", 1); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - Logger.getLogger(ViewDeleteCollectionController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void printCollection(ActionEvent event) { - try { - collectionService.generatePdf(voucher_no_field.getText()); - Utils.launchPdf(Constants.REPORT_FILE_NAME); - Utils.showAlert("Report Successfully Generated", 1); - } catch (JRException | SQLException | IOException ex) { - Utils.showAlert(ex.toString()); - Logger.getLogger(ViewDeleteCollectionController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - } -} diff --git a/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java b/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java deleted file mode 100644 index fe7f9f0..0000000 --- a/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java +++ /dev/null @@ -1,15 +0,0 @@ -package openconsignment.controller.entry.payment; - -import java.net.URL; -import java.util.ResourceBundle; -import javafx.fxml.Initializable; -import javax.inject.Inject; - -public class PaymentEntryController implements Initializable { - - @Inject - public PaymentEntryController() {} - - @Override - public void initialize(URL location, ResourceBundle resources) {} -} diff --git a/src/main/java/openconsignment/controller/entry/payment/ViewDeletePaymentController.java b/src/main/java/openconsignment/controller/entry/payment/ViewDeletePaymentController.java deleted file mode 100644 index 8868d1a..0000000 --- a/src/main/java/openconsignment/controller/entry/payment/ViewDeletePaymentController.java +++ /dev/null @@ -1,191 +0,0 @@ -package openconsignment.controller.entry.payment; - -import java.io.IOException; -import java.net.URL; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; -import javafx.collections.FXCollections; -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.*; -import javafx.scene.control.cell.PropertyValueFactory; -import javax.inject.Inject; -import net.sf.jasperreports.engine.*; -import openconsignment.common.Constants; -import openconsignment.common.Utils; -import openconsignment.entity.PaymentEntity; -import openconsignment.model.PaymentItem; -import openconsignment.service.PaymentService; - -public class ViewDeletePaymentController implements Initializable { - - @FXML - private TextField voucher_no_field; - - @FXML - private TableColumn bill_no_col; - - @FXML - private TableColumn bill_amt_col; - - @FXML - private TableColumn bill_date_col; - - @FXML - private TableColumn buyer_col; - - @FXML - private TableColumn amount_paid_col; - - @FXML - private TableColumn bank_col; - - @FXML - private TableColumn dd_no_col; - - @FXML - private TableColumn dd_date_col; - - @FXML - private Label display_board_label; - - @FXML - private TextField total_amount_field; - - @FXML - private Button get_details_btn; - - @FXML - private Button delete_entry_btn; - - @FXML - private TextField voucher_date; - - @FXML - private TextField supplier_name; - - @FXML - private TableColumn due_col; - - @FXML - private TableView payment_tableview; - - @FXML - private Button print_payment_btn; - - private final PaymentService paymentService; - private final Connection conn; - - @Inject - public ViewDeletePaymentController(PaymentService paymentService, Connection conn) { - this.paymentService = paymentService; - this.conn = conn; - } - - @Override - public void initialize(URL url, ResourceBundle rb) { - bill_no_col.setCellValueFactory(new PropertyValueFactory<>("billNo")); - bill_amt_col.setCellValueFactory(new PropertyValueFactory<>("billAmount")); - buyer_col.setCellValueFactory(new PropertyValueFactory<>("buyerName")); - bill_date_col.setCellValueFactory(new PropertyValueFactory<>("billDate")); - due_col.setCellValueFactory(new PropertyValueFactory<>("due")); - amount_paid_col.setCellValueFactory(new PropertyValueFactory<>("amountPaid")); - dd_no_col.setCellValueFactory(new PropertyValueFactory<>("ddNo")); - bank_col.setCellValueFactory(new PropertyValueFactory<>("bank")); - dd_date_col.setCellValueFactory(new PropertyValueFactory<>("ddDate")); - } - - @FXML - private void getDetails(ActionEvent event) { - - if (voucher_no_field.getText().isEmpty()) { - Utils.showAlert("Voucher No. Field is kept empty. Please fill the voucher no."); - return; - } - - try { - PaymentEntity paymentEntity = paymentService.getPayment(voucher_no_field.getText()); - - if (null == paymentEntity) { - Utils.showAlert("Payment voucher not found"); - return; - } - - List paymentItemList = paymentEntity.getItems().stream() - .map(it -> PaymentItem.builder() - .billNo(it.getBillNo()) - .billAmount(it.getBillAmount()) - .billDate(it.getBillDate()) - .buyerName(it.getBuyerName()) - .amountPaid(it.getAmountPaid()) - .bank(it.getBank()) - .ddNo(it.getDdNo()) - .ddDate(it.getDdDate()) - .build()) - .toList(); - - voucher_date.setText(paymentEntity.getVoucherDate()); - supplier_name.setText(paymentEntity.getSupplierName()); - total_amount_field.setText(paymentEntity.getTotalAmount()); - display_board_label.setText(paymentEntity.getSupplierName()); - - payment_tableview.setItems(FXCollections.observableArrayList(paymentItemList)); - delete_entry_btn.setDisable(false); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - Logger.getLogger(ViewDeletePaymentController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void deleteEntry(ActionEvent event) { - Alert alert = new Alert( - Alert.AlertType.CONFIRMATION, - "Are you sure that you want delete " + voucher_no_field.getText() + " ?", - ButtonType.YES, - ButtonType.NO, - ButtonType.CANCEL); - alert.showAndWait(); - - if (alert.getResult() != ButtonType.YES) { - return; - } - - try { - paymentService.deletePayment(voucher_no_field.getText()); - - payment_tableview.setItems(FXCollections.observableArrayList()); - - voucher_date.setText(""); - supplier_name.setText(""); - display_board_label.setText(""); - total_amount_field.setText(""); - Utils.showAlert(voucher_no_field.getText().toUpperCase() + " Entry was successfully deleted.", 1); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - try { - conn.rollback(); - } catch (SQLException ex1) { - Utils.showAlert(ex1.toString()); - Logger.getLogger(PaymentEntryController.class.getName()).log(Level.SEVERE, ex1.toString(), ex1); - } - Logger.getLogger(PaymentEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void printPayment(ActionEvent event) { - try { - paymentService.generatePdf(voucher_no_field.getText()); - Utils.launchPdf(Constants.REPORT_FILE_NAME); - Utils.showAlert("Report Successfully Generated", 1); - } catch (IOException | JRException | SQLException ex) { - Utils.showAlert(ex.toString()); - Logger.getLogger(PaymentEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - } -} diff --git a/src/main/java/openconsignment/di/ControllerModule.java b/src/main/java/openconsignment/di/ControllerModule.java index 7dcb67e..bd963e1 100644 --- a/src/main/java/openconsignment/di/ControllerModule.java +++ b/src/main/java/openconsignment/di/ControllerModule.java @@ -7,16 +7,10 @@ import javafx.util.Callback; import openconsignment.controller.MainController; import openconsignment.controller.WelcomeScreenController; +import openconsignment.controller.entry.BillEntryController; +import openconsignment.controller.entry.CollectionEntryController; import openconsignment.controller.entry.EntryController; -import openconsignment.controller.entry.bill.AddBillController; -import openconsignment.controller.entry.bill.BillEntryController; -import openconsignment.controller.entry.bill.ViewDeleteBillController; -import openconsignment.controller.entry.collection.AddCollectionController; -import openconsignment.controller.entry.collection.CollectionEntryController; -import openconsignment.controller.entry.collection.ViewDeleteCollectionController; -import openconsignment.controller.entry.payment.AddPaymentController; -import openconsignment.controller.entry.payment.PaymentEntryController; -import openconsignment.controller.entry.payment.ViewDeletePaymentController; +import openconsignment.controller.entry.PaymentEntryController; import openconsignment.controller.history.BillHistoryController; import openconsignment.controller.history.CollectionHistoryController; import openconsignment.controller.history.PaymentHistoryController; @@ -106,16 +100,6 @@ public interface ControllerModule { @ClassKey(ReportController.class) Object bindReportController(ReportController controller); - @Binds - @IntoMap - @ClassKey(AddCollectionController.class) - Object bindAddCollectionController(AddCollectionController controller); - - @Binds - @IntoMap - @ClassKey(ViewDeleteCollectionController.class) - Object bindViewDeleteCollectionController(ViewDeleteCollectionController controller); - @Binds @IntoMap @ClassKey(CollectionEntryController.class) @@ -126,26 +110,6 @@ public interface ControllerModule { @ClassKey(BillEntryController.class) Object bindBillEntryController(BillEntryController controller); - @Binds - @IntoMap - @ClassKey(AddBillController.class) - Object bindAddBillController(AddBillController controller); - - @Binds - @IntoMap - @ClassKey(ViewDeleteBillController.class) - Object bindViewDeleteBillController(ViewDeleteBillController controller); - - @Binds - @IntoMap - @ClassKey(AddPaymentController.class) - Object bindAddPaymentController(AddPaymentController controller); - - @Binds - @IntoMap - @ClassKey(ViewDeletePaymentController.class) - Object bindViewDeletePaymentController(ViewDeletePaymentController controller); - @Binds @IntoMap @ClassKey(PaymentEntryController.class) diff --git a/src/main/java/openconsignment/repository/BillRepository.java b/src/main/java/openconsignment/repository/BillRepository.java index 181a1c1..09add82 100644 --- a/src/main/java/openconsignment/repository/BillRepository.java +++ b/src/main/java/openconsignment/repository/BillRepository.java @@ -33,6 +33,8 @@ public void save(BillEntity billEntity) throws SQLException { conn.setAutoCommit(false); + deleteBill(billEntity.getBillNo()); + PreparedStatement preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, billEntity.getSupplierName()); preparedStatement.setString(2, billEntity.getBuyerName()); diff --git a/src/main/java/openconsignment/repository/CollectionRepository.java b/src/main/java/openconsignment/repository/CollectionRepository.java index 98b39f6..1092ff6 100644 --- a/src/main/java/openconsignment/repository/CollectionRepository.java +++ b/src/main/java/openconsignment/repository/CollectionRepository.java @@ -177,8 +177,17 @@ cte_final as ( public void saveCollection(CollectionEntity collectionEntity) throws SQLException { try { conn.setAutoCommit(false); + deleteCollection(collectionEntity.getVoucherNo()); + // language=sql String sql = - "INSERT INTO `Collection_Entry_Table`(`Voucher No.`,`Voucher Date`,`Buyer Name`,`Total Amount`) VALUES (?,?,?,?)"; + """ + INSERT INTO `Collection_Entry_Table`(`Voucher No.`,`Voucher Date`,`Buyer Name`,`Total Amount`) + VALUES (?,?,?,?) + on conflict(`Voucher No.`) do update set + `Voucher Date` = excluded.`Voucher Date`, + `Buyer Name` = excluded.`Buyer Name`, + `Total Amount` = excluded.`Total Amount`; +"""; PreparedStatement preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, collectionEntity.getVoucherNo()); preparedStatement.setString(2, collectionEntity.getVoucherDate()); diff --git a/src/main/java/openconsignment/repository/PaymentRepository.java b/src/main/java/openconsignment/repository/PaymentRepository.java index a29a704..e997777 100644 --- a/src/main/java/openconsignment/repository/PaymentRepository.java +++ b/src/main/java/openconsignment/repository/PaymentRepository.java @@ -27,8 +27,16 @@ public PaymentRepository(Connection conn) { public void savePayment(PaymentEntity paymentEntity) throws SQLException { try { conn.setAutoCommit(false); + deletePayment(paymentEntity.getVoucherNo()); + // language=sql String sql = - "INSERT INTO `Payment_Entry_Table`(`Voucher No.`,`Voucher Date`,`Supplier Name`,`Total Amount`) VALUES (?,?,?,?)"; + """ + INSERT INTO `Payment_Entry_Table`(`Voucher No.`,`Voucher Date`,`Supplier Name`,`Total Amount`) VALUES (?,?,?,?) + on conflict(`Voucher No.`) do update set + `Voucher Date` = excluded.`Voucher Date`, + `Supplier Name` = excluded.`Supplier Name`, + `Total Amount` = excluded.`Total Amount`; +"""; PreparedStatement preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, paymentEntity.getVoucherNo()); preparedStatement.setString(2, paymentEntity.getVoucherDate()); diff --git a/src/main/java/openconsignment/service/PaymentService.java b/src/main/java/openconsignment/service/PaymentService.java index 9886b77..2615661 100644 --- a/src/main/java/openconsignment/service/PaymentService.java +++ b/src/main/java/openconsignment/service/PaymentService.java @@ -1,9 +1,11 @@ package openconsignment.service; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import net.sf.jasperreports.engine.JRException; @@ -25,7 +27,7 @@ public PaymentService(PaymentRepository paymentRepository) { public List fetchPendingBillNosForSupplier(String supplierName) throws SQLException { return paymentRepository.fetchPendingBillsForSupplier(supplierName).stream() .map(BillEntity::getBillNo) - .toList(); + .collect(Collectors.toCollection(ArrayList::new)); } public List fetchPendingBillsForSupplier(String supplierName) throws SQLException { diff --git a/src/main/resources/css/bills.css b/src/main/resources/css/bills.css new file mode 100644 index 0000000..c96fbe0 --- /dev/null +++ b/src/main/resources/css/bills.css @@ -0,0 +1,141 @@ +.bill-entry { + -fx-background-color: #f8fbff; + -fx-background-radius: 8; +} + +.bill-entry .bill-title { + -fx-text-fill: #17324f; + -fx-font-size: 23px; + -fx-font-weight: bold; +} + +.bill-entry .bill-muted { + -fx-text-fill: #64748b; + -fx-font-size: 12px; +} + +.bill-entry .bill-field-label { + -fx-text-fill: #49647f; + -fx-font-size: 12px; + -fx-font-weight: bold; +} + +.bill-entry .bill-section-title { + -fx-text-fill: #17324f; + -fx-font-size: 14px; + -fx-font-weight: bold; +} + +.bill-entry .text-field, +.bill-entry .date-picker { + -fx-background-color: white; + -fx-background-radius: 5; + -fx-border-color: #cbd9e7; + -fx-border-radius: 5; + -fx-pref-height: 36; + -fx-font-size: 13px; +} + +.bill-entry .text-field { + -fx-padding: 5 9; + -fx-text-fill: #17324f; + -fx-prompt-text-fill: #748397; +} + +.bill-entry .date-picker, +.bill-entry .date-picker > .text-field { + -fx-font-size: 16px; +} + +.bill-entry .date-picker > .text-field { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +.bill-entry .text-field:focused, +.bill-entry .date-picker:focused { + -fx-border-color: #4f91c8; +} + +.bill-entry .bill-primary-button, +.bill-entry .bill-secondary-button, +.bill-entry .bill-danger-button { + -fx-background-radius: 5; + -fx-pref-height: 36; + -fx-min-width: 78; + -fx-font-size: 12px; + -fx-font-weight: bold; + -fx-cursor: hand; +} + +.bill-entry .bill-primary-button { + -fx-background-color: #3478b8; + -fx-text-fill: white; +} + +.bill-entry .bill-primary-button:hover { + -fx-background-color: #28649d; +} + +.bill-entry .bill-secondary-button { + -fx-background-color: #e7eff8; + -fx-text-fill: #35536f; +} + +.bill-entry .bill-secondary-button:hover { + -fx-background-color: #d7e5f3; +} + +.bill-entry .bill-danger-button { + -fx-background-color: #fff1f2; + -fx-text-fill: #b42340; +} + +.bill-entry .bill-danger-button:hover { + -fx-background-color: #ffe1e5; +} + +.bill-entry .bill-primary-button:focused, +.bill-entry .bill-secondary-button:focused, +.bill-entry .bill-danger-button:focused { + -fx-border-color: #4f91c8; + -fx-border-radius: 5; + -fx-border-insets: -2; +} + +.bill-entry .table-view { + -fx-background-color: white; + -fx-background-radius: 5; + -fx-border-color: #d5e3f0; + -fx-border-radius: 5; + -fx-padding: 1; +} + +.bill-entry .table-view .column-header-background, +.bill-entry .table-view .column-header, +.bill-entry .table-view .filler { + -fx-background-color: #edf3fa; +} + +.bill-entry .table-view .column-header { + -fx-size: 32; + -fx-border-color: transparent #dce6f1 #dce6f1 transparent; +} + +.bill-entry .table-view .column-header .label { + -fx-text-fill: #49647f; + -fx-font-size: 12px; + -fx-font-weight: bold; +} + +.bill-entry .bill-footer { + -fx-padding: 14 0 0 0; + -fx-border-color: #dce6f1 transparent transparent transparent; + -fx-border-width: 1 0 0 0; +} + +.bill-entry .bill-amount-field { + -fx-background-color: #eaf2fa; + -fx-font-size: 16px; + -fx-font-weight: bold; +} diff --git a/src/main/resources/css/collections.css b/src/main/resources/css/collections.css new file mode 100644 index 0000000..bb9f37a --- /dev/null +++ b/src/main/resources/css/collections.css @@ -0,0 +1,149 @@ +.collection-page-title { + -fx-fill: #343c46; + -fx-font-size: 27px; + -fx-font-weight: bold; +} + +.collection-entry { + -fx-background-color: #fffdf5; + -fx-background-radius: 8; +} + +.collection-field-label { + -fx-fill: #5e594a; + -fx-font-size: 12px; + -fx-font-weight: bold; +} + +.collection-muted { + -fx-text-fill: #77715f; + -fx-font-size: 12px; +} + +.collection-summary, +.collection-total-label { + -fx-font-size: 14px; + -fx-font-weight: bold; +} + +.collection-summary { + -fx-text-fill: #343c46; +} + +.collection-total-label { + -fx-fill: #343c46; +} + +.collection-entry .text-field, +.collection-entry .combo-box, +.collection-entry .date-picker { + -fx-background-color: white; + -fx-background-radius: 5; + -fx-border-color: #dcd5c1; + -fx-border-radius: 5; + -fx-font-size: 13px; +} + +.collection-entry .text-field { + -fx-padding: 5 8; + -fx-text-fill: #343c46; + -fx-prompt-text-fill: #817d70; +} + +.collection-entry .text-field:readonly { + -fx-background-color: #f7f5ec; +} + +.collection-entry .date-picker > .text-field { + -fx-border-width: 0; + -fx-background-color: transparent; +} + +.collection-entry .text-field:focused, +.collection-entry .combo-box:focused, +.collection-entry .date-picker:focused { + -fx-border-color: #a8791d; +} + +.collection-entry .collection-total-field:readonly { + -fx-background-color: #f4edda; + -fx-font-size: 16px; + -fx-font-weight: bold; +} + +.collection-primary-button, +.collection-secondary-button, +.collection-danger-button { + -fx-background-radius: 5; + -fx-font-size: 12px; + -fx-font-weight: bold; + -fx-cursor: hand; +} + +.collection-primary-button { + -fx-background-color: #896219; + -fx-text-fill: white; +} + +.collection-primary-button:hover { + -fx-background-color: #704e12; +} + +.collection-secondary-button { + -fx-background-color: #eee8d7; + -fx-text-fill: #5e512f; +} + +.collection-secondary-button:hover { + -fx-background-color: #e4dcc6; +} + +.collection-danger-button { + -fx-background-color: #fff0ef; + -fx-text-fill: #ab3440; +} + +.collection-danger-button:hover { + -fx-background-color: #fbe0df; +} + +.collection-primary-button:focused, +.collection-secondary-button:focused, +.collection-danger-button:focused { + -fx-border-color: #a8791d; + -fx-border-radius: 5; + -fx-border-insets: -2; +} + +.collection-entry .table-view { + -fx-background-color: white; + -fx-background-radius: 5; + -fx-border-color: #e1dac6; + -fx-border-radius: 5; + -fx-padding: 1; + -fx-selection-bar: #eee3bf; + -fx-selection-bar-non-focused: #f3edda; + -fx-selection-bar-text: #343c46; +} + +.collection-entry .table-view .column-header-background, +.collection-entry .table-view .column-header, +.collection-entry .table-view .filler { + -fx-background-color: #f5f0df; +} + +.collection-entry .table-view .column-header { + -fx-size: 32; + -fx-border-color: transparent #e9e1cc #e9e1cc transparent; +} + +.collection-entry .table-view .column-header .label { + -fx-text-fill: #5e594a; + -fx-font-size: 12px; + -fx-font-weight: bold; +} + +.collection-entry .table-view .placeholder .label { + -fx-text-fill: #77715f; + -fx-font-size: 12px; +} diff --git a/src/main/resources/fxml/main.css b/src/main/resources/css/main.css similarity index 56% rename from src/main/resources/fxml/main.css rename to src/main/resources/css/main.css index dd98a9d..73e31b0 100644 --- a/src/main/resources/fxml/main.css +++ b/src/main/resources/css/main.css @@ -1,37 +1,18 @@ -/* - * Empty Stylesheet file. - */ - -.mainFxmlClass { - -} - -JFXButton { - -fx-background-color: linear-gradient(#7480f8, #00f6ff); - -fx-background-radius: 15; - -} - -JFXButton:hover { - -fx-background-color: #38ae00; - -fx-text-fill: yellow; - -} - - -.tab-pane .tab-header-background { - -fx-background-color: #e0f7fa; /* light sky blue */ -} - -.tab-pane .tab { - -fx-background-insets: 0; - -fx-padding: 10 15 10 15; - -fx-background-radius: 8 8 0 0; - -fx-font-weight: bold; - -fx-background-color: #b3e5fc; -} - -.tab-pane .tab:selected { - -fx-background-color: #4fc3f7; - -fx-text-fill: white; -} + + +.tab-pane .tab-header-background { + -fx-background-color: #e0f7fa; /* light sky blue */ +} + +.tab-pane .tab { + -fx-background-insets: 0; + -fx-padding: 10 15 10 15; + -fx-background-radius: 8 8 0 0; + -fx-font-weight: bold; + -fx-background-color: #b3e5fc; +} + +.tab-pane .tab:selected { + -fx-background-color: #4fc3f7; + -fx-text-fill: white; +} diff --git a/src/main/resources/css/payments.css b/src/main/resources/css/payments.css new file mode 100644 index 0000000..bb8e125 --- /dev/null +++ b/src/main/resources/css/payments.css @@ -0,0 +1,79 @@ +.payment-page-title { + -fx-fill: #17324f; + -fx-font-weight: bold; +} + +.payment-muted { + -fx-fill: #647f9b; + -fx-font-size: 12px; +} + +.payment-entry { + -fx-background-color: #fbfdff; + -fx-background-radius: 8; +} + +.payment-field-label { + -fx-fill: #49647f; + -fx-font-size: 12px; + -fx-font-weight: bold; +} + +.payment-total-label { + -fx-fill: #17324f; + -fx-font-size: 14px; + -fx-font-weight: bold; +} + +.payment-primary-button { + -fx-background-color: #3478b8; + -fx-background-radius: 5; + -fx-text-fill: white; + -fx-font-weight: bold; +} + +.payment-primary-button:hover { + -fx-background-color: #28649d; +} + +.payment-secondary-button { + -fx-background-color: #eaf2fa; + -fx-background-radius: 5; + -fx-text-fill: #35536f; +} + +.payment-danger-button { + -fx-background-color: #fff1f2; + -fx-background-radius: 5; + -fx-text-fill: #be123c; +} + +.payment-entry .text-field, +.payment-entry .combo-box, +.payment-entry .date-picker { + -fx-background-radius: 5; + -fx-border-color: #c8d9e9; + -fx-border-radius: 5; + -fx-padding: 5 8 5 8; +} + +.payment-entry .text-field:focused, +.payment-entry .combo-box:focused, +.payment-entry .date-picker:focused { + -fx-border-color: #4f91c8; +} + +.payment-entry .table-view { + -fx-border-color: #d5e3f0; + -fx-border-radius: 5; + -fx-background-radius: 5; +} + +.payment-entry .table-view .column-header-background { + -fx-background-color: #f0f6fc; +} + +.payment-entry .table-view .column-header .label { + -fx-text-fill: #49647f; + -fx-font-weight: bold; +} \ No newline at end of file diff --git a/src/main/resources/fxml/Main.fxml b/src/main/resources/fxml/Main.fxml index 04a7e21..6357a76 100644 --- a/src/main/resources/fxml/Main.fxml +++ b/src/main/resources/fxml/Main.fxml @@ -7,7 +7,7 @@ - +
diff --git a/src/main/resources/fxml/entry/BillEntry.fxml b/src/main/resources/fxml/entry/BillEntry.fxml new file mode 100644 index 0000000..4284919 --- /dev/null +++ b/src/main/resources/fxml/entry/BillEntry.fxml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/entry/collection/AddCollection.fxml b/src/main/resources/fxml/entry/collection/AddCollection.fxml deleted file mode 100644 index 0583481..0000000 --- a/src/main/resources/fxml/entry/collection/AddCollection.fxml +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/entry/collection/CollectionEntry.fxml b/src/main/resources/fxml/entry/collection/CollectionEntry.fxml deleted file mode 100644 index ebfa86f..0000000 --- a/src/main/resources/fxml/entry/collection/CollectionEntry.fxml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/entry/collection/ViewAndDeleteCollection.fxml b/src/main/resources/fxml/entry/collection/ViewAndDeleteCollection.fxml deleted file mode 100644 index c6cb55d..0000000 --- a/src/main/resources/fxml/entry/collection/ViewAndDeleteCollection.fxml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/entry/payment/AddPayment.fxml b/src/main/resources/fxml/entry/payment/AddPayment.fxml deleted file mode 100644 index 4e7eda9..0000000 --- a/src/main/resources/fxml/entry/payment/AddPayment.fxml +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/entry/payment/PaymentEntry.fxml b/src/main/resources/fxml/entry/payment/PaymentEntry.fxml deleted file mode 100644 index 3f7c93f..0000000 --- a/src/main/resources/fxml/entry/payment/PaymentEntry.fxml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/entry/payment/ViewAndDeletePayment.fxml b/src/main/resources/fxml/entry/payment/ViewAndDeletePayment.fxml deleted file mode 100644 index cf60721..0000000 --- a/src/main/resources/fxml/entry/payment/ViewAndDeletePayment.fxml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/java/functional/AddBillLayoutTest.java b/src/test/java/functional/AddBillLayoutTest.java new file mode 100644 index 0000000..b72635c --- /dev/null +++ b/src/test/java/functional/AddBillLayoutTest.java @@ -0,0 +1,70 @@ +package functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.stage.Stage; +import openconsignment.controller.entry.BillEntryController; +import openconsignment.model.LR; +import openconsignment.service.BillService; +import openconsignment.service.BuyerService; +import openconsignment.service.SupplierService; +import openconsignment.service.TransportService; +import org.junit.jupiter.api.Test; +import org.testfx.framework.junit5.ApplicationTest; + +class AddBillLayoutTest extends ApplicationTest { + @Override + public void start(Stage stage) throws Exception { + SupplierService suppliers = mock(SupplierService.class); + BuyerService buyers = mock(BuyerService.class); + TransportService transports = mock(TransportService.class); + when(suppliers.getAllSuppliers()).thenReturn(List.of()); + when(buyers.getAllBuyers()).thenReturn(List.of()); + when(transports.getAllTransports()).thenReturn(List.of()); + + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/entry/BillEntry.fxml")); + loader.setControllerFactory( + type -> new BillEntryController(mock(BillService.class), suppliers, buyers, transports)); + Parent root = loader.load(); + // Match the width available inside the existing bill-entry tab. + stage.setScene(new Scene(root, 717, 600)); + stage.show(); + } + + @Test + void layoutFitsTabAndLorryReceiptActionsRemainConnected() { + interact(() -> { + TextField billNo = lookup("#bill_no_field").query(); + TextField lrNo = lookup("#lr_field").query(); + TextField pm = lookup("#pm_field").query(); + billNo.setText("B-100"); + lrNo.setText("LR-1"); + pm.setText("10"); + Button save = lookup("#save_btn").queryButton(); + assertTrue(save.localToScene(save.getBoundsInLocal()).getMaxX() <= 717); + assertTrue(save.localToScene(save.getBoundsInLocal()).getMaxY() <= 600); + }); + clickOn("#add_btn"); + interact(() -> { + TableView table = lookup("#lrTable").queryTableView(); + assertEquals(1, table.getItems().size()); + assertEquals("LR-1", table.getItems().get(0).getLrNo()); + table.getSelectionModel().select(0); + }); + clickOn("#delete_btn"); + interact(() -> assertTrue(lookup("#lrTable").queryTableView().getItems().isEmpty())); + clickOn("#clear_btn"); + interact(() -> assertEquals( + "", lookup("#bill_no_field").queryTextInputControl().getText())); + } +} diff --git a/src/test/java/functional/CollectionEntryTest.java b/src/test/java/functional/CollectionEntryTest.java new file mode 100644 index 0000000..b83af5b --- /dev/null +++ b/src/test/java/functional/CollectionEntryTest.java @@ -0,0 +1,127 @@ +package functional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.LocalDate; +import java.util.List; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.DatePicker; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.stage.Stage; +import openconsignment.controller.entry.CollectionEntryController; +import openconsignment.entity.CollectionEntity; +import openconsignment.entity.CollectionItemEntity; +import openconsignment.model.CollectionItem; +import openconsignment.service.BillService; +import openconsignment.service.BuyerService; +import openconsignment.service.CollectionService; +import org.junit.jupiter.api.Test; +import org.testfx.framework.junit5.ApplicationTest; + +class CollectionEntryTest extends ApplicationTest { + private CollectionService collectionService; + + @Override + public void start(Stage stage) throws Exception { + collectionService = mock(CollectionService.class); + BuyerService buyerService = mock(BuyerService.class); + when(buyerService.getAllBuyers()).thenReturn(List.of("Acme Buyer")); + when(collectionService.fetchPendingBillNosForBuyer("Acme Buyer")).thenReturn(List.of()); + when(collectionService.getLastVoucher()).thenReturn("C-100"); + when(collectionService.getCollection("C-100")) + .thenReturn(CollectionEntity.builder() + .voucherNo("C-100") + .voucherDate("27-10-2026") + .buyerName("Acme Buyer") + .totalAmount("250") + .items(List.of(CollectionItemEntity.builder() + .billNo("B-1") + .billDate("20-10-2026") + .billAmount("1000") + .supplierName("Acme Supplier") + .due("750") + .amountCollected("250") + .bank("Bank") + .ddNo("DD-1") + .ddDate("27-10-2026") + .build())) + .build()); + + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/entry/CollectionEntry.fxml")); + loader.setControllerFactory( + type -> new CollectionEntryController(mock(BillService.class), buyerService, collectionService)); + Parent root = loader.load(); + stage.setScene(new Scene(root)); + stage.show(); + } + + @Test + void fetchLoadsVoucherAndAllowsRemovingRowsAndClearingTheForm() { + fetchVoucher(); + + interact(() -> { + TableView table = lookup("#collection_tableview").queryTableView(); + assertEquals(1, table.getItems().size()); + assertEquals("750", table.getItems().get(0).getDue()); + assertEquals("20-10-2026", table.getItems().get(0).getBillDate()); + assertEquals( + "250", lookup("#total_amount_field").queryTextInputControl().getText()); + DatePicker date = lookup("#voucher_date_field").query(); + assertEquals(LocalDate.of(2026, 10, 27), date.getValue()); + assertEquals("Acme Buyer", lookup("#buyer_name").queryComboBox().getValue()); + table.getSelectionModel().select(0); + }); + clickOn("#delete_collection_btn"); + interact(() -> { + assertTrue( + lookup("#collection_tableview").queryTableView().getItems().isEmpty()); + assertEquals( + "0", lookup("#total_amount_field").queryTextInputControl().getText()); + }); + + clickOn("#fetch_collection_voucher_btn"); + clickOn("#clear_btn"); + interact(() -> { + assertEquals("", lookup("#voucher_no_field").queryTextInputControl().getText()); + assertTrue( + lookup("#collection_tableview").queryTableView().getItems().isEmpty()); + assertEquals( + "0", lookup("#total_amount_field").queryTextInputControl().getText()); + }); + } + + @Test + void deleteRequiresConfirmationAndClearsVoucherAfterSuccess() throws Exception { + fetchVoucher(); + clickOn("#delete_collection_voucher_btn"); + clickOn("No"); + verify(collectionService, never()).deleteCollection("C-100"); + + clickOn("#delete_collection_voucher_btn"); + clickOn("Yes"); + clickOn("OK"); + verify(collectionService).deleteCollection("C-100"); + interact(() -> { + assertTrue( + lookup("#collection_tableview").queryTableView().getItems().isEmpty()); + assertEquals( + "0", lookup("#voucher_no_field").queryTextInputControl().getText()); + }); + } + + private void fetchVoucher() { + interact(() -> { + TextField voucherNo = lookup("#voucher_no_field").query(); + voucherNo.setText("C-100"); + }); + clickOn("#fetch_collection_voucher_btn"); + } +} diff --git a/src/test/java/openconsignment/common/UtilsTest.java b/src/test/java/openconsignment/common/UtilsTest.java index e88e207..2ebc28f 100644 --- a/src/test/java/openconsignment/common/UtilsTest.java +++ b/src/test/java/openconsignment/common/UtilsTest.java @@ -19,10 +19,38 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.MockedStatic; class UtilsTest { + @ParameterizedTest + @CsvSource({ + "0, 1", + "1, 2", + "123, 124", + "0000, 0001", + "0009, 0010", + "0099, 0100", + "0999, 1000", + "9, 10", + "9999, 10000", + "2147483646, 2147483647" + }) + void nextVoucherNumberIncrementsAndPreservesMinimumWidth(String lastVoucher, String expected) { + assertEquals(expected, Utils.nextVoucherNumber(lastVoucher)); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" ", " 123", "123 ", "abc", "V-001", "12.5", "1,000", "2147483648"}) + void nextVoucherNumberReturnsZeroForMissingOrInvalidInput(String lastVoucher) { + assertEquals("0", Utils.nextVoucherNumber(lastVoucher)); + } + @AfterEach void cleanUpGeneratedReport() throws IOException { Files.deleteIfExists(new File(Constants.REPORT_FILE_NAME).toPath());