From f9a1521d65bbebf6bf239757ba250dea1dac3164 Mon Sep 17 00:00:00 2001 From: William Brawner Date: Sat, 3 Apr 2021 17:16:25 -0700 Subject: [PATCH] WIP: Implement recurring transactions Signed-off-by: William Brawner --- .../budgetserver/budget/BudgetRequest.java | 2 - .../recurrence/RecurringTransaction.java | 184 ++++++++++++++++++ .../RecurringTransactionController.java | 168 ++++++++++++++++ .../RecurringTransactionRepository.java | 13 ++ .../RecurringTransactionRequest.java | 83 ++++++++ .../RecurringTransactionResponse.java | 62 ++++++ .../recurrence/RecurringTransactionTask.java | 84 ++++++++ .../budgetserver/transaction/Transaction.java | 40 ++-- .../transaction/TransactionController.java | 3 +- build.gradle | 2 +- 10 files changed, 625 insertions(+), 16 deletions(-) create mode 100644 api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransaction.java create mode 100644 api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionController.java create mode 100644 api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionRepository.java create mode 100644 api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionRequest.java create mode 100644 api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionResponse.java create mode 100644 api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionTask.java diff --git a/api/src/main/java/com/wbrawner/budgetserver/budget/BudgetRequest.java b/api/src/main/java/com/wbrawner/budgetserver/budget/BudgetRequest.java index e7e3b67..0a9a25b 100644 --- a/api/src/main/java/com/wbrawner/budgetserver/budget/BudgetRequest.java +++ b/api/src/main/java/com/wbrawner/budgetserver/budget/BudgetRequest.java @@ -2,7 +2,6 @@ package com.wbrawner.budgetserver.budget; import com.wbrawner.budgetserver.permission.UserPermissionRequest; -import javax.validation.constraints.NotNull; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -23,7 +22,6 @@ public class BudgetRequest { this.users.addAll(users); } - @NotNull public Set getUsers() { return Set.copyOf(users); } diff --git a/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransaction.java b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransaction.java new file mode 100644 index 0000000..f1df3ff --- /dev/null +++ b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransaction.java @@ -0,0 +1,184 @@ +package com.wbrawner.budgetserver.recurrence; + +import com.wbrawner.budgetserver.budget.Budget; +import com.wbrawner.budgetserver.category.Category; +import com.wbrawner.budgetserver.user.User; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import java.util.TimeZone; + +import static com.wbrawner.budgetserver.Utils.randomId; + +@Entity +public class RecurringTransaction { + @Id + private final String id = randomId(); + @ManyToOne + @JoinColumn(nullable = false) + private final User createdBy; + private String title; + private String description; + private Long amount; + @ManyToOne + private Category category; + private Boolean expense; + @ManyToOne + @JoinColumn(nullable = false) + private Budget budget; + private String timeZone; + private int time; + private FrequencyUnit frequencyUnit; + private int frequencyValue; + + public RecurringTransaction() { + this( + null, + null, + FrequencyUnit.DAILY, + 0, + null, + 0, + null, + null, + null, + null, + null + ); + } + + public RecurringTransaction( + String title, + String description, + FrequencyUnit frequencyUnit, + int frequencyValue, + String timeZone, + int time, + Long amount, + Category category, + Boolean expense, + User createdBy, + Budget budget + ) { + this.title = title; + this.description = description; + this.frequencyUnit = frequencyUnit; + this.frequencyValue = frequencyValue; + setTimeZone(timeZone); + this.time = time; + this.amount = amount; + this.category = category; + this.expense = expense; + this.createdBy = createdBy; + this.budget = budget; + } + + public String getId() { + return id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public FrequencyUnit getFrequencyUnit() { + return frequencyUnit; + } + + public int getFrequencyValue() { + return frequencyValue; + } + + public void setFrequency(FrequencyUnit frequencyUnit, int frequencyValue) { + if (frequencyValue < 0) throw new IllegalArgumentException("frequencyValue must be at least 0"); + if (frequencyValue > frequencyUnit.maxValue) { + throw new IllegalArgumentException(String.format( + "Invalid frequencyValue. Requested %d for %s but maxValue is %d", + frequencyValue, + frequencyUnit.name(), + frequencyUnit.maxValue + )); + } + this.frequencyUnit = frequencyUnit; + this.frequencyValue = frequencyValue; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = TimeZone.getTimeZone(timeZone).getID(); + } + + public int getTimeOfDayInSeconds() { + return time; + } + + public void setTimeOfDayInSeconds(int time) { + this.time = time; + } + + public Long getAmount() { + return amount; + } + + public void setAmount(Long amount) { + this.amount = amount; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public Boolean isExpense() { + return expense; + } + + public void setExpense(Boolean expense) { + this.expense = expense; + } + + public User getCreatedBy() { + return createdBy; + } + + public Budget getBudget() { + return budget; + } + + public void setBudget(Budget budget) { + this.budget = budget; + } + + enum FrequencyUnit { + DAILY(0), + WEEKLY(7), + MONTHLY(30), + YEARLY(365); + + int maxValue; + + FrequencyUnit(int maxValue) { + this.maxValue = maxValue; + } + } +} \ No newline at end of file diff --git a/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionController.java b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionController.java new file mode 100644 index 0000000..61b42dd --- /dev/null +++ b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionController.java @@ -0,0 +1,168 @@ +package com.wbrawner.budgetserver.recurrence; + +import com.wbrawner.budgetserver.ErrorResponse; +import com.wbrawner.budgetserver.category.Category; +import com.wbrawner.budgetserver.category.CategoryRepository; +import com.wbrawner.budgetserver.permission.Permission; +import com.wbrawner.budgetserver.permission.UserPermission; +import com.wbrawner.budgetserver.permission.UserPermissionRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.transaction.Transactional; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static com.wbrawner.budgetserver.Utils.getCurrentUser; + +@RestController +@RequestMapping(path = "/recurrence") +@Transactional +public class RecurringTransactionController { + private final CategoryRepository categoryRepository; + private final RecurringTransactionRepository recurringTransactionRepository; + private final UserPermissionRepository userPermissionsRepository; + + private final Logger logger = LoggerFactory.getLogger(RecurringTransactionController.class); + + public RecurringTransactionController( + CategoryRepository categoryRepository, + RecurringTransactionRepository recurringTransactionRepository, + UserPermissionRepository userPermissionsRepository + ) { + this.categoryRepository = categoryRepository; + this.recurringTransactionRepository = recurringTransactionRepository; + this.userPermissionsRepository = userPermissionsRepository; + } + + @GetMapping(path = "", produces = {MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity> getRecurringTransactions( + @RequestParam("budgetId") String budgetId + ) { + List userPermissions = userPermissionsRepository.findAllByUserAndBudget_IdIn( + getCurrentUser(), + Collections.singletonList(budgetId), + null + ); + if (userPermissions.isEmpty()) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + var budget = userPermissions.get(0).getBudget(); + var transactions = recurringTransactionRepository.findAllByBudget(budget) + .stream() + .map(RecurringTransactionResponse::new) + .collect(Collectors.toList()); + return ResponseEntity.ok(transactions); + } + + @GetMapping(path = "/{id}", produces = {MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity getRecurringTransaction(@PathVariable String id) { + var budgets = userPermissionsRepository.findAllByUser(getCurrentUser(), null) + .stream() + .map(UserPermission::getBudget) + .collect(Collectors.toList()); + var transaction = recurringTransactionRepository.findByIdAndBudgetIn(id, budgets).orElse(null); + if (transaction == null) return ResponseEntity.notFound().build(); + return ResponseEntity.ok(new RecurringTransactionResponse(transaction)); + } + + @PostMapping(path = "", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity newTransaction(@RequestBody RecurringTransactionRequest request) { + var userResponse = userPermissionsRepository.findByUserAndBudget_Id(getCurrentUser(), request.getBudgetId()) + .orElse(null); + if (userResponse == null) { + return ResponseEntity.badRequest().body(new ErrorResponse("Invalid budget ID")); + } + if (userResponse.getPermission().isNotAtLeast(Permission.WRITE)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + var budget = userResponse.getBudget(); + Category category = null; + if (request.getCategoryId() != null) { + category = categoryRepository.findByBudgetAndId(budget, request.getCategoryId()).orElse(null); + } + return ResponseEntity.ok(new RecurringTransactionResponse(recurringTransactionRepository.save(new RecurringTransaction( + request.getTitle(), + request.getDescription(), + request.getFrequencyUnit(), + request.getFrequencyValue(), + request.getTimeZone(), + request.getTime(), + request.getAmount(), + category, + request.getExpense(), + getCurrentUser(), + budget + )))); + } + + @PutMapping(path = "/{id}", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity updateTransaction(@PathVariable String id, @RequestBody RecurringTransactionRequest request) { + var transaction = recurringTransactionRepository.findById(id).orElse(null); + if (transaction == null) return ResponseEntity.notFound().build(); + var userPermission = userPermissionsRepository.findByUserAndBudget_Id(getCurrentUser(), transaction.getBudget().getId()).orElse(null); + if (userPermission == null) return ResponseEntity.notFound().build(); + if (userPermission.getPermission().isNotAtLeast(Permission.WRITE)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + if (request.getTitle() != null) { + transaction.setTitle(request.getTitle()); + } + if (request.getDescription() != null) { + transaction.setDescription(request.getDescription()); + } + if (request.getTimeZone() != null) { + transaction.setTimeZone(request.getTimeZone()); + } + if (request.getAmount() != null) { + transaction.setAmount(request.getAmount()); + } + if (request.getExpense() != null) { + transaction.setExpense(request.getExpense()); + } + if (request.getTime() != null) { + transaction.setTimeOfDayInSeconds(request.getTime()); + } + if (request.getFrequencyUnit() != null && request.getFrequencyValue() != null) { + transaction.setFrequency(request.getFrequencyUnit(), request.getFrequencyValue()); + } + if (request.getBudgetId() != null) { + var newUserPermission = userPermissionsRepository.findByUserAndBudget_Id(getCurrentUser(), request.getBudgetId()).orElse(null); + if (newUserPermission == null || newUserPermission.getPermission().isNotAtLeast(Permission.WRITE)) { + return ResponseEntity + .badRequest() + .body(new ErrorResponse("Invalid budget")); + } + transaction.setBudget(newUserPermission.getBudget()); + } + if (request.getCategoryId() != null) { + var category = categoryRepository.findByBudgetAndId(transaction.getBudget(), request.getCategoryId()).orElse(null); + if (category == null) { + return ResponseEntity + .badRequest() + .body(new ErrorResponse("Invalid category")); + } + transaction.setCategory(category); + } + return ResponseEntity.ok(new RecurringTransactionResponse(recurringTransactionRepository.save(transaction))); + } + + @DeleteMapping(path = "/{id}", produces = {MediaType.TEXT_PLAIN_VALUE}) + public ResponseEntity deleteTransaction(@PathVariable String id) { + var transaction = recurringTransactionRepository.findById(id).orElse(null); + if (transaction == null) return ResponseEntity.notFound().build(); + // Check that the transaction belongs to an budget that the user has access to before deleting it + var userPermission = userPermissionsRepository.findByUserAndBudget_Id(getCurrentUser(), transaction.getBudget().getId()).orElse(null); + if (userPermission == null) return ResponseEntity.notFound().build(); + if (userPermission.getPermission().isNotAtLeast(Permission.WRITE)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + recurringTransactionRepository.delete(transaction); + return ResponseEntity.ok().build(); + } +} \ No newline at end of file diff --git a/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionRepository.java b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionRepository.java new file mode 100644 index 0000000..9f74433 --- /dev/null +++ b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionRepository.java @@ -0,0 +1,13 @@ +package com.wbrawner.budgetserver.recurrence; + +import com.wbrawner.budgetserver.budget.Budget; +import org.springframework.data.repository.PagingAndSortingRepository; + +import java.util.List; +import java.util.Optional; + +public interface RecurringTransactionRepository extends PagingAndSortingRepository { + Optional findByIdAndBudgetIn(String id, List budgets); + + List findAllByBudget(Budget budget); +} \ No newline at end of file diff --git a/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionRequest.java b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionRequest.java new file mode 100644 index 0000000..c63c8bc --- /dev/null +++ b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionRequest.java @@ -0,0 +1,83 @@ +package com.wbrawner.budgetserver.recurrence; + +class RecurringTransactionRequest { + private final String title; + private final String description; + private final Long amount; + private final String categoryId; + private final Boolean expense; + private final String budgetId; + private final String timeZone; + private final Integer time; + private final RecurringTransaction.FrequencyUnit frequencyUnit; + private final Integer frequencyValue; + + + RecurringTransactionRequest() { + this(null, null, null, 0, null, 0, null, null, null, null); + } + + RecurringTransactionRequest( + String title, + String description, + RecurringTransaction.FrequencyUnit frequencyUnit, + int frequencyValue, + String timeZone, + int time, + Long amount, + String categoryId, + Boolean expense, + String budgetId + ) { + this.title = title; + this.description = description; + this.frequencyUnit = frequencyUnit; + this.frequencyValue = frequencyValue; + this.timeZone = timeZone; + this.time = time; + this.amount = amount; + this.categoryId = categoryId; + this.expense = expense; + this.budgetId = budgetId; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public String getTimeZone() { + return timeZone; + } + + public Integer getTime() { + return time; + } + + public RecurringTransaction.FrequencyUnit getFrequencyUnit() { + return frequencyUnit; + } + + public Integer getFrequencyValue() { + return frequencyValue; + } + + public Long getAmount() { + return amount; + } + + public String getCategoryId() { + return categoryId; + } + + public Boolean getExpense() { + return expense; + } + + public String getBudgetId() { + return budgetId; + } +} \ No newline at end of file diff --git a/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionResponse.java b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionResponse.java new file mode 100644 index 0000000..aa35748 --- /dev/null +++ b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionResponse.java @@ -0,0 +1,62 @@ +package com.wbrawner.budgetserver.recurrence; + +class RecurringTransactionResponse { + public final String id; + public final String title; + public final String description; + public final long amount; + public final boolean expense; + public final String budgetId; + public final String categoryId; + public final String createdBy; + public final String timeZone; + public final int time; + public final RecurringTransaction.FrequencyUnit frequencyUnit; + public final int frequencyValue; + + + RecurringTransactionResponse( + String id, + String title, + String description, + long amount, + boolean expense, + String budgetId, + String categoryId, + String createdBy, + String timeZone, + int time, + RecurringTransaction.FrequencyUnit frequencyUnit, + int frequencyValue + ) { + this.id = id; + this.title = title; + this.description = description; + this.amount = amount; + this.expense = expense; + this.budgetId = budgetId; + this.categoryId = categoryId; + this.createdBy = createdBy; + this.timeZone = timeZone; + this.time = time; + this.frequencyUnit = frequencyUnit; + this.frequencyValue = frequencyValue; + } + + RecurringTransactionResponse(RecurringTransaction recurringTransaction) { + this( + recurringTransaction.getId(), + recurringTransaction.getTitle(), + recurringTransaction.getDescription(), + recurringTransaction.getAmount(), + recurringTransaction.isExpense(), + recurringTransaction.getBudget().getId(), + recurringTransaction.getCategory() != null ? recurringTransaction.getCategory().getId() : null, + recurringTransaction.getCreatedBy().getId(), + recurringTransaction.getTimeZone(), + recurringTransaction.getTimeOfDayInSeconds(), + recurringTransaction.getFrequencyUnit(), + recurringTransaction.getFrequencyValue() + ); + } +} \ No newline at end of file diff --git a/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionTask.java b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionTask.java new file mode 100644 index 0000000..f5683b5 --- /dev/null +++ b/api/src/main/java/com/wbrawner/budgetserver/recurrence/RecurringTransactionTask.java @@ -0,0 +1,84 @@ +package com.wbrawner.budgetserver.recurrence; + +import com.wbrawner.budgetserver.transaction.Transaction; +import com.wbrawner.budgetserver.transaction.TransactionRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +@Component +public class RecurringTransactionTask { + private final RecurringTransactionRepository recurringTransactionRepository; + private final TransactionRepository transactionRepository; + + @Autowired + public RecurringTransactionTask( + RecurringTransactionRepository recurringTransactionRepository, + TransactionRepository transactionRepository + ) { + this.recurringTransactionRepository = recurringTransactionRepository; + this.transactionRepository = transactionRepository; + } + + @Scheduled(cron = "0 0 * * * *") + public void createTransactions() { + recurringTransactionRepository.findAll().forEach(recurringTransaction -> { + GregorianCalendar today = new GregorianCalendar(TimeZone.getTimeZone(recurringTransaction.getTimeZone())); + // if recurrence matches today, create a new transaction + int adjustedFrequencyValue, calendarField; + switch (recurringTransaction.getFrequencyUnit()) { + case DAILY -> { + // Daily transactions should have the frequency value set to 0, so we just force it to be the same + // as the current date in order to force the transaction creation. + adjustedFrequencyValue = today.get(Calendar.DATE); + calendarField = Calendar.DATE; + } + case WEEKLY -> { + // No adjustments needed for day of week + adjustedFrequencyValue = today.get(Calendar.DAY_OF_WEEK); + calendarField = Calendar.DAY_OF_WEEK; + } + case MONTHLY -> { + // Check if the day of the month is correct + adjustedFrequencyValue = Math.min(recurringTransaction.getFrequencyValue(), today.getActualMaximum(Calendar.DAY_OF_MONTH)); + calendarField = Calendar.DAY_OF_MONTH; + } + case YEARLY -> { + adjustedFrequencyValue = recurringTransaction.getFrequencyValue(); + if (today.isLeapYear(today.get(Calendar.YEAR)) && today.get(Calendar.DAY_OF_YEAR) >= 31 + 29) { + // We're just pretending that Feb 29th doesn't exist here... + adjustedFrequencyValue -= 1; + } + calendarField = Calendar.DAY_OF_YEAR; + } + default -> throw new IllegalStateException("Unexpected value: " + recurringTransaction.getFrequencyUnit()); + } + if (adjustedFrequencyValue == today.get(calendarField)) { + createTransaction(recurringTransaction, today); + } + }); + } + + private void createTransaction(RecurringTransaction recurringTransaction, Calendar transactionCalendar) { + transactionCalendar.set(Calendar.HOUR, 0); + transactionCalendar.set(Calendar.MINUTE, 0); + transactionCalendar.set(Calendar.SECOND, 0); + transactionCalendar.set(Calendar.MILLISECOND, 0); + transactionCalendar.add(Calendar.SECOND, recurringTransaction.getTimeOfDayInSeconds()); + transactionRepository.save(new Transaction( + recurringTransaction.getTitle(), + recurringTransaction.getDescription(), + transactionCalendar.toInstant(), + recurringTransaction.getAmount(), + recurringTransaction.getCategory(), + recurringTransaction.isExpense(), + recurringTransaction.getCreatedBy(), + recurringTransaction.getBudget(), + recurringTransaction + )); + } +} diff --git a/api/src/main/java/com/wbrawner/budgetserver/transaction/Transaction.java b/api/src/main/java/com/wbrawner/budgetserver/transaction/Transaction.java index c20697c..f0edf30 100644 --- a/api/src/main/java/com/wbrawner/budgetserver/transaction/Transaction.java +++ b/api/src/main/java/com/wbrawner/budgetserver/transaction/Transaction.java @@ -2,9 +2,13 @@ package com.wbrawner.budgetserver.transaction; import com.wbrawner.budgetserver.budget.Budget; import com.wbrawner.budgetserver.category.Category; +import com.wbrawner.budgetserver.recurrence.RecurringTransaction; import com.wbrawner.budgetserver.user.User; -import javax.persistence.*; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import java.time.Instant; import static com.wbrawner.budgetserver.Utils.randomId; @@ -26,19 +30,24 @@ public class Transaction implements Comparable { @ManyToOne @JoinColumn(nullable = false) private Budget budget; + @ManyToOne + private RecurringTransaction recurrence; public Transaction() { - this(null, null, null, null, null, null, null, null); + this(null, null, null, null, null, null, null, null, null); } - public Transaction(String title, - String description, - Instant date, - Long amount, - Category category, - Boolean expense, - User createdBy, - Budget budget) { + public Transaction( + String title, + String description, + Instant date, + Long amount, + Category category, + Boolean expense, + User createdBy, + Budget budget, + RecurringTransaction recurrence + ) { this.title = title; this.description = description; this.date = date; @@ -47,11 +56,10 @@ public class Transaction implements Comparable { this.expense = expense; this.createdBy = createdBy; this.budget = budget; + this.recurrence = recurrence; } public String getId() { - // This should only be set from Hibernate so it shouldn't actually be null ever - //noinspection ConstantConditions return id; } @@ -115,6 +123,14 @@ public class Transaction implements Comparable { this.budget = budget; } + public RecurringTransaction getRecurrence() { + return this.recurrence; + } + + public void setRecurrence(RecurringTransaction recurrence) { + this.recurrence = recurrence; + } + @Override public int compareTo(Transaction other) { return this.date.compareTo(other.date); diff --git a/api/src/main/java/com/wbrawner/budgetserver/transaction/TransactionController.java b/api/src/main/java/com/wbrawner/budgetserver/transaction/TransactionController.java index 56497e9..dc855cc 100644 --- a/api/src/main/java/com/wbrawner/budgetserver/transaction/TransactionController.java +++ b/api/src/main/java/com/wbrawner/budgetserver/transaction/TransactionController.java @@ -144,7 +144,8 @@ public class TransactionController { category, request.getExpense(), getCurrentUser(), - budget + budget, + null )))); } diff --git a/build.gradle b/build.gradle index fd68fce..5d1325d 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath "org.springframework.boot:spring-boot-gradle-plugin:2.2.2.RELEASE" + classpath "org.springframework.boot:spring-boot-gradle-plugin:2.4.3" } }