From 50a0325341954fb917651b832a303356ee51477e Mon Sep 17 00:00:00 2001 From: filippo-ferrari Date: Sun, 8 Sep 2024 16:10:50 +0200 Subject: [PATCH] fix: major changes --- .../com/application/munera/data/Expense.java | 6 +- .../com/application/munera/data/Person.java | 9 +- .../repositories/ExpenseRepository.java | 16 +- .../munera/services/ExpenseService.java | 180 +++++++++++------- .../munera/services/PersonService.java | 104 +++++++--- .../munera/views/expenses/ExpensesView.java | 33 ++-- .../munera/views/people/PeopleView.java | 12 +- 7 files changed, 224 insertions(+), 136 deletions(-) diff --git a/src/main/java/com/application/munera/data/Expense.java b/src/main/java/com/application/munera/data/Expense.java index 7c651e2..be44a20 100644 --- a/src/main/java/com/application/munera/data/Expense.java +++ b/src/main/java/com/application/munera/data/Expense.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; @Getter @Setter @Table(name = "expenses") -public class Expense extends AbstractEntity { +public class Expense { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -46,11 +46,11 @@ public class Expense extends AbstractEntity { @ManyToOne @JoinColumn(name = "CreditorId") - private Person creditor; + private Person payer; @ManyToOne @JoinColumn(name = "DebtorId") - private Person debtor; + private Person beneficiary; @ManyToOne @JoinColumn(name = "EventId") diff --git a/src/main/java/com/application/munera/data/Person.java b/src/main/java/com/application/munera/data/Person.java index ccfcec7..0638e4d 100644 --- a/src/main/java/com/application/munera/data/Person.java +++ b/src/main/java/com/application/munera/data/Person.java @@ -40,11 +40,12 @@ public class Person extends AbstractEntity { @Column(name = "credit") private BigDecimal credit; - @OneToMany(mappedBy = "creditor") - private Set creditExpenses; + // Updated to match the new field names in Expense + @OneToMany(mappedBy = "payer") + private Set expensesAsPayer; - @OneToMany(mappedBy = "debtor") - private Set debtExpenses; + @OneToMany(mappedBy = "beneficiary") + private Set expensesAsBeneficiary; @ManyToMany(mappedBy = "participants") private Set events; diff --git a/src/main/java/com/application/munera/repositories/ExpenseRepository.java b/src/main/java/com/application/munera/repositories/ExpenseRepository.java index af4c4eb..5da7e8d 100644 --- a/src/main/java/com/application/munera/repositories/ExpenseRepository.java +++ b/src/main/java/com/application/munera/repositories/ExpenseRepository.java @@ -14,24 +14,24 @@ import java.util.Set; public interface ExpenseRepository extends JpaRepository, JpaSpecificationExecutor { // Find expenses where the creditor is a specific person - @Query("SELECT e FROM Expense e WHERE e.creditor.id = :personId") - Set findCreditorsExpensesByPersonId(@Param("personId") Long personId); + @Query("SELECT e FROM Expense e WHERE e.payer.id = :personId") + Set findExpensesByPayer(@Param("personId") Long personId); // Find expenses where the debtor is a specific person - @Query("SELECT e FROM Expense e WHERE e.debtor.id = :personId") - Set findDebtorsExpensesByPersonId(@Param("personId") Long personId); + @Query("SELECT e FROM Expense e WHERE e.beneficiary.id = :personId") + Set findExpensesByBeneficiary(@Param("personId") Long personId); // Find all expenses for a given year @Query("SELECT e FROM Expense e WHERE YEAR(e.date) = :year") List findAllByYear(@Param("year") int year); // Find unpaid expenses where the creditor is a specific person - @Query("SELECT e FROM Expense e WHERE e.creditor.id = :personId AND e.isPaid = false") - Set findUnpaidCreditorsExpensesByPersonId(@Param("personId") Long personId); + @Query("SELECT e FROM Expense e WHERE e.payer.id = :personId AND e.isPaid = false") + Set findUnpaidExpensesByPayer(@Param("personId") Long personId); // Find unpaid expenses where the debtor is a specific person - @Query("SELECT e FROM Expense e WHERE e.debtor.id = :personId AND e.isPaid = false") - Set findUnpaidDebtorsExpensesByPersonId(@Param("personId") Long personId); + @Query("SELECT e FROM Expense e WHERE e.beneficiary.id = :personId AND e.isPaid = false") + Set findUnapidExpensesByBeneficiary(@Param("personId") Long personId); // Find expenses for a given year and filter by expense type and paid status @Query("SELECT e FROM Expense e WHERE YEAR(e.date) = :year AND NOT (e.expenseType = :expenseType AND e.isPaid = true)") diff --git a/src/main/java/com/application/munera/services/ExpenseService.java b/src/main/java/com/application/munera/services/ExpenseService.java index 2b7f93d..9a49ad1 100644 --- a/src/main/java/com/application/munera/services/ExpenseService.java +++ b/src/main/java/com/application/munera/services/ExpenseService.java @@ -26,99 +26,102 @@ public class ExpenseService { this.expenseRepository = expenseRepository; } + /** + * Retrieves an expense by its ID. + * @param id the ID of the expense + * @return an Optional containing the expense if found, otherwise empty + */ public Optional get(Long id) { return expenseRepository.findById(id); } /** - * finds all expenses tagged as debit given a user + * Finds all expenses where the specified person is the beneficiary. * @param person the user of the expenses - * @return the collections of expenses found + * @return the collection of expenses found */ - public Collection findDebtByUser(final Person person) { - return expenseRepository.findDebtorsExpensesByPersonId(person.getId()); + public Collection findExpensesWhereBeneficiary(final Person person) { + return expenseRepository.findExpensesByBeneficiary(person.getId()); } /** - * finds all expenses tagged as credit given a user + * Finds all expenses where the specified person is the payer. * @param person the user of the expenses - * @return the collections of expenses found + * @return the collection of expenses found */ - public Collection findCreditByUser(final Person person) { - return expenseRepository.findCreditorsExpensesByPersonId(person.getId()); + public Collection findExpensesWherePayer(final Person person) { + return expenseRepository.findExpensesByPayer(person.getId()); } /** - * finds all expenses tagged as debit and unpaid given a user + * Finds all expenses where the specified person is the beneficiary and the expense is unpaid. * @param person the user of the expenses - * @return the collections of expenses found + * @return the collection of unpaid expenses found */ - public Collection findUnpaidDebtByUser(final Person person) { - return expenseRepository.findUnpaidDebtorsExpensesByPersonId(person.getId()); + public Collection findUnpaidExpensesWhereBeneficiary(final Person person) { + return expenseRepository.findUnapidExpensesByBeneficiary(person.getId()); } /** - * finds all expenses tagged as credit and unpaid given a user + * Finds all expenses where the specified person is the payer and the expense is unpaid. * @param person the user of the expenses - * @return the collections of expenses found + * @return the collection of unpaid expenses found */ - public Collection findUnpaidCreditByUser(final Person person) { - return expenseRepository.findUnpaidCreditorsExpensesByPersonId(person.getId()); + public Collection findUnpaidExpensesWherePayer(final Person person) { + return expenseRepository.findUnpaidExpensesByPayer(person.getId()); } /** - * finds all expenses related to a user + * Finds all expenses related to a user, both where the user is a payer and a beneficiary. * @param person the user of the expenses - * @return the collections of expenses found + * @return the list of expenses found */ - public List findExpenseByUser(final Person person) { - final var credits = this.findCreditByUser(person); - final var debits = this.findDebtByUser(person); - return Stream.concat(credits.stream(), debits.stream()).toList(); + public List findExpensesByUser(final Person person) { + // Retrieve expenses where the person is the payer + final var payerExpenses = this.findExpensesWherePayer(person); + // Retrieve expenses where the person is the beneficiary + final var beneficiaryExpenses = this.findExpensesWhereBeneficiary(person); + // Combine both sets of expenses into a single list without duplicates + return Stream.concat(payerExpenses.stream(), beneficiaryExpenses.stream()) + .distinct() + .toList(); } - - public List findAll() {return expenseRepository.findAll();} - /** - * updates an expense - * @param entity the expense to update + * Retrieves all expenses. + * @return the list of all expenses */ - public void update(Expense entity) { - if (Boolean.TRUE.equals(entity.getIsPaid())) entity.setPaymentDate(LocalDateTime.now()); - this.setExpenseType(entity); - expenseRepository.save(entity); + public List findAll() { + return expenseRepository.findAll(); } /** - * deletes an expense given the ID - * @param id the id of the expense to delete + * Finds all expenses for a given year. + * @param year the year for which to find expenses + * @return the list of expenses found */ - public void delete(Long id) { - expenseRepository.deleteById(id); - } - - public Page list(Pageable pageable) { - return expenseRepository.findAll(pageable); - } - - public Page list(Pageable pageable, Specification filter) { - return expenseRepository.findAll(filter, pageable); - } - - public int count() { - return (int) expenseRepository.count(); - } - - public List findAllByYear(final int year ) { + public List findAllByYear(final int year) { return this.expenseRepository.findAllByYear(year); } + /** + * Fetches all expenses ordered by date in descending order. + * @return the list of expenses found + */ + public List findAllOrderByDateDescending() { + return this.expenseRepository.findAllByOrderByDateDesc(); + } + + /** + * Finds expenses by year excluding those marked as credit and paid. + * @param year the year for which to find expenses + * @return the list of expenses found + */ public List findExpensesByYearExcludingCreditPaid(int year) { return expenseRepository.findByYearAndFilterCreditPaid(year, ExpenseType.CREDIT); } /** - * checks if an expense has been paid + * Checks if an expense has been paid. * @param expense the expense to check * @return true if the expense has been paid, false otherwise */ @@ -127,35 +130,76 @@ public class ExpenseService { } /** - * fetches all expenses ordered by date descending - * @return the list of expenses found + * Updates an existing expense. + * @param entity the expense to update */ - public List findAllOrderByDateDescending() { - return this.expenseRepository.findAllByOrderByDateDesc(); + public void update(Expense entity) { + if (Boolean.TRUE.equals(entity.getIsPaid())) { + entity.setPaymentDate(LocalDateTime.now()); + } + this.setExpenseType(entity); + expenseRepository.save(entity); } /** - * sets the Expense type depending on the presence or absence of creditors and debtors - * this is used to filter expenses with a creditor that are paid, since they are not part of - * the actual money the user has spent, it's just a load technically + * Deletes an expense given its ID. + * @param id the ID of the expense to delete + */ + public void delete(Long id) { + expenseRepository.deleteById(id); + } + + /** + * Lists expenses in a paginated format. + * @param pageable the pagination information + * @return a page of expenses + */ + public Page list(Pageable pageable) { + return expenseRepository.findAll(pageable); + } + + /** + * Lists expenses in a paginated format with filtering options. + * @param pageable the pagination information + * @param filter the filter specification + * @return a page of expenses matching the filter + */ + public Page list(Pageable pageable, Specification filter) { + return expenseRepository.findAll(filter, pageable); + } + + /** + * Counts the total number of expenses. + * @return the count of expenses + */ + public int count() { + return (int) expenseRepository.count(); + } + + // ================================ + // Private methods + // ================================ + + /** + * Sets the expense type depending on the presence or absence of a payer and beneficiary. + * This is used to filter expenses where the payer has been reimbursed. * @param expense the expense to set the type of */ private void setExpenseType(final @Nonnull Expense expense) { - // Check if the creditor is present - if (Objects.nonNull(expense.getCreditor())) { - // If creditor is present, set type to CREDIT + // Check if the payer is present + if (Objects.nonNull(expense.getPayer())) { + // If payer is present, set type to CREDIT expense.setExpenseType(ExpenseType.CREDIT); } - // Check if the debtor is present and no creditor - else if (Objects.nonNull(expense.getDebtor())) { - // If debtor is present and no creditor, set type to DEBIT + // Check if the beneficiary is present and no payer + else if (Objects.nonNull(expense.getBeneficiary())) { + // If beneficiary is present and no payer, set type to DEBIT expense.setExpenseType(ExpenseType.DEBIT); } - // If neither creditor nor debtor is present + // If neither payer nor beneficiary is present else { - // If neither creditor nor debtor is present, set type to NONE + // Set type to NONE expense.setExpenseType(ExpenseType.NONE); } } - -} +} \ No newline at end of file diff --git a/src/main/java/com/application/munera/services/PersonService.java b/src/main/java/com/application/munera/services/PersonService.java index 3538724..4d5c9c5 100644 --- a/src/main/java/com/application/munera/services/PersonService.java +++ b/src/main/java/com/application/munera/services/PersonService.java @@ -9,7 +9,7 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import java.math.BigDecimal; -import java.util.Collection; +import java.util.List; import java.util.Optional; @Service @@ -23,60 +23,108 @@ public class PersonService { this.expenseService = expenseService; } + /** + * Finds a person by ID. + * @param id the ID of the person + * @return an optional containing the person if found, otherwise empty + */ public Optional get(Long id) { return personRepository.findById(id); } - public Collection findAll() { + /** + * Finds all persons. + * @return a collection of all persons + */ + public List findAll() { return this.personRepository.findAll(); } - public void update(Person person) { - this.personRepository.save(person); - } - - public void delete(Long id) { - this.personRepository.deleteById(id); - } - - public Page list(Pageable pageable){ + /** + * Lists all persons with pagination. + * @param pageable the pagination information + * @return a page of persons + */ + public Page list(Pageable pageable) { return personRepository.findAll(pageable); } + /** + * Lists all persons with pagination and filtering. + * @param pageable the pagination information + * @param filter the specification filter + * @return a page of persons matching the filter + */ public Page list(Pageable pageable, Specification filter) { - return this.personRepository.findAll(filter, pageable); + return this.personRepository.findAll(filter, pageable); } + /** + * Counts the total number of persons. + * @return the total count of persons + */ public int count() { return (int) this.personRepository.count(); } /** - * calculates the debt a certain person has - * @param person the person of which you want to know the debt - * @return the debt that a certain person has + * Updates a person in the repository. + * @param person the person to update */ - public BigDecimal calculateDebt(final Person person){ - return this.expenseService.findDebtByUser(person).stream().map(Expense::getCost).reduce(BigDecimal.ZERO, BigDecimal::add); + public void update(Person person) { + this.personRepository.save(person); } /** - * calculates the credit a certain person has - * @param person the person of which you want to know the credit - * @return the credit that a certain person has + * Deletes a person by ID. + * @param id the ID of the person to delete + */ + public void delete(Long id) { + this.personRepository.deleteById(id); + } + + /** + * Calculates the total debt of a person. + * @param person the person whose debt is to be calculated + * @return the total debt amount + */ + public BigDecimal calculateDebt(final Person person) { + return this.expenseService.findExpensesWherePayer(person).stream() + .filter(expense -> !expense.getBeneficiary().equals(person) && Boolean.FALSE.equals(expense.getIsPaid())) + .map(Expense::getCost) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + /** + * Calculates the total credit of a person. + * @param person the person whose credit is to be calculated + * @return the total credit amount */ public BigDecimal calculateCredit(final Person person) { - return this.expenseService.findCreditByUser(person).stream().map(Expense::getCost).reduce(BigDecimal.ZERO, BigDecimal::add); + return this.expenseService.findExpensesWhereBeneficiary(person).stream() + .filter(expense -> !expense.getPayer().equals(person) && Boolean.FALSE.equals(expense.getIsPaid())) + .map(Expense::getCost) + .reduce(BigDecimal.ZERO, BigDecimal::add); } /** - * calculates the balance of a person using the money owed or paid off to that person - * @param person the person of which you want to know the balance - * @return the amount of money owed or paid off to a certain person + * Calculates the net balance of a person. + * The net balance is the difference between the total amount the person is owed + * (expenses where they are the payer) and the total amount the person owes + * (expenses where they are the beneficiary). + * + * A positive net balance means the person is owed money. + * A negative net balance means the person owes money. + * + * @param person the person whose net balance is to be calculated + * @return the net balance amount */ public BigDecimal calculateNetBalance(final Person person) { - final var credit = this.expenseService.findUnpaidCreditByUser(person).stream().map(Expense::getCost).reduce(BigDecimal.ZERO, BigDecimal::add); - final var debit = this.expenseService.findUnpaidDebtByUser(person).stream().map(Expense::getCost).reduce(BigDecimal.ZERO, BigDecimal::add); - return credit.subtract(debit); + // Calculate total debt (what others owe to the person) + final BigDecimal debt = this.calculateDebt(person); + // Calculate total credit (what the person owes to others) + final BigDecimal credit = this.calculateCredit(person); + // Net balance calculation: debt (owed to the person) - credit (person owes) + return debt.subtract(credit); } -} +} \ No newline at end of file diff --git a/src/main/java/com/application/munera/views/expenses/ExpensesView.java b/src/main/java/com/application/munera/views/expenses/ExpensesView.java index 2170c54..0e25096 100644 --- a/src/main/java/com/application/munera/views/expenses/ExpensesView.java +++ b/src/main/java/com/application/munera/views/expenses/ExpensesView.java @@ -66,8 +66,8 @@ public class ExpensesView extends Div implements BeforeEnterObserver { private ComboBox periodUnit; private TextField periodInterval; private DatePicker date; - private ComboBox creditor; - private ComboBox debtor; + private ComboBox payer; + private ComboBox beneficiary; private ComboBox event; public ExpensesView(ExpenseService expenseService, CategoryService categoryService, PersonService personService, EventService eventService, ViewService viewService) { @@ -94,7 +94,6 @@ public class ExpensesView extends Div implements BeforeEnterObserver { grid.addColumn(Expense::getPeriodUnit).setHeader("Period Unit").setSortable(true); grid.addColumn(Expense::getDate).setHeader("Date").setSortable(true).setSortProperty("date"); // grid.addColumn(expenseEvent -> expenseEvent.getEvent().getName()).setHeader("Event").setSortable(true); - grid.addColumn(new ComponentRenderer<>(this.viewService::createExpenseBadge)).setHeader("Status").setSortable(true); grid.getColumns().forEach(col -> col.setAutoWidth(true)); @@ -156,11 +155,11 @@ public class ExpensesView extends Div implements BeforeEnterObserver { //TODO: THIS NEEDS TO BE IMPLEMENTED BUT FOR THE SINGLE PERSON NOW, STILL NEEDED // // Event listeners that will remove the selected creditors from the debtors list and vice versa // // Done so that the user cant create an expense with the same person as creditor and debtor -// debtors.addValueChangeListener(event -> { -// Set selectedDebtors = event.getValue(); +// payer.addValueChangeListener(event -> { +// Person selectedDebtors = event.getValue(); // final var creditorsSet = new HashSet<>(personService.findAll()); -// creditorsSet.removeIf(selectedDebtors::contains); -// creditors.setItems(creditorsSet); +// creditorsSet.removeIf(creditorsSet.contains(selectedDebtors)); +// payer.setItems(creditorsSet); // }); // // creditors.addValueChangeListener(event -> { @@ -232,10 +231,10 @@ public class ExpensesView extends Div implements BeforeEnterObserver { private void createEditorLayout(SplitLayout splitLayout) { Div editorLayoutDiv = new Div(); editorLayoutDiv.setClassName("editor-layout"); - Div editorDiv = new Div(); editorDiv.setClassName("editor"); editorLayoutDiv.add(editorDiv); + final var people = this.personService.findAll(); FormLayout formLayout = new FormLayout(); name = new TextField("Name"); @@ -247,15 +246,15 @@ public class ExpensesView extends Div implements BeforeEnterObserver { periodUnit = new ComboBox<>("Period Unit"); periodUnit.setItems(PeriodUnit.values()); periodInterval = new TextField("Period Interval"); - creditor = new ComboBox<>("Creditor"); - creditor.setItems(personService.findAll()); - creditor.setItemLabelGenerator(Person::getFirstName); + payer = new ComboBox<>("Payer"); + payer.setItems(people); + payer.setItemLabelGenerator(Person::getFirstName); event = new ComboBox<>("Event"); event.setItems(eventService.findAll()); event.setItemLabelGenerator(Event::getName); - debtor = new ComboBox<>("Debtor"); - debtor.setItems(personService.findAll()); - debtor.setItemLabelGenerator(Person::getFirstName); + beneficiary = new ComboBox<>("Beneficiary"); + beneficiary.setItems(people); + beneficiary.setItemLabelGenerator(Person::getFirstName); date = new DatePicker("Date"); // Horizontal layout for checkboxes @@ -264,7 +263,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver { isPaid = new Checkbox("Paid"); checkboxLayout.add(isPeriodic, isPaid); - formLayout.add(name, cost, category, description, checkboxLayout, periodUnit, periodInterval, date, creditor, debtor, event); + formLayout.add(name, cost, category, description, checkboxLayout, periodUnit, periodInterval, date, payer, beneficiary, event); editorDiv.add(formLayout); createButtonLayout(editorLayoutDiv); @@ -304,9 +303,5 @@ public class ExpensesView extends Div implements BeforeEnterObserver { boolean isPeriodicChecked = (value != null) && value.getIsPeriodic(); periodUnit.setVisible(isPeriodicChecked); periodInterval.setVisible(isPeriodicChecked); - - // Set selected items for creditor and debtor - creditor.setValue(value != null ? value.getCreditor() : null); - debtor.setValue(value != null ? value.getDebtor() : null); } } \ No newline at end of file diff --git a/src/main/java/com/application/munera/views/people/PeopleView.java b/src/main/java/com/application/munera/views/people/PeopleView.java index c950c9c..4f5f3a2 100644 --- a/src/main/java/com/application/munera/views/people/PeopleView.java +++ b/src/main/java/com/application/munera/views/people/PeopleView.java @@ -83,9 +83,9 @@ public class PeopleView extends Div implements BeforeEnterObserver { else return this.viewService.createExpenseBadge(((Expense) persona)); })).setHeader("Balance Status"); - grid.addColumn(new ComponentRenderer<>(person -> { - if (person instanceof Person) { - Button markPaidButton = new Button("Mark All Expenses Paid", event -> markExpensesPaid((Person) person)); + grid.addColumn(new ComponentRenderer<>(persona -> { + if (persona instanceof Person) { + Button markPaidButton = new Button("Mark All Expenses Paid", event -> markExpensesPaid((Person) persona)); markPaidButton.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_PRIMARY); return markPaidButton; } else { @@ -93,7 +93,7 @@ public class PeopleView extends Div implements BeforeEnterObserver { } })).setHeader("Actions"); - List people = (List) personService.findAll(); + List people = personService.findAll(); this.setGridData(people); @@ -248,7 +248,7 @@ public class PeopleView extends Div implements BeforeEnterObserver { grid.getTreeData().addItem(null, user); // Fetch expenses for the current person - List expenses = expenseService.findExpenseByUser(user); + List expenses = expenseService.findExpensesByUser(user); // Add each expense as a child item under the person for (Expense expense : expenses) grid.getTreeData().addItem(user, expense); @@ -257,7 +257,7 @@ public class PeopleView extends Div implements BeforeEnterObserver { private void markExpensesPaid(Person person) { try { - List expenses = expenseService.findCreditByUser(person).stream().toList(); + List expenses = expenseService.findExpensesWherePayer(person).stream().toList(); for (Expense expense : expenses) { expense.setIsPaid(true); expenseService.update(expense);