From 59f09ed326bec4492957ec40768de38aeae79cc4 Mon Sep 17 00:00:00 2001 From: Rajdeep Roy Chowdhury Date: Mon, 26 Oct 2026 09:31:16 +0530 Subject: [PATCH 1/5] feat: payment entry UI revamped Signed-off-by: Rajdeep Roy Chowdhury --- .../entry/payment/AddPaymentController.java | 400 --------------- .../entry/payment/PaymentEntryController.java | 470 +++++++++++++++++- .../payment/ViewDeletePaymentController.java | 191 ------- .../openconsignment/di/ControllerModule.java | 12 - .../repository/CollectionRepository.java | 11 +- .../repository/PaymentRepository.java | 10 +- .../service/PaymentService.java | 4 +- .../fxml/entry/payment/AddPayment.fxml | 171 ------- .../fxml/entry/payment/PaymentEntry.fxml | 146 ++++-- .../entry/payment/ViewAndDeletePayment.fxml | 90 ---- src/main/resources/fxml/main.css | 151 +++++- 11 files changed, 742 insertions(+), 914 deletions(-) delete mode 100644 src/main/java/openconsignment/controller/entry/payment/AddPaymentController.java delete mode 100644 src/main/java/openconsignment/controller/entry/payment/ViewDeletePaymentController.java delete mode 100644 src/main/resources/fxml/entry/payment/AddPayment.fxml delete mode 100644 src/main/resources/fxml/entry/payment/ViewAndDeletePayment.fxml diff --git a/src/main/java/openconsignment/controller/entry/payment/AddPaymentController.java b/src/main/java/openconsignment/controller/entry/payment/AddPaymentController.java deleted file mode 100644 index 5b9a12b..0000000 --- a/src/main/java/openconsignment/controller/entry/payment/AddPaymentController.java +++ /dev/null @@ -1,400 +0,0 @@ -package openconsignment.controller.entry.payment; - -import static openconsignment.common.Utils.formatDate; - -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 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 javafx.scene.text.Text; -import javax.inject.Inject; -import openconsignment.common.Constants; -import openconsignment.common.Utils; -import openconsignment.entity.BillEntity; -import openconsignment.entity.PaymentEntity; -import openconsignment.entity.PaymentItemEntity; -import openconsignment.model.PaymentItem; -import openconsignment.service.BillService; -import openconsignment.service.PaymentService; -import openconsignment.service.SupplierService; - -public class AddPaymentController implements Initializable { - - private List paymentItems; - private List supplierNameComboList; - private List billNoComboList; - private int previouslyDue; - - private static final Logger logger = Logger.getLogger(AddPaymentController.class.getName()); - - @FXML - private TextField dd_no_field; - - @FXML - private DatePicker dd_date_field; - - @FXML - private DatePicker voucher_date_field; - - @FXML - private TextField voucher_no_field; - - @FXML - private Button add_payment_btn; - - @FXML - private Button replace_payment_btn; - - @FXML - private Button delete_payment_btn; - - @FXML - private TableColumn bill_no_col; - - @FXML - private TableColumn bill_amt_col; - - @FXML - private TableColumn dd_no_col; - - @FXML - private TableColumn dd_date_col; - - @FXML - private TableColumn bill_dt_col; - - @FXML - private TableColumn buyer_col; - - @FXML - private TableColumn due_col; - - @FXML - private TableColumn amount_paid_col; - - @FXML - private TableView payment_tableview; - - @FXML - private TextField amount_paid_field; - - @FXML - private TextField buyer_name_field; - - @FXML - private TextField bill_date_field; - - @FXML - private TextField bill_amount_field; - - @FXML - private ComboBox supplier_name_combo; - - @FXML - private ComboBox bill_no_combo; - - @FXML - private TextField due_amount_field; - - @FXML - private TableColumn bank_col; - - @FXML - private TextField bank_field; - - @FXML - private TextField total_amount_paid_field; - - @FXML - private Button save_btn; - - @FXML - private Button clear_btn; - - @FXML - private Text last_voucher_field; - - private final BillService billService; - private final PaymentService paymentService; - private final SupplierService supplierService; - - @Inject - public AddPaymentController( - BillService billService, PaymentService paymentService, SupplierService supplierService) { - this.billService = billService; - this.paymentService = paymentService; - this.supplierService = supplierService; - } - - @Override - public void initialize(URL url, ResourceBundle rb) { - paymentItems = new ArrayList<>(); - supplierNameComboList = new ArrayList<>(); - billNoComboList = new ArrayList<>(); - - bill_no_col.setCellValueFactory(new PropertyValueFactory<>("billNo")); - bill_amt_col.setCellValueFactory(new PropertyValueFactory<>("billAmount")); - bill_dt_col.setCellValueFactory(new PropertyValueFactory<>("billDate")); - buyer_col.setCellValueFactory(new PropertyValueFactory<>("buyerName")); - bank_col.setCellValueFactory(new PropertyValueFactory<>("bank")); - dd_no_col.setCellValueFactory(new PropertyValueFactory<>("ddNo")); - dd_date_col.setCellValueFactory(new PropertyValueFactory<>("ddDate")); - due_col.setCellValueFactory(new PropertyValueFactory<>("due")); - amount_paid_col.setCellValueFactory(new PropertyValueFactory<>("amountPaid")); - - refreshPaymentTableView(); - fillSupplierCombo(); - - updateLastVoucher(); - } - - private void refreshPaymentTableView() { - payment_tableview.setItems(FXCollections.observableArrayList(paymentItems)); - } - - @FXML - private void addPayment(ActionEvent ignoredEvent) { - if (bill_no_combo.getValue().isEmpty() - || bill_amount_field.getText().isEmpty() - || buyer_name_field.getText().isEmpty() - || supplier_name_combo.getValue().isEmpty() - || bank_field.getText().isEmpty() - || bank_field.getText().isEmpty() - || due_amount_field.getText().isEmpty() - || amount_paid_field.getText().isEmpty() - || dd_no_field.getText().isEmpty() - || dd_date_field.getValue().toString().isEmpty()) { - Utils.showAlert("Please check whether the fields are properly filled or not.", 2); - return; - } - - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); - - paymentItems.add(new PaymentItem( - bill_no_combo.getValue(), - bill_amount_field.getText(), - bill_date_field.getText(), - buyer_name_field.getText(), - due_amount_field.getText(), - amount_paid_field.getText(), - bank_field.getText(), - dd_no_field.getText(), - dateTimeFormatter.format(dd_date_field.getValue()))); - - refreshPaymentTableView(); - - clearRepeatingFields(); - updateTotalAmountPaid(); - } - - @FXML - private void replacePayment(ActionEvent ignoredEvent) { - - if (payment_tableview.getSelectionModel().getSelectedIndex() == -1) { - Utils.showAlert("Please select an item from the Payment Table", 2); - return; - } - - if (bill_no_combo.getValue().isEmpty() - || bill_amount_field.getText().isEmpty() - || buyer_name_field.getText().isEmpty() - || supplier_name_combo.getValue().isEmpty() - || bank_field.getText().isEmpty() - || bank_field.getText().isEmpty() - || due_amount_field.getText().isEmpty() - || amount_paid_field.getText().isEmpty() - || dd_no_field.getText().isEmpty() - || dd_date_field.getValue().toString().isEmpty()) { - Utils.showAlert("Please check whether the fields are properly filled or not.", 2); - return; - } - - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); - - paymentItems.set( - payment_tableview.getSelectionModel().getSelectedIndex(), - new PaymentItem( - bill_no_combo.getValue(), - bill_amount_field.getText(), - bill_date_field.getText(), - buyer_name_field.getText(), - due_amount_field.getText(), - amount_paid_field.getText(), - bank_field.getText(), - dd_no_field.getText(), - dateTimeFormatter.format(dd_date_field.getValue()))); - - refreshPaymentTableView(); - - clearRepeatingFields(); - updateTotalAmountPaid(); - } - - @FXML - private void deletePayment(ActionEvent ignoredEvent) { - if (payment_tableview.getSelectionModel().getSelectedIndex() == -1) { - Utils.showAlert("Please select an item from the Payment Table", 2); - return; - } - paymentItems.remove(payment_tableview.getSelectionModel().getSelectedIndex()); - refreshPaymentTableView(); - updateTotalAmountPaid(); - } - - private void clearRepeatingFields() { - bill_date_field.setText(""); - buyer_name_field.setText(""); - bill_amount_field.setText("0"); - due_amount_field.setText("0"); - amount_paid_field.setText("0"); - bank_field.setText(""); - dd_no_field.setText(""); - dd_date_field.setValue(null); - } - - private void updateTotalAmountPaid() { - int totalAmountPaid = 0; - for (PaymentItem paymentItem : paymentItems) { - totalAmountPaid += Integer.parseInt(paymentItem.getAmountPaid()); - } - total_amount_paid_field.setText(Integer.toString(totalAmountPaid)); - } - - @FXML - private void clearAllFields() { - clearRepeatingFields(); - voucher_no_field.setText(""); - voucher_date_field.setValue(null); - supplier_name_combo.setDisable(false); - supplier_name_combo.getSelectionModel().clearSelection(); - billNoComboList.clear(); - bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); - } - - @FXML - private void saveData(ActionEvent ignoredEvent) { - Alert alert = new Alert( - Alert.AlertType.CONFIRMATION, - "Are you sure that you want save " + voucher_no_field.getText() + " ?", - ButtonType.YES, - ButtonType.NO, - ButtonType.CANCEL); - alert.showAndWait(); - - if (alert.getResult() != ButtonType.YES) { - return; - } - - if (voucher_no_field.getText().isEmpty() - || voucher_date_field.getValue() == null - || voucher_date_field.getValue().toString().isEmpty()) { - Utils.showAlert("Check whether the Voucher No. and the Voucher Date is properly filled", 2); - return; - } - - List paymentItemEntities = paymentItems.stream() - .map(it -> PaymentItemEntity.builder() - .voucherNo(voucher_no_field.getText()) - .buyerName(it.getBuyerName()) - .billNo(it.getBillNo()) - .billDate(formatDate(it.getBillDate())) - .billAmount(it.getBillAmount()) - .dueAmount(it.getDue()) - .amountPaid(it.getAmountPaid()) - .bank(it.getBank()) - .ddNo(it.getDdNo()) - .ddDate(formatDate(it.getDdDate())) - .build()) - .toList(); - - PaymentEntity paymentEntity = PaymentEntity.builder() - .voucherNo(voucher_no_field.getText()) - .voucherDate(formatDate(voucher_date_field.getValue().toString())) - .supplierName(supplier_name_combo.getValue()) - .totalAmount(total_amount_paid_field.getText()) - .items(paymentItemEntities) - .build(); - - try { - paymentService.savePayment(paymentEntity); - Utils.showAlert("Saved Successfully", 1); - clearAllFields(); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - Logger.getLogger(PaymentEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - - clearAllFields(); - paymentItems.clear(); - refreshPaymentTableView(); - updateLastVoucher(); - } - - private void updateLastVoucher() { - try { - String answer = paymentService.getLastVoucher(); - last_voucher_field.setText("Last Voucher No. : " + answer); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, ex.toString(), ex); - } - } - - private void fillSupplierCombo() { - try { - supplierNameComboList = supplierService.getAllSuppliers(); - supplier_name_combo.setItems(FXCollections.observableArrayList(supplierNameComboList)); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, "Failed to populate the supplier combo", ex); - } - } - - @FXML - private void fillBillNoCombo(ActionEvent ignoredEvent) { - try { - billNoComboList = paymentService.fetchPendingBillNosForSupplier(supplier_name_combo.getValue()); - bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, "Failed to populate bill number combo", ex); - } - } - - @FXML - private void fetchData(ActionEvent ignoredEvent) { - if (null == bill_no_combo.getValue()) { - return; - } - try { - BillEntity billEntity = billService.getBill(bill_no_combo.getValue()); - buyer_name_field.setText(billEntity.getBuyerName()); - bill_date_field.setText(billEntity.getBillDate()); - bill_amount_field.setText(billEntity.getBillAmount()); - previouslyDue = paymentService.fetchPendingAmountForBillNo(bill_no_combo.getValue()); - updateDueAmount(); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void updateDueAmount() { - int amountPaid = 0; - try { - amountPaid = Integer.parseInt(amount_paid_field.getText()); - } catch (Exception ex) { - logger.log(Level.WARNING, "cannot be converted to integer"); - } - due_amount_field.setText(Integer.toString(previouslyDue - amountPaid)); - } -} diff --git a/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java b/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java index fe7f9f0..ed4e692 100644 --- a/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java +++ b/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java @@ -1,15 +1,479 @@ package openconsignment.controller.entry.payment; +import static openconsignment.common.Utils.formatDate; + +import java.io.IOException; import java.net.URL; -import java.util.ResourceBundle; +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; import javafx.fxml.Initializable; +import javafx.scene.control.*; +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; +import openconsignment.entity.PaymentEntity; +import openconsignment.entity.PaymentItemEntity; +import openconsignment.model.PaymentItem; +import openconsignment.service.BillService; +import openconsignment.service.PaymentService; +import openconsignment.service.SupplierService; +@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(PaymentEntryController.class.getName()); + + @FXML + private TextField dd_no_field; + + @FXML + private DatePicker dd_date_field; + + @FXML + private DatePicker voucher_date_field; + + @FXML + private TextField voucher_no_field; + + @FXML + private Button add_payment_btn; + + @FXML + private Button replace_payment_btn; + + @FXML + private Button delete_payment_btn; + + @FXML + private TableColumn bill_no_col; + + @FXML + private TableColumn bill_amt_col; + + @FXML + private TableColumn dd_no_col; + + @FXML + private TableColumn dd_date_col; + + @FXML + private TableColumn bill_dt_col; + + @FXML + private TableColumn buyer_col; + + @FXML + private TableColumn due_col; + + @FXML + private TableColumn amount_paid_col; + + @FXML + private TableView payment_tableview; + + @FXML + private TextField amount_paid_field; + + @FXML + private TextField buyer_name_field; + + @FXML + private TextField bill_date_field; + + @FXML + private TextField bill_amount_field; + + @FXML + private ComboBox supplier_name_combo; + + @FXML + private ComboBox bill_no_combo; + + @FXML + private TextField due_amount_field; + + @FXML + private TableColumn bank_col; + + @FXML + private TextField bank_field; + + @FXML + private TextField total_amount_paid_field; + + @FXML + private Button save_btn; + + @FXML + private Button clear_btn; + + @FXML + private Text last_voucher_field; + + private final BillService billService; + private final PaymentService paymentService; + private final SupplierService supplierService; + @Inject - public PaymentEntryController() {} + public PaymentEntryController( + BillService billService, PaymentService paymentService, SupplierService supplierService) { + this.billService = billService; + this.paymentService = paymentService; + this.supplierService = supplierService; + } @Override - public void initialize(URL location, ResourceBundle resources) {} + public void initialize(URL url, ResourceBundle rb) { + paymentItems = new ArrayList<>(); + supplierNameComboList = new ArrayList<>(); + billNoComboList = new ArrayList<>(); + + bill_no_col.setCellValueFactory(new PropertyValueFactory<>("billNo")); + bill_amt_col.setCellValueFactory(new PropertyValueFactory<>("billAmount")); + bill_dt_col.setCellValueFactory(new PropertyValueFactory<>("billDate")); + buyer_col.setCellValueFactory(new PropertyValueFactory<>("buyerName")); + bank_col.setCellValueFactory(new PropertyValueFactory<>("bank")); + dd_no_col.setCellValueFactory(new PropertyValueFactory<>("ddNo")); + dd_date_col.setCellValueFactory(new PropertyValueFactory<>("ddDate")); + due_col.setCellValueFactory(new PropertyValueFactory<>("due")); + amount_paid_col.setCellValueFactory(new PropertyValueFactory<>("amountPaid")); + + refreshPaymentTableView(); + fillSupplierCombo(); + + updateLastVoucher(); + } + + private void refreshPaymentTableView() { + payment_tableview.setItems(FXCollections.observableArrayList(paymentItems)); + } + + @FXML + private void addPayment(ActionEvent ignoredEvent) { + if (bill_no_combo.getValue().isEmpty() + || bill_amount_field.getText().isEmpty() + || buyer_name_field.getText().isEmpty() + || supplier_name_combo.getValue().isEmpty() + || bank_field.getText().isEmpty() + || bank_field.getText().isEmpty() + || due_amount_field.getText().isEmpty() + || amount_paid_field.getText().isEmpty() + || dd_no_field.getText().isEmpty() + || dd_date_field.getValue().toString().isEmpty()) { + Utils.showAlert("Please check whether the fields are properly filled or not.", 2); + return; + } + + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); + + paymentItems.add(new PaymentItem( + bill_no_combo.getValue(), + bill_amount_field.getText(), + bill_date_field.getText(), + buyer_name_field.getText(), + due_amount_field.getText(), + amount_paid_field.getText(), + bank_field.getText(), + dd_no_field.getText(), + dateTimeFormatter.format(dd_date_field.getValue()))); + + refreshPaymentTableView(); + + clearRepeatingFields(); + updateTotalAmountPaid(); + } + + @FXML + private void replacePayment(ActionEvent ignoredEvent) { + + if (payment_tableview.getSelectionModel().getSelectedIndex() == -1) { + Utils.showAlert("Please select an item from the Payment Table", 2); + return; + } + + if (bill_no_combo.getValue().isEmpty() + || bill_amount_field.getText().isEmpty() + || buyer_name_field.getText().isEmpty() + || supplier_name_combo.getValue().isEmpty() + || bank_field.getText().isEmpty() + || bank_field.getText().isEmpty() + || due_amount_field.getText().isEmpty() + || amount_paid_field.getText().isEmpty() + || dd_no_field.getText().isEmpty() + || dd_date_field.getValue().toString().isEmpty()) { + Utils.showAlert("Please check whether the fields are properly filled or not.", 2); + return; + } + + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); + + paymentItems.set( + payment_tableview.getSelectionModel().getSelectedIndex(), + new PaymentItem( + bill_no_combo.getValue(), + bill_amount_field.getText(), + bill_date_field.getText(), + buyer_name_field.getText(), + due_amount_field.getText(), + amount_paid_field.getText(), + bank_field.getText(), + dd_no_field.getText(), + dateTimeFormatter.format(dd_date_field.getValue()))); + + refreshPaymentTableView(); + + clearRepeatingFields(); + updateTotalAmountPaid(); + } + + @FXML + private void deletePayment(ActionEvent ignoredEvent) { + if (payment_tableview.getSelectionModel().getSelectedIndex() == -1) { + Utils.showAlert("Please select an item from the Payment Table", 2); + return; + } + paymentItems.remove(payment_tableview.getSelectionModel().getSelectedIndex()); + refreshPaymentTableView(); + updateTotalAmountPaid(); + } + + private void clearRepeatingFields() { + bill_date_field.setText(""); + buyer_name_field.setText(""); + bill_amount_field.setText("0"); + due_amount_field.setText("0"); + amount_paid_field.setText("0"); + bank_field.setText(""); + dd_no_field.setText(""); + dd_date_field.setValue(null); + } + + private void updateTotalAmountPaid() { + int totalAmountPaid = 0; + for (PaymentItem paymentItem : paymentItems) { + totalAmountPaid += Integer.parseInt(paymentItem.getAmountPaid()); + } + total_amount_paid_field.setText(Integer.toString(totalAmountPaid)); + } + + @FXML + private void clearAllFields() { + clearRepeatingFields(); + voucher_no_field.setText(""); + voucher_date_field.setValue(null); + supplier_name_combo.setDisable(false); + supplier_name_combo.getSelectionModel().clearSelection(); + billNoComboList.clear(); + bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); + total_amount_paid_field.setText(""); + } + + @FXML + private void saveData(ActionEvent ignoredEvent) { + Alert alert = new Alert( + Alert.AlertType.CONFIRMATION, + "Are you sure that you want save " + voucher_no_field.getText() + " ?", + ButtonType.YES, + ButtonType.NO, + ButtonType.CANCEL); + alert.showAndWait(); + + if (alert.getResult() != ButtonType.YES) { + return; + } + + if (voucher_no_field.getText().isEmpty() + || voucher_date_field.getValue() == null + || voucher_date_field.getValue().toString().isEmpty()) { + Utils.showAlert("Check whether the Voucher No. and the Voucher Date is properly filled", 2); + return; + } + + List paymentItemEntities = paymentItems.stream() + .map(it -> PaymentItemEntity.builder() + .voucherNo(voucher_no_field.getText()) + .buyerName(it.getBuyerName()) + .billNo(it.getBillNo()) + .billDate(formatDate(it.getBillDate())) + .billAmount(it.getBillAmount()) + .dueAmount(null == it.getDue() ? "0" : it.getDue()) + .amountPaid(it.getAmountPaid()) + .bank(it.getBank()) + .ddNo(it.getDdNo()) + .ddDate(formatDate(it.getDdDate())) + .build()) + .toList(); + + PaymentEntity paymentEntity = PaymentEntity.builder() + .voucherNo(voucher_no_field.getText()) + .voucherDate(formatDate(voucher_date_field.getValue().toString())) + .supplierName(supplier_name_combo.getValue()) + .totalAmount(total_amount_paid_field.getText()) + .items(paymentItemEntities) + .build(); + + try { + paymentService.savePayment(paymentEntity); + Utils.showAlert("Saved Successfully", 1); + clearAllFields(); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + Logger.getLogger(PaymentEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); + } + + clearAllFields(); + paymentItems.clear(); + refreshPaymentTableView(); + updateLastVoucher(); + } + + private void updateLastVoucher() { + try { + String answer = paymentService.getLastVoucher(); + last_voucher_field.setText("Last Voucher No. : " + answer); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, ex.toString(), ex); + } + } + + private void fillSupplierCombo() { + try { + supplierNameComboList = supplierService.getAllSuppliers(); + supplier_name_combo.setItems(FXCollections.observableArrayList(supplierNameComboList)); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, "Failed to populate the supplier combo", ex); + } + } + + @FXML + private void fillBillNoCombo(ActionEvent ignoredEvent) { + try { + billNoComboList = paymentService.fetchPendingBillNosForSupplier(supplier_name_combo.getValue()); + bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, "Failed to populate bill number combo", ex); + } + } + + @FXML + private void fetchBillNos(ActionEvent ignoredEvent) { + if (null == bill_no_combo.getValue()) { + return; + } + try { + BillEntity billEntity = billService.getBill(bill_no_combo.getValue()); + buyer_name_field.setText(billEntity.getBuyerName()); + bill_date_field.setText(billEntity.getBillDate()); + bill_amount_field.setText(billEntity.getBillAmount()); + previouslyDue = paymentService.fetchPendingAmountForBillNo(bill_no_combo.getValue()); + updateDueAmount(); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, ex.toString(), ex); + } + } + + @FXML + private void updateDueAmount() { + int amountPaid = 0; + try { + amountPaid = Integer.parseInt(amount_paid_field.getText()); + } catch (Exception ex) { + logger.log(Level.WARNING, "cannot be converted to integer"); + } + 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/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..488d6c5 100644 --- a/src/main/java/openconsignment/di/ControllerModule.java +++ b/src/main/java/openconsignment/di/ControllerModule.java @@ -14,9 +14,7 @@ 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.history.BillHistoryController; import openconsignment.controller.history.CollectionHistoryController; import openconsignment.controller.history.PaymentHistoryController; @@ -136,16 +134,6 @@ public interface ControllerModule { @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/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/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 index 3f7c93f..0b0f4de 100644 --- a/src/main/resources/fxml/entry/payment/PaymentEntry.fxml +++ b/src/main/resources/fxml/entry/payment/PaymentEntry.fxml @@ -1,37 +1,109 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/main.css b/src/main/resources/fxml/main.css index dd98a9d..ab5fe16 100644 --- a/src/main/resources/fxml/main.css +++ b/src/main/resources/fxml/main.css @@ -2,9 +2,128 @@ * Empty Stylesheet file. */ -.mainFxmlClass { - -} +.mainFxmlClass { + +} + +.payment-page { + -fx-background-color: #eef5fc; +} + +.payment-page-title { + -fx-fill: #17324f; + -fx-font-weight: bold; +} + +.payment-page-subtitle, +.payment-muted { + -fx-fill: #647f9b; + -fx-font-size: 12px; +} + +.payment-tabs .tab-header-area { + -fx-padding: 0 18 0 18; +} + +.payment-tabs .tab-header-background { + -fx-background-color: transparent; +} + +.payment-tabs .tab { + -fx-background-color: transparent; + -fx-background-radius: 0; + -fx-padding: 8 18 8 18; + -fx-font-weight: bold; +} + +.payment-tabs .tab:selected { + -fx-background-color: white; + -fx-border-color: transparent transparent #3b82c4 transparent; + -fx-border-width: 0 0 2 0; +} + +.payment-tabs .tab-content-area { + -fx-background-color: white; + -fx-border-color: #e2e8f0; + -fx-border-radius: 8; + -fx-background-radius: 8; +} + +.payment-entry { + -fx-background-color: #fbfdff; + -fx-background-radius: 8; +} + +.payment-section-title { + -fx-fill: #17324f; + -fx-font-size: 15px; + -fx-font-weight: bold; +} + +.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; +} JFXButton { -fx-background-color: linear-gradient(#7480f8, #00f6ff); @@ -31,7 +150,25 @@ JFXButton:hover { -fx-background-color: #b3e5fc; } -.tab-pane .tab:selected { - -fx-background-color: #4fc3f7; - -fx-text-fill: white; -} +.tab-pane .tab:selected { + -fx-background-color: #4fc3f7; + -fx-text-fill: white; +} + +/* Keep the payment workspace styling scoped without changing other screens. */ +.payment-tabs .tab-header-background { + -fx-background-color: transparent; +} + +.payment-tabs .tab { + -fx-background-color: transparent; + -fx-background-radius: 0; + -fx-padding: 8 18 8 18; +} + +.payment-tabs .tab:selected { + -fx-background-color: white; + -fx-border-color: transparent transparent #3b82c4 transparent; + -fx-border-width: 0 0 2 0; + -fx-text-fill: #17324f; +} From 26468f056afd8d6fd91a3b7202cfd4bffee91af7 Mon Sep 17 00:00:00 2001 From: Rajdeep Roy Chowdhury Date: Tue, 27 Oct 2026 11:06:28 +0530 Subject: [PATCH 2/5] feat: revamped collection entry Signed-off-by: Rajdeep Roy Chowdhury --- build.gradle.kts | 5 +- gradle.properties | 3 + .../collection/AddCollectionController.java | 359 -------------- .../collection/CollectionEntryController.java | 460 +++++++++++++++++- .../ViewDeleteCollectionController.java | 183 ------- .../openconsignment/di/ControllerModule.java | 12 - src/main/resources/css/collections.css | 149 ++++++ src/main/resources/css/main.css | 18 + src/main/resources/css/payments.css | 79 +++ src/main/resources/fxml/Main.fxml | 2 +- .../fxml/entry/collection/AddCollection.fxml | 174 ------- .../entry/collection/CollectionEntry.fxml | 112 +++-- .../collection/ViewAndDeleteCollection.fxml | 88 ---- .../fxml/entry/payment/PaymentEntry.fxml | 2 +- src/main/resources/fxml/main.css | 174 ------- .../java/functional/CollectionEntryTest.java | 126 +++++ 16 files changed, 914 insertions(+), 1032 deletions(-) create mode 100644 gradle.properties delete mode 100644 src/main/java/openconsignment/controller/entry/collection/AddCollectionController.java delete mode 100644 src/main/java/openconsignment/controller/entry/collection/ViewDeleteCollectionController.java create mode 100644 src/main/resources/css/collections.css create mode 100644 src/main/resources/css/main.css create mode 100644 src/main/resources/css/payments.css delete mode 100644 src/main/resources/fxml/entry/collection/AddCollection.fxml delete mode 100644 src/main/resources/fxml/entry/collection/ViewAndDeleteCollection.fxml delete mode 100644 src/main/resources/fxml/main.css create mode 100644 src/test/java/functional/CollectionEntryTest.java 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/controller/entry/collection/AddCollectionController.java b/src/main/java/openconsignment/controller/entry/collection/AddCollectionController.java deleted file mode 100644 index 885d583..0000000 --- a/src/main/java/openconsignment/controller/entry/collection/AddCollectionController.java +++ /dev/null @@ -1,359 +0,0 @@ -package openconsignment.controller.entry.collection; - -import static openconsignment.common.Utils.formatDate; - -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 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.Constants; -import openconsignment.common.Utils; -import openconsignment.entity.BillEntity; -import openconsignment.entity.CollectionEntity; -import openconsignment.entity.CollectionItemEntity; -import openconsignment.model.CollectionItem; -import openconsignment.service.BillService; -import openconsignment.service.BuyerService; -import openconsignment.service.CollectionService; - -public class AddCollectionController implements Initializable { - private List collectionItemList; - private List buyerNameComboList; - private List billNoComboList; - private int previouslyDue; - - private static final Logger logger = Logger.getLogger(AddCollectionController.class.getName()); - - @FXML - private TextField dd_no_field; - - @FXML - private DatePicker dd_date_field; - - @FXML - private TextField voucher_no_field; - - @FXML - private DatePicker voucher_date_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 TextField bill_date; - - @FXML - private TextField bill_amount; - - @FXML - private ComboBox buyer_name; - - @FXML - private TextField supplier_name; - - @FXML - private ComboBox bill_no_combo; - - @FXML - private TextField bank_field; - - @FXML - private TextField total_amount_field; - - @FXML - private TextField amount_collected_field; - - @FXML - private TextField collection_due_field; - - @FXML - private Label last_voucher_field; - - private final BillService billService; - private final BuyerService buyerService; - private final CollectionService collectionService; - - @Inject - public AddCollectionController( - BillService billService, BuyerService buyerService, CollectionService collectionService) { - this.billService = billService; - this.buyerService = buyerService; - this.collectionService = collectionService; - } - - @Override - public void initialize(URL url, ResourceBundle rb) { - collectionItemList = new ArrayList<>(); - buyerNameComboList = new ArrayList<>(); - billNoComboList = new ArrayList<>(); - bill_no_col.setCellValueFactory(new PropertyValueFactory<>("billNo")); - bill_amt_col.setCellValueFactory(new PropertyValueFactory<>("billAmount")); - supplier_col.setCellValueFactory(new PropertyValueFactory<>("supplierName")); - 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")); - refreshCollectionTableView(); - fillBuyerNameCombo(); - updateLastVoucher(); - } - - private void refreshCollectionTableView() { - collection_tableview.setItems(FXCollections.observableArrayList(collectionItemList)); - } - - private void fillBuyerNameCombo() { - try { - buyerNameComboList = buyerService.getAllBuyers(); - buyer_name.setItems(FXCollections.observableArrayList(buyerNameComboList)); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void fillBillNoCombo() { - try { - billNoComboList = collectionService.fetchPendingBillNosForBuyer(buyer_name.getValue()); - bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void fetchData() { - if (null == bill_no_combo.getValue()) { - return; - } - try { - BillEntity billEntity = billService.getBill(bill_no_combo.getValue()); - supplier_name.setText(billEntity.getSupplierName()); - bill_date.setText(billEntity.getBillDate()); - bill_amount.setText(billEntity.getBillAmount()); - previouslyDue = collectionService.fetchPendingAmountForBillNo(bill_no_combo.getValue()); - updateCollectionDue(); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void addCollection(ActionEvent ignoredEvent) { - if (bill_no_combo.getValue().isEmpty() - || bill_amount.getText().isEmpty() - || buyer_name.getValue().isEmpty() - || supplier_name.getText().isEmpty() - || bank_field.getText().isEmpty() - || collection_due_field.getText().isEmpty() - || amount_collected_field.getText().isEmpty() - || dd_no_field.getText().isEmpty() - || dd_date_field.getValue().toString().isEmpty()) { - Utils.showAlert("Please check whether the fields are properly filled or not.", 2); - return; - } - - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); - - collectionItemList.add(new CollectionItem( - bill_no_combo.getValue(), - bill_date.getText(), - bill_amount.getText(), - supplier_name.getText(), - collection_due_field.getText(), - amount_collected_field.getText(), - bank_field.getText(), - dd_no_field.getText(), - dateTimeFormatter.format(dd_date_field.getValue()))); - - refreshCollectionTableView(); - updateTotalAmount(); - clearRepeatingFields(); - } - - @FXML - private void updateCollectionDue() { - int amountCollected = 0; - try { - amountCollected = Integer.parseInt(amount_collected_field.getText()); - } catch (Exception ex) { - logger.log(Level.WARNING, "cannot be converted to integer"); - } - collection_due_field.setText(Integer.toString(previouslyDue - amountCollected)); - } - - private void updateTotalAmount() { - int totalAmount = 0; - for (CollectionItem collectionItem : collectionItemList) { - totalAmount += Integer.parseInt(collectionItem.getAmountCollected()); - } - total_amount_field.setText(Integer.toString(totalAmount)); - } - - private void clearRepeatingFields() { - bill_date.setText(""); - supplier_name.setText(""); - bill_amount.setText(""); - collection_due_field.setText("0"); - amount_collected_field.setText("0"); - bank_field.setText(""); - dd_no_field.setText(""); - - dd_date_field.setValue(null); - } - - @FXML - private void replaceCollection(ActionEvent ignoredEvent) { - if (collection_tableview.getSelectionModel().getSelectedIndex() == -1) { - Utils.showAlert("Please select an item from the Collection Table", 2); - return; - } - if (bill_no_combo.getValue().isEmpty() - || bill_amount.getText().isEmpty() - || buyer_name.getValue().isEmpty() - || supplier_name.getText().isEmpty() - || bank_field.getText().isEmpty() - || collection_due_field.getText().isEmpty() - || amount_collected_field.getText().isEmpty() - || dd_no_field.getText().isEmpty() - || dd_date_field.getValue().toString().isEmpty()) { - Utils.showAlert("Please check whether the fields are properly filled or not.", 2); - return; - } - collectionItemList.set( - collection_tableview.getSelectionModel().getSelectedIndex(), - CollectionItem.builder() - .billNo(bill_no_combo.getValue()) - .billDate(bill_date.getText()) - .billAmount(bill_amount.getText()) - .supplierName(supplier_name.getText()) - .due(collection_due_field.getText()) - .amountCollected(amount_collected_field.getText()) - .bank(bank_field.getText()) - .ddNo(dd_no_field.getText()) - .ddDate(dd_date_field.getValue().toString()) - .build()); - refreshCollectionTableView(); - updateTotalAmount(); - } - - @FXML - private void deleteCollection(ActionEvent ignoredEvent) { - if (collection_tableview.getSelectionModel().getSelectedIndex() == -1) { - Utils.showAlert("Please select an item from the LR Table", 2); - return; - } - collectionItemList.remove(collection_tableview.getSelectionModel().getSelectedIndex()); - refreshCollectionTableView(); - updateTotalAmount(); - } - - @FXML - private void clearAllFields() { - clearRepeatingFields(); - voucher_no_field.setText(""); - voucher_date_field.setValue(null); - buyer_name.setDisable(false); - buyer_name.getSelectionModel().clearSelection(); - billNoComboList.clear(); - bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); - } - - @FXML - private void saveData(ActionEvent ignoredEvent) { - Alert alert = new Alert( - Alert.AlertType.CONFIRMATION, - "Are you sure that you want save " + voucher_no_field.getText() + " ?", - ButtonType.YES, - ButtonType.NO, - ButtonType.CANCEL); - alert.showAndWait(); - - if (alert.getResult() != ButtonType.YES) { - return; - } - if (voucher_no_field.getText().isEmpty() - || voucher_date_field.getValue() == null - || voucher_date_field.getValue().toString().isEmpty()) { - Utils.showAlert("Check whether the Voucher No. and the Voucher Date is properly filled", 2); - return; - } - - List collectionItemEntities = collectionItemList.stream() - .map(it -> CollectionItemEntity.builder() - .billNo(it.getBillNo()) - .billDate(formatDate(it.getBillDate())) - .billAmount(it.getBillAmount()) - .supplierName(it.getSupplierName()) - .due(it.getDue()) - .amountCollected(it.getAmountCollected()) - .bank(it.getBank()) - .ddNo(it.getDdNo()) - .ddDate(it.getDdDate()) - .build()) - .toList(); - - CollectionEntity collectionEntity = CollectionEntity.builder() - .voucherNo(voucher_no_field.getText()) - .voucherDate(formatDate(voucher_date_field.getValue().toString())) - .buyerName(buyer_name.getValue()) - .totalAmount(total_amount_field.getText()) - .items(collectionItemEntities) - .build(); - try { - collectionService.saveCollection(collectionEntity); - Utils.showAlert("Saved Successfully", 1); - } catch (SQLException ex) { - Utils.showAlert(ex.toString()); - logger.log(Level.SEVERE, ex.toString(), ex); - } - - clearAllFields(); - collectionItemList.clear(); - refreshCollectionTableView(); - updateLastVoucher(); - } - - private void updateLastVoucher() { - try { - String answer = collectionService.getLastVoucher(); - last_voucher_field.setText("Last Voucher No. : " + answer); - } 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 index 271fe83..0c9aa2a 100644 --- a/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java +++ b/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java @@ -1,15 +1,471 @@ package openconsignment.controller.entry.collection; +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.ArrayList; +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.JRException; +import openconsignment.common.Constants; +import openconsignment.common.Utils; +import openconsignment.entity.BillEntity; +import openconsignment.entity.CollectionEntity; +import openconsignment.entity.CollectionItemEntity; +import openconsignment.model.CollectionItem; +import openconsignment.service.BillService; +import openconsignment.service.BuyerService; +import openconsignment.service.CollectionService; public class CollectionEntryController implements Initializable { + private List collectionItemList; + private List buyerNameComboList; + private List billNoComboList; + private int previouslyDue; + + private static final Logger logger = Logger.getLogger(CollectionEntryController.class.getName()); + + @FXML + private TextField dd_no_field; + + @FXML + private DatePicker dd_date_field; + + @FXML + private TextField voucher_no_field; + + @FXML + private DatePicker voucher_date_field; + + @FXML + private TableView collection_tableview; + + @FXML + private TableColumn bill_no_col; + + @FXML + private TableColumn bill_amt_col; + + @FXML + private TableColumn bill_date_col; + + @FXML + private TableColumn due_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 TextField bill_date; + + @FXML + private TextField bill_amount; + + @FXML + private ComboBox buyer_name; + + @FXML + private TextField supplier_name; + + @FXML + private ComboBox bill_no_combo; + + @FXML + private TextField bank_field; + + @FXML + private TextField total_amount_field; + + @FXML + private TextField amount_collected_field; + + @FXML + private TextField collection_due_field; + + @FXML + private Label last_voucher_field; + + private final BillService billService; + private final BuyerService buyerService; + private final CollectionService collectionService; + @Inject - public CollectionEntryController() {} + public CollectionEntryController( + BillService billService, BuyerService buyerService, CollectionService collectionService) { + this.billService = billService; + this.buyerService = buyerService; + this.collectionService = collectionService; + } @Override - public void initialize(URL location, ResourceBundle resources) {} + public void initialize(URL url, ResourceBundle rb) { + collectionItemList = new ArrayList<>(); + buyerNameComboList = new ArrayList<>(); + 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")); + bank_col.setCellValueFactory(new PropertyValueFactory<>("bank")); + dd_date_col.setCellValueFactory(new PropertyValueFactory<>("ddDate")); + refreshCollectionTableView(); + fillBuyerNameCombo(); + updateLastVoucher(); + } + + private void refreshCollectionTableView() { + collection_tableview.setItems(FXCollections.observableArrayList(collectionItemList)); + } + + private void fillBuyerNameCombo() { + try { + buyerNameComboList = buyerService.getAllBuyers(); + buyer_name.setItems(FXCollections.observableArrayList(buyerNameComboList)); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, ex.toString(), ex); + } + } + + @FXML + private void fillBillNoCombo() { + if (buyer_name.getValue() == null) { + return; + } + try { + billNoComboList = collectionService.fetchPendingBillNosForBuyer(buyer_name.getValue()); + bill_no_combo.setItems(FXCollections.observableArrayList(billNoComboList)); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, ex.toString(), ex); + } + } + + @FXML + private void fetchData() { + if (null == bill_no_combo.getValue()) { + return; + } + try { + BillEntity billEntity = billService.getBill(bill_no_combo.getValue()); + supplier_name.setText(billEntity.getSupplierName()); + bill_date.setText(billEntity.getBillDate()); + bill_amount.setText(billEntity.getBillAmount()); + previouslyDue = collectionService.fetchPendingAmountForBillNo(bill_no_combo.getValue()); + updateCollectionDue(); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, ex.toString(), ex); + } + } + + @FXML + private void addCollection(ActionEvent ignoredEvent) { + if (bill_no_combo.getValue().isEmpty() + || bill_amount.getText().isEmpty() + || buyer_name.getValue().isEmpty() + || supplier_name.getText().isEmpty() + || bank_field.getText().isEmpty() + || collection_due_field.getText().isEmpty() + || amount_collected_field.getText().isEmpty() + || dd_no_field.getText().isEmpty() + || dd_date_field.getValue().toString().isEmpty()) { + Utils.showAlert("Please check whether the fields are properly filled or not.", 2); + return; + } + + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Constants.DATE_TIME_FORMAT); + + collectionItemList.add(new CollectionItem( + bill_no_combo.getValue(), + bill_date.getText(), + bill_amount.getText(), + supplier_name.getText(), + collection_due_field.getText(), + amount_collected_field.getText(), + bank_field.getText(), + dd_no_field.getText(), + dateTimeFormatter.format(dd_date_field.getValue()))); + + refreshCollectionTableView(); + updateTotalAmount(); + clearRepeatingFields(); + } + + @FXML + private void updateCollectionDue() { + int amountCollected = 0; + try { + amountCollected = Integer.parseInt(amount_collected_field.getText()); + } catch (Exception ex) { + logger.log(Level.WARNING, "cannot be converted to integer"); + } + collection_due_field.setText(Integer.toString(previouslyDue - amountCollected)); + } + + private void updateTotalAmount() { + int totalAmount = 0; + for (CollectionItem collectionItem : collectionItemList) { + totalAmount += Integer.parseInt(collectionItem.getAmountCollected()); + } + total_amount_field.setText(Integer.toString(totalAmount)); + } + + private void clearRepeatingFields() { + bill_date.setText(""); + supplier_name.setText(""); + bill_amount.setText(""); + collection_due_field.setText("0"); + amount_collected_field.setText("0"); + bank_field.setText(""); + dd_no_field.setText(""); + + dd_date_field.setValue(null); + } + + @FXML + private void replaceCollection(ActionEvent ignoredEvent) { + if (collection_tableview.getSelectionModel().getSelectedIndex() == -1) { + Utils.showAlert("Please select an item from the Collection Table", 2); + return; + } + if (bill_no_combo.getValue().isEmpty() + || bill_amount.getText().isEmpty() + || buyer_name.getValue().isEmpty() + || supplier_name.getText().isEmpty() + || bank_field.getText().isEmpty() + || collection_due_field.getText().isEmpty() + || amount_collected_field.getText().isEmpty() + || dd_no_field.getText().isEmpty() + || dd_date_field.getValue().toString().isEmpty()) { + Utils.showAlert("Please check whether the fields are properly filled or not.", 2); + return; + } + collectionItemList.set( + collection_tableview.getSelectionModel().getSelectedIndex(), + CollectionItem.builder() + .billNo(bill_no_combo.getValue()) + .billDate(bill_date.getText()) + .billAmount(bill_amount.getText()) + .supplierName(supplier_name.getText()) + .due(collection_due_field.getText()) + .amountCollected(amount_collected_field.getText()) + .bank(bank_field.getText()) + .ddNo(dd_no_field.getText()) + .ddDate(dd_date_field.getValue().toString()) + .build()); + refreshCollectionTableView(); + updateTotalAmount(); + } + + @FXML + private void deleteCollection(ActionEvent ignoredEvent) { + if (collection_tableview.getSelectionModel().getSelectedIndex() == -1) { + Utils.showAlert("Please select an item from the LR Table", 2); + return; + } + collectionItemList.remove(collection_tableview.getSelectionModel().getSelectedIndex()); + refreshCollectionTableView(); + updateTotalAmount(); + } + + @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 = 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( + Alert.AlertType.CONFIRMATION, + "Are you sure that you want save " + voucher_no_field.getText() + " ?", + ButtonType.YES, + ButtonType.NO, + ButtonType.CANCEL); + alert.showAndWait(); + + if (alert.getResult() != ButtonType.YES) { + return; + } + if (voucher_no_field.getText().isEmpty() + || voucher_date_field.getValue() == null + || voucher_date_field.getValue().toString().isEmpty()) { + Utils.showAlert("Check whether the Voucher No. and the Voucher Date is properly filled", 2); + return; + } + + List collectionItemEntities = collectionItemList.stream() + .map(it -> CollectionItemEntity.builder() + .billNo(it.getBillNo()) + .billDate(formatDate(it.getBillDate())) + .billAmount(it.getBillAmount()) + .supplierName(it.getSupplierName()) + .due(it.getDue()) + .amountCollected(it.getAmountCollected()) + .bank(it.getBank()) + .ddNo(it.getDdNo()) + .ddDate(it.getDdDate()) + .build()) + .toList(); + + CollectionEntity collectionEntity = CollectionEntity.builder() + .voucherNo(voucher_no_field.getText()) + .voucherDate(formatDate(voucher_date_field.getValue().toString())) + .buyerName(buyer_name.getValue()) + .totalAmount(total_amount_field.getText()) + .items(collectionItemEntities) + .build(); + try { + collectionService.saveCollection(collectionEntity); + Utils.showAlert("Saved Successfully", 1); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, ex.toString(), ex); + return; + } + + clearAllFields(); + updateLastVoucher(); + } + + private void updateLastVoucher() { + try { + String answer = collectionService.getLastVoucher(); + last_voucher_field.setText("Last Voucher No. : " + answer); + } catch (SQLException ex) { + Utils.showAlert(ex.toString()); + logger.log(Level.SEVERE, ex.toString(), ex); + } + } } 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/di/ControllerModule.java b/src/main/java/openconsignment/di/ControllerModule.java index 488d6c5..93e825c 100644 --- a/src/main/java/openconsignment/di/ControllerModule.java +++ b/src/main/java/openconsignment/di/ControllerModule.java @@ -11,9 +11,7 @@ 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.PaymentEntryController; import openconsignment.controller.history.BillHistoryController; import openconsignment.controller.history.CollectionHistoryController; @@ -104,16 +102,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) 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/css/main.css b/src/main/resources/css/main.css new file mode 100644 index 0000000..73e31b0 --- /dev/null +++ b/src/main/resources/css/main.css @@ -0,0 +1,18 @@ + + +.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/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 index ebfa86f..639eee7 100644 --- a/src/main/resources/fxml/entry/collection/CollectionEntry.fxml +++ b/src/main/resources/fxml/entry/collection/CollectionEntry.fxml @@ -1,37 +1,75 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/entry/payment/PaymentEntry.fxml b/src/main/resources/fxml/entry/payment/PaymentEntry.fxml index 0b0f4de..c292a5f 100644 --- a/src/main/resources/fxml/entry/payment/PaymentEntry.fxml +++ b/src/main/resources/fxml/entry/payment/PaymentEntry.fxml @@ -12,7 +12,7 @@ - + diff --git a/src/main/resources/fxml/main.css b/src/main/resources/fxml/main.css deleted file mode 100644 index ab5fe16..0000000 --- a/src/main/resources/fxml/main.css +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Empty Stylesheet file. - */ - -.mainFxmlClass { - -} - -.payment-page { - -fx-background-color: #eef5fc; -} - -.payment-page-title { - -fx-fill: #17324f; - -fx-font-weight: bold; -} - -.payment-page-subtitle, -.payment-muted { - -fx-fill: #647f9b; - -fx-font-size: 12px; -} - -.payment-tabs .tab-header-area { - -fx-padding: 0 18 0 18; -} - -.payment-tabs .tab-header-background { - -fx-background-color: transparent; -} - -.payment-tabs .tab { - -fx-background-color: transparent; - -fx-background-radius: 0; - -fx-padding: 8 18 8 18; - -fx-font-weight: bold; -} - -.payment-tabs .tab:selected { - -fx-background-color: white; - -fx-border-color: transparent transparent #3b82c4 transparent; - -fx-border-width: 0 0 2 0; -} - -.payment-tabs .tab-content-area { - -fx-background-color: white; - -fx-border-color: #e2e8f0; - -fx-border-radius: 8; - -fx-background-radius: 8; -} - -.payment-entry { - -fx-background-color: #fbfdff; - -fx-background-radius: 8; -} - -.payment-section-title { - -fx-fill: #17324f; - -fx-font-size: 15px; - -fx-font-weight: bold; -} - -.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; -} - -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; -} - -/* Keep the payment workspace styling scoped without changing other screens. */ -.payment-tabs .tab-header-background { - -fx-background-color: transparent; -} - -.payment-tabs .tab { - -fx-background-color: transparent; - -fx-background-radius: 0; - -fx-padding: 8 18 8 18; -} - -.payment-tabs .tab:selected { - -fx-background-color: white; - -fx-border-color: transparent transparent #3b82c4 transparent; - -fx-border-width: 0 0 2 0; - -fx-text-fill: #17324f; -} diff --git a/src/test/java/functional/CollectionEntryTest.java b/src/test/java/functional/CollectionEntryTest.java new file mode 100644 index 0000000..5cbdddc --- /dev/null +++ b/src/test/java/functional/CollectionEntryTest.java @@ -0,0 +1,126 @@ +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.collection.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/collection/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("", 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"); + } +} From 7fab9951d062679aa8fd172ea48c42696797fe9b Mon Sep 17 00:00:00 2001 From: Rajdeep Roy Chowdhury Date: Tue, 27 Oct 2026 19:42:00 +0530 Subject: [PATCH 3/5] feat: revamped bill entry Signed-off-by: Rajdeep Roy Chowdhury --- .../entry/bill/AddBillController.java | 206 -------------- .../entry/bill/BillEntryController.java | 268 +++++++++++++++++- .../entry/bill/ViewDeleteBillController.java | 151 ---------- .../openconsignment/di/ControllerModule.java | 12 - .../repository/BillRepository.java | 2 + src/main/resources/css/bills.css | 141 +++++++++ .../resources/fxml/entry/bill/AddBill.fxml | 131 --------- .../resources/fxml/entry/bill/BillEntry.fxml | 132 +++++++-- .../fxml/entry/bill/ViewDeleteBill.fxml | 118 -------- .../fxml/entry/payment/PaymentEntry.fxml | 11 +- .../java/functional/AddBillLayoutTest.java | 70 +++++ 11 files changed, 592 insertions(+), 650 deletions(-) delete mode 100644 src/main/java/openconsignment/controller/entry/bill/AddBillController.java delete mode 100644 src/main/java/openconsignment/controller/entry/bill/ViewDeleteBillController.java create mode 100644 src/main/resources/css/bills.css delete mode 100644 src/main/resources/fxml/entry/bill/AddBill.fxml delete mode 100644 src/main/resources/fxml/entry/bill/ViewDeleteBill.fxml create mode 100644 src/test/java/functional/AddBillLayoutTest.java diff --git a/src/main/java/openconsignment/controller/entry/bill/AddBillController.java b/src/main/java/openconsignment/controller/entry/bill/AddBillController.java deleted file mode 100644 index 84336bd..0000000 --- a/src/main/java/openconsignment/controller/entry/bill/AddBillController.java +++ /dev/null @@ -1,206 +0,0 @@ -package openconsignment.controller.entry.bill; - -import static openconsignment.common.Utils.formatDate; - -import java.net.URL; -import java.util.List; -import java.util.ResourceBundle; -import java.util.logging.Level; -import java.util.logging.Logger; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -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.model.LR; -import openconsignment.service.*; -import org.controlsfx.control.textfield.TextFields; - -public class AddBillController implements Initializable { - - @FXML - private TextField supplier_field; - - @FXML - private TextField buyer_name_field; - - @FXML - private TextField bill_no_field; - - @FXML - private TextField transport_field; - - @FXML - private DatePicker date_field; - - @FXML - private DatePicker lr_date; - - @FXML - private TextField bill_amount_field; - - @FXML - private TextField pm_field; - - @FXML - private TextField lr_field; - - @FXML - private Button save_btn; - - @FXML - private Button clear_btn; - - @FXML - private Button add_btn; - - @FXML - private TableView lrTable; - - @FXML - private TableColumn lr_no_col; - - @FXML - private TableColumn pm_col; - - @FXML - private Button replace_btn; - - @FXML - private Button delete_btn; - - final ObservableList lrList = FXCollections.observableArrayList(); - - private final BillService billService; - private final SupplierService supplierService; - private final BuyerService buyerService; - private final TransportService transportService; - - @Inject - public AddBillController( - BillService billService, - SupplierService supplierService, - BuyerService buyerService, - TransportService transportService) { - this.billService = billService; - this.supplierService = supplierService; - this.buyerService = buyerService; - this.transportService = transportService; - } - - @Override - public void initialize(URL url, ResourceBundle rb) { - try { - TextFields.bindAutoCompletion(supplier_field, supplierService.getAllSuppliers()); - TextFields.bindAutoCompletion(buyer_name_field, buyerService.getAllBuyers()); - TextFields.bindAutoCompletion(transport_field, transportService.getAllTransports()); - - lr_no_col.setCellValueFactory(new PropertyValueFactory<>("lrNo")); - pm_col.setCellValueFactory(new PropertyValueFactory<>("pm")); - - } catch (Exception ex) { - Utils.showAlert(ex.toString()); - Logger.getLogger(BillEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void addToList(ActionEvent event) { - if (bill_no_field.getText().isEmpty() - || lr_field.getText().isEmpty() - || pm_field.getText().isEmpty()) { - Utils.showAlert("Please ensure that the Bill No., LR No. and PM fields are filled up properly.", 2); - return; - } - lrList.add(new LR(bill_no_field.getText(), lr_field.getText(), pm_field.getText())); - lrTable.setItems(lrList); - lr_field.clear(); - pm_field.clear(); - } - - @FXML - private void save(ActionEvent event) { - if (supplier_field.getText().isEmpty() - || buyer_name_field.getText().isEmpty() - || bill_no_field.getText().isEmpty() - || date_field.getValue().toString().isEmpty() - || transport_field.getText().isEmpty() - || lr_date.getValue().toString().isEmpty() - || bill_amount_field.getText().isEmpty() - || lrList.isEmpty()) { - Utils.showAlert("Please ensure to fill up all the fields"); - return; - } - - List lrEntityList = lrList.stream() - .map(it -> new LREntity(it.getBillNo(), it.getLrNo(), it.getPm())) - .toList(); - - BillEntity billEntity = BillEntity.builder() - .supplierName(supplier_field.getText()) - .buyerName(buyer_name_field.getText()) - .billNo(bill_no_field.getText()) - .billDate(formatDate(date_field.getValue().toString())) - .transport(transport_field.getText()) - .lrDate(formatDate(lr_date.getValue().toString())) - .billAmount(bill_amount_field.getText()) - .lrEntities(lrEntityList) - .build(); - try { - billService.saveBill(billEntity); - clearAllFields(); - Utils.showAlert("Saved Successfully", 1); - } catch (Exception ex) { - Utils.showAlert(ex.toString()); - Logger.getLogger(BillEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); - } - } - - @FXML - private void clearAllFields() { - supplier_field.clear(); - buyer_name_field.clear(); - bill_no_field.clear(); - transport_field.clear(); - date_field.setValue(null); - lr_date.setValue(null); - bill_amount_field.clear(); - pm_field.clear(); - lr_field.clear(); - lrList.clear(); - } - - @FXML - private void replaceInList(ActionEvent event) { - if (lrTable.getSelectionModel().getSelectedIndex() == -1) { - Utils.showAlert("Please select an item from the LR Table", 2); - return; - } - if (bill_no_field.getText().isEmpty() - || lr_field.getText().isEmpty() - || pm_field.getText().isEmpty()) { - Utils.showAlert("Please ensure that the Bill No., LR No. and PM fields are filled up properly.", 2); - return; - } - lrList.set( - lrTable.getSelectionModel().getSelectedIndex(), - new LR(bill_no_field.getText(), lr_field.getText(), pm_field.getText())); - lr_field.clear(); - pm_field.clear(); - } - - @FXML - private void deleteFromList(ActionEvent event) { - if (lrTable.getSelectionModel().getSelectedIndex() == -1) { - Utils.showAlert("Please select an item from the LR Table", 2); - return; - } - lrList.remove(lrTable.getSelectionModel().getSelectedIndex()); - } -} diff --git a/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java b/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java index 6d93efa..0f517eb 100644 --- a/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java +++ b/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java @@ -1,15 +1,279 @@ package openconsignment.controller.entry.bill; +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.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.model.LR; +import openconsignment.service.BillService; +import openconsignment.service.BuyerService; +import openconsignment.service.SupplierService; +import openconsignment.service.TransportService; +import org.controlsfx.control.textfield.TextFields; +@SuppressWarnings("unused") public class BillEntryController implements Initializable { + @FXML + private TextField supplier_field; + + @FXML + private TextField buyer_name_field; + + @FXML + private TextField bill_no_field; + + @FXML + private TextField transport_field; + + @FXML + private DatePicker date_field; + + @FXML + private DatePicker lr_date; + + @FXML + private TextField bill_amount_field; + + @FXML + private TextField pm_field; + + @FXML + private TextField lr_field; + + @FXML + private Button save_btn; + + @FXML + private Button clear_btn; + + @FXML + private Button add_btn; + + @FXML + private TableView lrTable; + + @FXML + private TableColumn lr_no_col; + + @FXML + private TableColumn pm_col; + + @FXML + private Button replace_btn; + + @FXML + private Button delete_btn; + + private List lrList; + + private final BillService billService; + private final SupplierService supplierService; + private final BuyerService buyerService; + private final TransportService transportService; + @Inject - public BillEntryController() {} + public BillEntryController( + BillService billService, + SupplierService supplierService, + BuyerService buyerService, + TransportService transportService) { + this.billService = billService; + this.supplierService = supplierService; + this.buyerService = buyerService; + this.transportService = transportService; + } @Override - public void initialize(URL location, ResourceBundle resources) {} + public void initialize(URL url, ResourceBundle rb) { + try { + TextFields.bindAutoCompletion(supplier_field, supplierService.getAllSuppliers()); + TextFields.bindAutoCompletion(buyer_name_field, buyerService.getAllBuyers()); + TextFields.bindAutoCompletion(transport_field, transportService.getAllTransports()); + + 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); + } + } + + @FXML + private void addToList(ActionEvent event) { + if (bill_no_field.getText().isEmpty() + || lr_field.getText().isEmpty() + || pm_field.getText().isEmpty()) { + Utils.showAlert("Please ensure that the Bill No., LR No. and PM fields are filled up properly.", 2); + return; + } + lrList.add(new LR(bill_no_field.getText(), lr_field.getText(), pm_field.getText())); + lrTable.setItems(FXCollections.observableList(lrList)); + lr_field.clear(); + pm_field.clear(); + } + + @FXML + private void save(ActionEvent event) { + if (supplier_field.getText().isEmpty() + || buyer_name_field.getText().isEmpty() + || bill_no_field.getText().isEmpty() + || date_field.getValue().toString().isEmpty() + || transport_field.getText().isEmpty() + || lr_date.getValue().toString().isEmpty() + || bill_amount_field.getText().isEmpty() + || lrList.isEmpty()) { + Utils.showAlert("Please ensure to fill up all the fields"); + return; + } + + List lrEntityList = lrList.stream() + .map(it -> new LREntity(it.getBillNo(), it.getLrNo(), it.getPm())) + .toList(); + + BillEntity billEntity = BillEntity.builder() + .supplierName(supplier_field.getText()) + .buyerName(buyer_name_field.getText()) + .billNo(bill_no_field.getText()) + .billDate(formatDate(date_field.getValue().toString())) + .transport(transport_field.getText()) + .lrDate(formatDate(lr_date.getValue().toString())) + .billAmount(bill_amount_field.getText()) + .lrEntities(lrEntityList) + .build(); + try { + billService.saveBill(billEntity); + clearAllFields(); + Utils.showAlert("Saved Successfully", 1); + } catch (Exception ex) { + Utils.showAlert(ex.toString()); + Logger.getLogger(BillEntryController.class.getName()).log(Level.SEVERE, ex.toString(), ex); + } + } + + @FXML + private void clearAllFields() { + supplier_field.clear(); + buyer_name_field.clear(); + bill_no_field.clear(); + transport_field.clear(); + date_field.setValue(null); + lr_date.setValue(null); + bill_amount_field.clear(); + pm_field.clear(); + lr_field.clear(); + lrList.clear(); + lrTable.setItems(FXCollections.observableList(lrList)); + } + + @FXML + private void replaceInList(ActionEvent event) { + if (lrTable.getSelectionModel().getSelectedIndex() == -1) { + Utils.showAlert("Please select an item from the LR Table", 2); + return; + } + if (bill_no_field.getText().isEmpty() + || lr_field.getText().isEmpty() + || pm_field.getText().isEmpty()) { + Utils.showAlert("Please ensure that the Bill No., LR No. and PM fields are filled up properly.", 2); + return; + } + lrList.set( + lrTable.getSelectionModel().getSelectedIndex(), + new LR(bill_no_field.getText(), lr_field.getText(), pm_field.getText())); + lr_field.clear(); + pm_field.clear(); + lrTable.setItems(FXCollections.observableList(lrList)); + } + + @FXML + private void deleteFromList(ActionEvent event) { + if (lrTable.getSelectionModel().getSelectedIndex() == -1) { + Utils.showAlert("Please select an item from the LR Table", 2); + 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/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/di/ControllerModule.java b/src/main/java/openconsignment/di/ControllerModule.java index 93e825c..5a445d3 100644 --- a/src/main/java/openconsignment/di/ControllerModule.java +++ b/src/main/java/openconsignment/di/ControllerModule.java @@ -8,9 +8,7 @@ import openconsignment.controller.MainController; import openconsignment.controller.WelcomeScreenController; 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.CollectionEntryController; import openconsignment.controller.entry.payment.PaymentEntryController; import openconsignment.controller.history.BillHistoryController; @@ -112,16 +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(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/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/fxml/entry/bill/AddBill.fxml b/src/main/resources/fxml/entry/bill/AddBill.fxml deleted file mode 100644 index fc15173..0000000 --- a/src/main/resources/fxml/entry/bill/AddBill.fxml +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/entry/payment/PaymentEntry.fxml b/src/main/resources/fxml/entry/payment/PaymentEntry.fxml index c292a5f..3847888 100644 --- a/src/main/resources/fxml/entry/payment/PaymentEntry.fxml +++ b/src/main/resources/fxml/entry/payment/PaymentEntry.fxml @@ -32,11 +32,7 @@ - - - - - + @@ -45,6 +41,11 @@ + + + + + diff --git a/src/test/java/functional/AddBillLayoutTest.java b/src/test/java/functional/AddBillLayoutTest.java new file mode 100644 index 0000000..5ed5515 --- /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.bill.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/bill/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())); + } +} From 6af51bedfc03ab29bb7218fd7a54570ffe12cb89 Mon Sep 17 00:00:00 2001 From: Rajdeep Roy Chowdhury Date: Wed, 28 Oct 2026 22:39:07 +0530 Subject: [PATCH 4/5] feat: implemented autogenerated voucher numbers Signed-off-by: Rajdeep Roy Chowdhury --- .../java/openconsignment/common/Utils.java | 14 ++++++++++ .../collection/CollectionEntryController.java | 6 ++-- .../entry/payment/PaymentEntryController.java | 6 ++-- .../java/functional/CollectionEntryTest.java | 3 +- .../openconsignment/common/UtilsTest.java | 28 +++++++++++++++++++ 5 files changed, 52 insertions(+), 5 deletions(-) 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/collection/CollectionEntryController.java b/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java index 0c9aa2a..50c3975 100644 --- a/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java +++ b/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java @@ -461,8 +461,10 @@ private void saveData(ActionEvent ignoredEvent) { 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/payment/PaymentEntryController.java b/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java index ed4e692..70a3ae9 100644 --- a/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java +++ b/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java @@ -345,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); diff --git a/src/test/java/functional/CollectionEntryTest.java b/src/test/java/functional/CollectionEntryTest.java index 5cbdddc..21bd266 100644 --- a/src/test/java/functional/CollectionEntryTest.java +++ b/src/test/java/functional/CollectionEntryTest.java @@ -112,7 +112,8 @@ void deleteRequiresConfirmationAndClearsVoucherAfterSuccess() throws Exception { interact(() -> { assertTrue( lookup("#collection_tableview").queryTableView().getItems().isEmpty()); - assertEquals("", lookup("#voucher_no_field").queryTextInputControl().getText()); + assertEquals( + "0", lookup("#voucher_no_field").queryTextInputControl().getText()); }); } 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()); From 6d9ecfc6b6b5b8936b52a06d38f6f4b5724da55c Mon Sep 17 00:00:00 2001 From: Rajdeep Roy Chowdhury Date: Thu, 29 Oct 2026 12:00:38 +0530 Subject: [PATCH 5/5] fix: layout fixes Signed-off-by: Rajdeep Roy Chowdhury --- README.md | 104 ++++++++++++++++-- .../entry/{bill => }/BillEntryController.java | 2 +- .../CollectionEntryController.java | 2 +- .../controller/entry/EntryController.java | 9 +- .../{payment => }/PaymentEntryController.java | 2 +- .../openconsignment/di/ControllerModule.java | 6 +- .../fxml/entry/{bill => }/BillEntry.fxml | 4 +- .../{collection => }/CollectionEntry.fxml | 18 +-- src/main/resources/fxml/entry/Input.fxml | 4 +- .../entry/{payment => }/PaymentEntry.fxml | 3 +- .../java/functional/AddBillLayoutTest.java | 4 +- .../java/functional/CollectionEntryTest.java | 4 +- 12 files changed, 124 insertions(+), 38 deletions(-) rename src/main/java/openconsignment/controller/entry/{bill => }/BillEntryController.java (99%) rename src/main/java/openconsignment/controller/entry/{collection => }/CollectionEntryController.java (99%) rename src/main/java/openconsignment/controller/entry/{payment => }/PaymentEntryController.java (99%) rename src/main/resources/fxml/entry/{bill => }/BillEntry.fxml (96%) rename src/main/resources/fxml/entry/{collection => }/CollectionEntry.fxml (98%) rename src/main/resources/fxml/entry/{payment => }/PaymentEntry.fxml (99%) 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/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java b/src/main/java/openconsignment/controller/entry/BillEntryController.java similarity index 99% rename from src/main/java/openconsignment/controller/entry/bill/BillEntryController.java rename to src/main/java/openconsignment/controller/entry/BillEntryController.java index 0f517eb..656a386 100644 --- a/src/main/java/openconsignment/controller/entry/bill/BillEntryController.java +++ b/src/main/java/openconsignment/controller/entry/BillEntryController.java @@ -1,4 +1,4 @@ -package openconsignment.controller.entry.bill; +package openconsignment.controller.entry; import static openconsignment.common.Utils.formatDate; diff --git a/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java b/src/main/java/openconsignment/controller/entry/CollectionEntryController.java similarity index 99% rename from src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java rename to src/main/java/openconsignment/controller/entry/CollectionEntryController.java index 50c3975..89f6eed 100644 --- a/src/main/java/openconsignment/controller/entry/collection/CollectionEntryController.java +++ b/src/main/java/openconsignment/controller/entry/CollectionEntryController.java @@ -1,4 +1,4 @@ -package openconsignment.controller.entry.collection; +package openconsignment.controller.entry; import static openconsignment.common.Utils.formatDate; 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/PaymentEntryController.java b/src/main/java/openconsignment/controller/entry/PaymentEntryController.java similarity index 99% rename from src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java rename to src/main/java/openconsignment/controller/entry/PaymentEntryController.java index 70a3ae9..5101e33 100644 --- a/src/main/java/openconsignment/controller/entry/payment/PaymentEntryController.java +++ b/src/main/java/openconsignment/controller/entry/PaymentEntryController.java @@ -1,4 +1,4 @@ -package openconsignment.controller.entry.payment; +package openconsignment.controller.entry; import static openconsignment.common.Utils.formatDate; diff --git a/src/main/java/openconsignment/di/ControllerModule.java b/src/main/java/openconsignment/di/ControllerModule.java index 5a445d3..bd963e1 100644 --- a/src/main/java/openconsignment/di/ControllerModule.java +++ b/src/main/java/openconsignment/di/ControllerModule.java @@ -7,10 +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.BillEntryController; -import openconsignment.controller.entry.collection.CollectionEntryController; -import openconsignment.controller.entry.payment.PaymentEntryController; +import openconsignment.controller.entry.PaymentEntryController; import openconsignment.controller.history.BillHistoryController; import openconsignment.controller.history.CollectionHistoryController; import openconsignment.controller.history.PaymentHistoryController; diff --git a/src/main/resources/fxml/entry/bill/BillEntry.fxml b/src/main/resources/fxml/entry/BillEntry.fxml similarity index 96% rename from src/main/resources/fxml/entry/bill/BillEntry.fxml rename to src/main/resources/fxml/entry/BillEntry.fxml index c4c2720..4284919 100644 --- a/src/main/resources/fxml/entry/bill/BillEntry.fxml +++ b/src/main/resources/fxml/entry/BillEntry.fxml @@ -6,7 +6,7 @@ + fx:controller="openconsignment.controller.entry.BillEntryController" stylesheets="@/css/bills.css"> @@ -83,9 +83,9 @@ + -