feat: multi-tenancy
This commit is contained in:
parent
d3e0490eee
commit
b476cb9846
12 changed files with 93 additions and 63 deletions
|
@ -49,7 +49,7 @@ public class Person {
|
|||
@Column(name = "Username", unique = true)
|
||||
private String username; // This field will link to the User entity
|
||||
|
||||
@Column(name = "UserId", unique = true, nullable = false)
|
||||
@Column(name = "UserId", nullable = false)
|
||||
private Long userId; // Reference to the User entity
|
||||
|
||||
@Override
|
||||
|
|
|
@ -14,9 +14,9 @@ public class ExpenseFacade {
|
|||
this.expenseService = expenseService;
|
||||
}
|
||||
|
||||
public void setExpensePaid(Expense expense, TreeGrid<Object> grid) {
|
||||
public void setExpensePaid(Expense expense, TreeGrid<Object> grid, Long userId) {
|
||||
expense.setIsPaid(true);
|
||||
this.expenseService.update(expense);
|
||||
this.expenseService.update(expense, userId);
|
||||
Notification.show("Expense " + expense.getName() + " set as paid" );
|
||||
grid.select(null);
|
||||
grid.getDataProvider().refreshAll();
|
||||
|
|
|
@ -18,12 +18,12 @@ public class PersonFacade {
|
|||
public PersonFacade(ExpenseService expenseService) {
|
||||
this.expenseService = expenseService;
|
||||
}
|
||||
public void setDebtPaid(Person person, TreeGrid<Object> grid) {
|
||||
public void setDebtPaid(Person person, TreeGrid<Object> grid, Long userId) {
|
||||
try {
|
||||
List<Expense> expenses = expenseService.findExpensesWherePayer(person).stream().toList();
|
||||
for (Expense expense : expenses) {
|
||||
expense.setIsPaid(true);
|
||||
expenseService.update(expense);
|
||||
expenseService.update(expense, userId);
|
||||
}
|
||||
Notification.show("All expenses marked as paid for " + person.getFirstName() + " " + person.getLastName());
|
||||
grid.select(null);
|
||||
|
@ -35,12 +35,12 @@ public class PersonFacade {
|
|||
}
|
||||
}
|
||||
|
||||
public void setCreditPaid(Person person, TreeGrid<Object> grid) {
|
||||
public void setCreditPaid(Person person, TreeGrid<Object> grid, Long userId) {
|
||||
try {
|
||||
List<Expense> expenses = expenseService.findExpensesWhereBeneficiary(person).stream().toList();
|
||||
for (Expense expense : expenses) {
|
||||
expense.setIsPaid(true);
|
||||
expenseService.update(expense);
|
||||
expenseService.update(expense, userId);
|
||||
}
|
||||
Notification.show("All expenses marked as paid for " + person.getFirstName() + " " + person.getLastName());
|
||||
grid.select(null);
|
||||
|
|
|
@ -56,5 +56,5 @@ public interface ExpenseRepository extends JpaRepository<Expense, Long>, JpaSpec
|
|||
boolean existsByIdAndIsPaidTrue(Long id);
|
||||
|
||||
// Find all expenses ordered by date descending
|
||||
List<Expense> findAllByOrderByDateDesc();
|
||||
List<Expense> findByUserIdOrderByDateDesc(Long userId);
|
||||
}
|
|
@ -1,16 +1,30 @@
|
|||
package com.application.munera.repositories;
|
||||
|
||||
import com.application.munera.data.Person;
|
||||
import com.application.munera.data.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface PersonRepository extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {
|
||||
Optional<Person> findByUserId(Long userId);
|
||||
|
||||
@Query("SELECT p FROM Person p WHERE p.userId IS NULL")
|
||||
List<Person> findAllExcludeUser();
|
||||
Person findByUsername(String username);
|
||||
|
||||
@Query("SELECT p FROM Person p WHERE p.username = :username")
|
||||
Optional<Person> findOptionalByUsername(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* finds all the people that the logged user has created, minus the person that represents the logged user
|
||||
* @param userId the logged user id, to get all people connected to id
|
||||
* @param username the logged username, to filter out
|
||||
* @return the list people found
|
||||
*/
|
||||
@Query("SELECT p FROM Person p WHERE p.userId = :userId AND (p.username IS NULL OR p.username <> :username)")
|
||||
List<Person> findAllByUserIdExcludingPerson(@Param("userId") Long userId, @Param("username") String username);
|
||||
|
||||
List<Person> findByUserId(Long userId);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.application.munera.repositories;
|
|||
|
||||
import com.application.munera.data.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
@ -10,5 +12,5 @@ import java.util.Optional;
|
|||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
Optional<User> findByUsername(final @Nonnull String username);
|
||||
}
|
||||
@Query("SELECT u FROM User u WHERE u.username = :username")
|
||||
Optional<User> findByUsername(@Param("username") String username);}
|
||||
|
|
|
@ -16,7 +16,6 @@ import org.springframework.stereotype.Service;
|
|||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Year;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -83,11 +82,11 @@ public class ExpenseService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Finds all expenses related to a user, both where the user is a payer and a beneficiary.
|
||||
* @param person the user of the expenses
|
||||
* Finds all expenses related to a person, both where the person is a payer and a beneficiary.
|
||||
* @param person the person of the expenses
|
||||
* @return the list of expenses found
|
||||
*/
|
||||
public List<Expense> findExpensesByUser(final Person person) {
|
||||
public List<Expense> findExpensesByPerson(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
|
||||
|
@ -118,8 +117,8 @@ public class ExpenseService {
|
|||
* Fetches all expenses ordered by date in descending order.
|
||||
* @return the list of expenses found
|
||||
*/
|
||||
public List<Expense> findAllOrderByDateDescending() {
|
||||
return this.expenseRepository.findAllByOrderByDateDesc();
|
||||
public List<Expense> findAllOrderByDateDescending(Long userId) {
|
||||
return this.expenseRepository.findByUserIdOrderByDateDesc(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +143,8 @@ public class ExpenseService {
|
|||
* Updates an existing expense.
|
||||
* @param entity the expense to update
|
||||
*/
|
||||
public void update(Expense entity) {
|
||||
public void update(Expense entity, Long userId) {
|
||||
entity.setUserId(userId);
|
||||
if (Boolean.TRUE.equals(entity.getIsPaid())) entity.setPaymentDate(LocalDate.now());
|
||||
else entity.setPaymentDate(null);
|
||||
this.setExpenseType(entity);
|
||||
|
@ -223,14 +223,12 @@ public class ExpenseService {
|
|||
*/
|
||||
private void setExpenseType(final @Nonnull Expense expense) {
|
||||
// Get the currently logged-in user
|
||||
UserDetails userDetails = SecurityUtils.getLoggedInUserDetails();
|
||||
if (userDetails == null) {
|
||||
throw new IllegalStateException("No logged-in user found");
|
||||
}
|
||||
final var userDetails = SecurityUtils.getLoggedInUserDetails();
|
||||
if (userDetails == null) throw new IllegalStateException("No logged-in user found");
|
||||
// Fetch the logged-in user
|
||||
final var loggedInUserId = userRepository.findByUsername(userDetails.getUsername())
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found")).getId();
|
||||
Person loggedInPerson = this.personRepository.findByUserId(loggedInUserId).orElse(null);
|
||||
final var loggedInUser = userRepository.findByUsername(userDetails.getUsername())
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
Person loggedInPerson = this.personRepository.findByUsername(loggedInUser.getUsername());
|
||||
|
||||
if (loggedInPerson == null) throw new IllegalStateException("No associated Person entity found for logged-in user");
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ package com.application.munera.services;
|
|||
|
||||
import com.application.munera.data.Expense;
|
||||
import com.application.munera.data.Person;
|
||||
import com.application.munera.data.User;
|
||||
import com.application.munera.repositories.PersonRepository;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
@ -40,20 +40,22 @@ public class PersonService {
|
|||
* Finds all persons.
|
||||
* @return a collection of all persons
|
||||
*/
|
||||
public List<Person> findAll() {
|
||||
return this.personRepository.findAll();
|
||||
public List<Person> findAllByUserId(Long userId) {
|
||||
return this.personRepository.findByUserId(userId);
|
||||
}
|
||||
|
||||
public Optional<Person> findByUserId(Long id) {
|
||||
return this.personRepository.findByUserId(id);
|
||||
public Person findByUsername(String username) {
|
||||
return this.personRepository.findByUsername(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all people excluding the users'ones.
|
||||
* @return a collection of all persons
|
||||
*/
|
||||
public List<Person> findAllExcludeUsers() {
|
||||
return this.personRepository.findAllExcludeUser();
|
||||
public List<Person> findAllExcludeLoggedUser(User user) {
|
||||
final var userId = user.getId();
|
||||
final var username = user.getUsername();
|
||||
return this.personRepository.findAllByUserIdExcludingPerson(userId, username);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,14 +92,15 @@ public class PersonService {
|
|||
*/
|
||||
public Person getLoggedInPerson() {
|
||||
final var user = userService.getLoggedInUser();
|
||||
return Objects.requireNonNull(personRepository.findByUserId(user.getId()).orElse(null));
|
||||
return Objects.requireNonNull(personRepository.findByUsername(user.getUsername()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a person in the repository.
|
||||
* @param person the person to update
|
||||
*/
|
||||
public void update(Person person) {
|
||||
public void update(Person person, Long userId) {
|
||||
person.setUserId(userId);
|
||||
this.personRepository.save(person);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import com.application.munera.data.User;
|
|||
import com.application.munera.repositories.PersonRepository;
|
||||
import com.application.munera.repositories.UserRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -59,7 +58,7 @@ public class UserService {
|
|||
userRepository.save(userToSave);
|
||||
|
||||
// Check if the associated person exists for the user
|
||||
final var existingPersonOptional = personRepository.findByUserId(userToSave.getId());
|
||||
final var existingPersonOptional = personRepository.findOptionalByUsername(userToSave.getUsername());
|
||||
|
||||
if (existingPersonOptional.isPresent()) {
|
||||
// If person exists, update the person entity
|
||||
|
@ -106,7 +105,7 @@ public class UserService {
|
|||
|
||||
public void delete(final User user) {
|
||||
this.userRepository.delete(user);
|
||||
final var person = this.personRepository.findByUserId(user.getId());
|
||||
person.ifPresent(this.personRepository::delete);
|
||||
final var person = this.personRepository.findByUsername(user.getUsername());
|
||||
this.personRepository.delete(person);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ package com.application.munera.views.dashboard;
|
|||
|
||||
import com.application.munera.data.Expense;
|
||||
import com.application.munera.data.Person;
|
||||
import com.application.munera.data.User;
|
||||
import com.application.munera.services.ExpenseService;
|
||||
import com.application.munera.services.PersonService;
|
||||
import com.application.munera.services.UserService;
|
||||
import com.application.munera.views.MainLayout;
|
||||
import com.nimbusds.jose.shaded.gson.Gson;
|
||||
import com.vaadin.flow.component.html.Div;
|
||||
|
@ -28,10 +30,14 @@ public class DashboardView extends Div {
|
|||
|
||||
private final ExpenseService expenseService;
|
||||
private final PersonService personService;
|
||||
private final UserService userService;
|
||||
private final User loggedUser;
|
||||
|
||||
public DashboardView(final ExpenseService expenseService, final PersonService personService) {
|
||||
public DashboardView(final ExpenseService expenseService, final PersonService personService, UserService userService) {
|
||||
this.expenseService = expenseService;
|
||||
this.personService = personService;
|
||||
this.userService = userService;
|
||||
loggedUser = userService.getLoggedInUser();
|
||||
addClassName("highcharts-view"); // Optional CSS class for styling
|
||||
|
||||
VerticalLayout mainLayout = new VerticalLayout();
|
||||
|
@ -190,7 +196,7 @@ public class DashboardView extends Div {
|
|||
}
|
||||
|
||||
private String generateNegativeColumnChartScript() {
|
||||
final var people = personService.findAllExcludeUsers().stream()
|
||||
final var people = personService.findAllExcludeLoggedUser(loggedUser).stream()
|
||||
.filter(person -> personService.calculateNetBalance(person).compareTo(BigDecimal.ZERO) != 0)
|
||||
.toList();
|
||||
if (people.isEmpty()) return generatePlaceholderChartScript("bottomLeftChart", "All Payments Settled");
|
||||
|
|
|
@ -56,6 +56,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
|||
private final BeanValidationBinder<Expense> binder;
|
||||
|
||||
private Expense expense;
|
||||
private Long userId;
|
||||
|
||||
private final ExpenseService expenseService;
|
||||
private final CategoryService categoryService;
|
||||
|
@ -81,6 +82,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
|||
this.personService = personService;
|
||||
this.viewsService = viewsService;
|
||||
this.userService = userService;
|
||||
this.userId = userService.getLoggedInUser().getId();
|
||||
addClassNames("expenses-view");
|
||||
|
||||
// Create UI
|
||||
|
@ -101,7 +103,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
|||
grid.addColumn(new ComponentRenderer<>(this.viewsService::createExpenseBadge)).setHeader("Status").setSortable(true);
|
||||
grid.getColumns().forEach(col -> col.setAutoWidth(true));
|
||||
|
||||
grid.setItems(this.expenseService.findAllOrderByDateDescending());
|
||||
grid.setItems(this.expenseService.findAllOrderByDateDescending(userId));
|
||||
grid.setPaginatorSize(5);
|
||||
grid.setPageSize(22); // setting page size
|
||||
grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
|
||||
|
@ -180,7 +182,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
|||
try {
|
||||
if (this.expense == null) this.expense = new Expense();
|
||||
binder.writeBean(this.expense);
|
||||
expenseService.update(this.expense);
|
||||
expenseService.update(this.expense, userId);
|
||||
clearForm();
|
||||
refreshGrid();
|
||||
Notification.show("Data updated");
|
||||
|
@ -235,13 +237,12 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
|||
}
|
||||
|
||||
private void createEditorLayout(SplitLayout splitLayout) {
|
||||
final var userId = this.userService.getLoggedInUser().getId();
|
||||
Div editorLayoutDiv = new Div();
|
||||
editorLayoutDiv.setClassName("editor-layout");
|
||||
Div editorDiv = new Div();
|
||||
editorDiv.setClassName("editor");
|
||||
editorLayoutDiv.add(editorDiv);
|
||||
final var people = this.personService.findAll();
|
||||
final var people = this.personService.findAllByUserId(userId);
|
||||
|
||||
FormLayout formLayout = new FormLayout();
|
||||
name = new TextField("Name");
|
||||
|
@ -292,7 +293,7 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
|||
}
|
||||
|
||||
private void refreshGrid() {
|
||||
grid.setItems(this.expenseService.findAllOrderByDateDescending());
|
||||
grid.setItems(this.expenseService.findAllOrderByDateDescending(userId));
|
||||
grid.select(null);
|
||||
grid.getDataProvider().refreshAll();
|
||||
}
|
||||
|
@ -309,19 +310,18 @@ public class ExpensesView extends Div implements BeforeEnterObserver {
|
|||
periodInterval.setVisible(isPeriodicChecked);
|
||||
}
|
||||
|
||||
//TODO: check and improve this mess pls
|
||||
private void initializeComboBoxes() {
|
||||
// Fetch the logged-in user's Person entity
|
||||
UserDetails userDetails = SecurityUtils.getLoggedInUserDetails();
|
||||
if (userDetails != null) {
|
||||
String username = userDetails.getUsername();
|
||||
final var user = this.userService.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
Optional<Person> loggedInPerson = personService.findByUserId(user.getId());
|
||||
if (loggedInPerson.isPresent()) {
|
||||
Person person = loggedInPerson.get();
|
||||
final var loggedInPerson = personService.findByUsername(user.getUsername());
|
||||
// Set default values for payer and beneficiary ComboBoxes
|
||||
payer.setValue(person);
|
||||
beneficiary.setValue(person);
|
||||
}
|
||||
payer.setValue(loggedInPerson);
|
||||
beneficiary.setValue(loggedInPerson);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,10 +2,12 @@ package com.application.munera.views.people;
|
|||
|
||||
import com.application.munera.data.Expense;
|
||||
import com.application.munera.data.Person;
|
||||
import com.application.munera.data.User;
|
||||
import com.application.munera.facades.ExpenseFacade;
|
||||
import com.application.munera.facades.PersonFacade;
|
||||
import com.application.munera.services.ExpenseService;
|
||||
import com.application.munera.services.PersonService;
|
||||
import com.application.munera.services.UserService;
|
||||
import com.application.munera.services.ViewsService;
|
||||
import com.application.munera.views.MainLayout;
|
||||
import com.vaadin.flow.component.UI;
|
||||
|
@ -56,21 +58,27 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
private final BeanValidationBinder<Person> binder;
|
||||
|
||||
private Person person;
|
||||
private User loggedUser;
|
||||
private Long userId;
|
||||
private final PersonService personService;
|
||||
private final PersonFacade personFacade;
|
||||
private final ExpenseFacade expenseFacade;
|
||||
private final ExpenseService expenseService;
|
||||
private final ViewsService viewsService;
|
||||
private final UserService userService;
|
||||
private TextField firstName;
|
||||
private TextField lastName;
|
||||
private EmailField email;
|
||||
|
||||
public PeopleView(PersonService personService, ExpenseService expenseService, ViewsService viewsService, PersonFacade personFacade, ExpenseFacade expenseFacade) {
|
||||
public PeopleView(PersonService personService, ExpenseService expenseService, ViewsService viewsService, PersonFacade personFacade, ExpenseFacade expenseFacade, UserService userService) {
|
||||
this.personService = personService;
|
||||
this.expenseService = expenseService;
|
||||
this.viewsService = viewsService;
|
||||
this.personFacade = personFacade;
|
||||
this.expenseFacade = expenseFacade;
|
||||
this.userService = userService;
|
||||
loggedUser = userService.getLoggedInUser();
|
||||
userId = loggedUser.getId();
|
||||
addClassNames("expenses-view");
|
||||
|
||||
// Create UI
|
||||
|
@ -92,12 +100,12 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
grid.addColumn(new ComponentRenderer<>(persona -> {
|
||||
switch (persona) {
|
||||
case Person person1 -> {
|
||||
Button setDebtPaidButton = new Button("Set all debt as paid", event -> this.personFacade.setDebtPaid(person1, grid));
|
||||
Button setDebtPaidButton = new Button("Set all debt as paid", event -> this.personFacade.setDebtPaid(person1, grid, userId));
|
||||
setDebtPaidButton.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_PRIMARY);
|
||||
return setDebtPaidButton;
|
||||
}
|
||||
case Expense expense -> {
|
||||
Button setExpensePaidButton = new Button("Set as paid", event -> this.expenseFacade.setExpensePaid(expense, grid));
|
||||
Button setExpensePaidButton = new Button("Set as paid", event -> this.expenseFacade.setExpensePaid(expense, grid, userId));
|
||||
setExpensePaidButton.addThemeVariants(ButtonVariant.LUMO_SMALL);
|
||||
if (Boolean.TRUE.equals((expense).getIsPaid())) setExpensePaidButton.setEnabled(false);
|
||||
return setExpensePaidButton;
|
||||
|
@ -110,13 +118,13 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
|
||||
grid.addColumn(new ComponentRenderer<>(persona -> {
|
||||
if (persona instanceof Person person1) {
|
||||
Button setCreditPaidButton = new Button("Set all credit as paid", event -> this.personFacade.setCreditPaid(person1, grid));
|
||||
Button setCreditPaidButton = new Button("Set all credit as paid", event -> this.personFacade.setCreditPaid(person1, grid, userId));
|
||||
setCreditPaidButton.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_PRIMARY);
|
||||
return setCreditPaidButton;
|
||||
} else return new Span();
|
||||
}));
|
||||
|
||||
List<Person> people = personService.findAllExcludeUsers();
|
||||
List<Person> people = personService.findAllExcludeLoggedUser(loggedUser);
|
||||
|
||||
this.setGridData(people);
|
||||
|
||||
|
@ -148,7 +156,7 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
try {
|
||||
if (this.person == null) this.person = new Person();
|
||||
binder.writeBean(this.person);
|
||||
personService.update(this.person);
|
||||
personService.update(this.person, userId);
|
||||
clearForm();
|
||||
refreshGrid();
|
||||
Notification.show("Data updated");
|
||||
|
@ -263,15 +271,15 @@ public class PeopleView extends Div implements BeforeEnterObserver {
|
|||
}
|
||||
|
||||
public void setGridData(List<Person> people) {
|
||||
for (Person user : people) {
|
||||
for (Person person : people) {
|
||||
// Add the person as a root item
|
||||
grid.getTreeData().addItem(null, user);
|
||||
grid.getTreeData().addItem(null, person);
|
||||
|
||||
// Fetch expenses for the current person
|
||||
List<Expense> expenses = expenseService.findExpensesByUser(user);
|
||||
List<Expense> expenses = expenseService.findExpensesByPerson(person);
|
||||
|
||||
// Add each expense as a child item under the person
|
||||
for (Expense expense : expenses) grid.getTreeData().addItem(user, expense);
|
||||
for (Expense expense : expenses) grid.getTreeData().addItem(person, expense);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue