diff --git a/src/main/java/com/application/munera/data/Expense.java b/src/main/java/com/application/munera/data/Expense.java index 270ece6..7c651e2 100644 --- a/src/main/java/com/application/munera/data/Expense.java +++ b/src/main/java/com/application/munera/data/Expense.java @@ -8,7 +8,6 @@ import lombok.Setter; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Set; @Entity @Getter @@ -45,19 +44,13 @@ public class Expense extends AbstractEntity { @Column(name = "PeriodInterval") private Integer periodInterval; - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable( - name = "Creditor_expenses", - joinColumns = @JoinColumn(name = "expense_id"), - inverseJoinColumns = @JoinColumn(name = "people_id")) - private Set creditors; + @ManyToOne + @JoinColumn(name = "CreditorId") + private Person creditor; - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable( - name = "Debtors_expenses", - joinColumns = @JoinColumn(name = "expense_id"), - inverseJoinColumns = @JoinColumn(name = "people_id")) - private Set debtors; + @ManyToOne + @JoinColumn(name = "DebtorId") + private Person debtor; @ManyToOne @JoinColumn(name = "EventId") @@ -69,13 +62,10 @@ public class Expense extends AbstractEntity { @Column(name = "PaymentDate") private LocalDateTime paymentDate; - /** - * the isPaid field starts as always false - */ @Column(name = "isPaid", nullable = false) private Boolean isPaid = false; @Enumerated(EnumType.STRING) @Column(name = "expenseType", nullable = false) private ExpenseType expenseType; -} +} \ No newline at end of file diff --git a/src/main/java/com/application/munera/data/Person.java b/src/main/java/com/application/munera/data/Person.java index 05f06d8..ccfcec7 100644 --- a/src/main/java/com/application/munera/data/Person.java +++ b/src/main/java/com/application/munera/data/Person.java @@ -40,11 +40,11 @@ public class Person extends AbstractEntity { @Column(name = "credit") private BigDecimal credit; - @ManyToMany(mappedBy = "creditors") - private Set creditorExpenses; + @OneToMany(mappedBy = "creditor") + private Set creditExpenses; - @ManyToMany(mappedBy = "debtors") - private Set debtorExpenses; + @OneToMany(mappedBy = "debtor") + private Set debtExpenses; @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 ed1864b..af4c4eb 100644 --- a/src/main/java/com/application/munera/repositories/ExpenseRepository.java +++ b/src/main/java/com/application/munera/repositories/ExpenseRepository.java @@ -13,24 +13,33 @@ import java.util.Set; public interface ExpenseRepository extends JpaRepository, JpaSpecificationExecutor { - @Query("SELECT e FROM Expense e JOIN e.creditors c WHERE c.id = :personId") + // 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 JOIN e.debtors d WHERE d.id = :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); + // Find all expenses for a given year @Query("SELECT e FROM Expense e WHERE YEAR(e.date) = :year") List findAllByYear(@Param("year") int year); - @Query("SELECT e FROM Expense e JOIN e.creditors c WHERE c.id = :personId AND e.isPaid = false") + // 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 JOIN e.debtors d WHERE d.id = :personId AND e.isPaid = false") + // 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); + // 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)") List findByYearAndFilterCreditPaid(@Param("year") int year, @Param("expenseType") ExpenseType expenseType); + // Check if an expense with the given ID exists and is paid boolean existsByIdAndIsPaidTrue(Long id); - List findAllByOrderByDateDesc();} \ No newline at end of file + // Find all expenses ordered by date descending + List findAllByOrderByDateDesc(); +} \ No newline at end of file diff --git a/src/main/java/com/application/munera/services/ExpenseService.java b/src/main/java/com/application/munera/services/ExpenseService.java index 53d8afa..2b7f93d 100644 --- a/src/main/java/com/application/munera/services/ExpenseService.java +++ b/src/main/java/com/application/munera/services/ExpenseService.java @@ -13,6 +13,7 @@ import javax.annotation.Nonnull; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -140,14 +141,21 @@ public class ExpenseService { * @param expense the expense to set the type of */ private void setExpenseType(final @Nonnull Expense expense) { - if (!expense.getCreditors().isEmpty()) - // If creditors are present, set type to CREDIT + // Check if the creditor is present + if (Objects.nonNull(expense.getCreditor())) { + // If creditor is present, set type to CREDIT expense.setExpenseType(ExpenseType.CREDIT); - else if (!expense.getDebtors().isEmpty()) - // If debtors are present and no creditors, set type to DEBIT + } + // 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 expense.setExpenseType(ExpenseType.DEBIT); - else - // If neither creditors nor debtors are present, set type to NONE + } + // If neither creditor nor debtor is present + else { + // If neither creditor nor debtor is present, set type to NONE expense.setExpenseType(ExpenseType.NONE); } + } + } 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 70da0d6..2170c54 100644 --- a/src/main/java/com/application/munera/views/expenses/ExpensesView.java +++ b/src/main/java/com/application/munera/views/expenses/ExpensesView.java @@ -8,7 +8,6 @@ import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.checkbox.Checkbox; import com.vaadin.flow.component.combobox.ComboBox; -import com.vaadin.flow.component.combobox.MultiSelectComboBox; import com.vaadin.flow.component.datepicker.DatePicker; import com.vaadin.flow.component.dependency.Uses; import com.vaadin.flow.component.formlayout.FormLayout; @@ -31,10 +30,8 @@ import jakarta.annotation.security.PermitAll; import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.vaadin.klaudeta.PaginatedGrid; -import java.util.HashSet; import java.util.Objects; import java.util.Optional; -import java.util.Set; @PermitAll @PageTitle("Expenses") @@ -69,9 +66,10 @@ public class ExpensesView extends Div implements BeforeEnterObserver { private ComboBox periodUnit; private TextField periodInterval; private DatePicker date; - private MultiSelectComboBox creditors; - private MultiSelectComboBox debtors; + private ComboBox creditor; + private ComboBox debtor; private ComboBox event; + public ExpensesView(ExpenseService expenseService, CategoryService categoryService, PersonService personService, EventService eventService, ViewService viewService) { this.expenseService = expenseService; this.categoryService = categoryService; @@ -102,7 +100,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver { grid.setItems(this.expenseService.findAllOrderByDateDescending()); grid.setPaginatorSize(5); - grid.setPageSize(22); // setting page size + grid.setPageSize(22); // setting page size grid.addThemeVariants(GridVariant.LUMO_NO_BORDER); // when a row is selected or deselected, populate form @@ -125,7 +123,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver { binder.forField(cost) .asRequired("Cost is required") - .withConverter( new StringToBigDecimalConverter("Invalid cost")) + .withConverter(new StringToBigDecimalConverter("Invalid cost")) .bind(Expense::getCost, Expense::setCost); binder.forField(category) @@ -155,21 +153,22 @@ public class ExpensesView extends Div implements BeforeEnterObserver { } }); - // 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(); - final var creditorsSet = new HashSet<>(personService.findAll()); - creditorsSet.removeIf(selectedDebtors::contains); - creditors.setItems(creditorsSet); - }); - - creditors.addValueChangeListener(event -> { - Set selectedCreditors = event.getValue(); - final var debtorsSet = new HashSet<>(personService.findAll()); - debtorsSet.removeIf(selectedCreditors::contains); - debtors.setItems(debtorsSet); - }); + //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(); +// final var creditorsSet = new HashSet<>(personService.findAll()); +// creditorsSet.removeIf(selectedDebtors::contains); +// creditors.setItems(creditorsSet); +// }); +// +// creditors.addValueChangeListener(event -> { +// Set selectedCreditors = event.getValue(); +// final var debtorsSet = new HashSet<>(personService.findAll()); +// debtorsSet.removeIf(selectedCreditors::contains); +// debtors.setItems(debtorsSet); +// }); cancel.addClickListener(e -> { clearForm(); @@ -205,7 +204,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver { UI.getCurrent().navigate(ExpensesView.class); } catch (ObjectOptimisticLockingFailureException exception) { Notification n = Notification.show( - "Error updating the data. Somebody else has updated the record while you were making changes."); + "Error deleting the data. Somebody else has updated the record while you were making changes."); n.setPosition(Position.MIDDLE); n.addThemeVariants(NotificationVariant.LUMO_ERROR); } @@ -218,7 +217,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver { if (expenseId.isPresent()) { Optional expenseFromBackend = expenseService.get(expenseId.get()); if (expenseFromBackend.isPresent()) populateForm(expenseFromBackend.get()); - else { + else { Notification.show( String.format("The requested expense was not found, ID = %s", expenseId.get()), 3000, Notification.Position.BOTTOM_START); @@ -248,15 +247,15 @@ public class ExpensesView extends Div implements BeforeEnterObserver { periodUnit = new ComboBox<>("Period Unit"); periodUnit.setItems(PeriodUnit.values()); periodInterval = new TextField("Period Interval"); - creditors = new MultiSelectComboBox<>("Creditors"); - creditors.setItems(personService.findAll()); - creditors.setItemLabelGenerator(Person::getFirstName); + creditor = new ComboBox<>("Creditor"); + creditor.setItems(personService.findAll()); + creditor.setItemLabelGenerator(Person::getFirstName); event = new ComboBox<>("Event"); event.setItems(eventService.findAll()); event.setItemLabelGenerator(Event::getName); - debtors = new MultiSelectComboBox<>("Debtors"); - debtors.setItems(personService.findAll()); - debtors.setItemLabelGenerator(Person::getFirstName); + debtor = new ComboBox<>("Debtor"); + debtor.setItems(personService.findAll()); + debtor.setItemLabelGenerator(Person::getFirstName); date = new DatePicker("Date"); // Horizontal layout for checkboxes @@ -265,7 +264,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, creditors, debtors, event); + formLayout.add(name, cost, category, description, checkboxLayout, periodUnit, periodInterval, date, creditor, debtor, event); editorDiv.add(formLayout); createButtonLayout(editorLayoutDiv); @@ -305,5 +304,9 @@ 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