From 297d7a80fdf4eeae9216148ddfffa0b86d5f2553 Mon Sep 17 00:00:00 2001 From: MK13 Date: Sun, 10 Mar 2019 23:13:55 +0100 Subject: [PATCH] Add basic mail reminder for recurring transactions Also add some basic accounts to the DB init. Further add dependency to JAXB as it was missing. Add an optional parameter amount to the recurringTransactions/createTransaction method that can be used to overwrite the amount of the recurring transaction. --- pom.xml | 5 ++ .../java/de/financer/FinancerApplication.java | 2 + .../de/financer/config/FinancerConfig.java | 25 +++++++ .../RecurringTransactionController.java | 6 +- .../service/RecurringTransactionService.java | 4 +- .../SendRecurringTransactionReminderTask.java | 69 ++++++++++++++++++ .../resources/config/application.properties | 13 +++- .../database/hsqldb/V1_0_0__init.sql | 20 +++++- ...dRecurringTransactionReminderTaskTest.java | 72 +++++++++++++++++++ .../integration/V999_99_00__testdata.sql | 15 ---- 10 files changed, 209 insertions(+), 22 deletions(-) create mode 100644 src/main/java/de/financer/task/SendRecurringTransactionReminderTask.java create mode 100644 src/test/java/de/financer/task/SendRecurringTransactionReminderTaskTest.java diff --git a/pom.xml b/pom.xml index 8fb3e96..790b37d 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,11 @@ jollyday 0.5.7 + + + org.glassfish.jaxb + jaxb-runtime + diff --git a/src/main/java/de/financer/FinancerApplication.java b/src/main/java/de/financer/FinancerApplication.java index e3cace7..670d579 100644 --- a/src/main/java/de/financer/FinancerApplication.java +++ b/src/main/java/de/financer/FinancerApplication.java @@ -2,8 +2,10 @@ package de.financer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class FinancerApplication { public static void main(String[] args) { SpringApplication.run(FinancerApplication.class); diff --git a/src/main/java/de/financer/config/FinancerConfig.java b/src/main/java/de/financer/config/FinancerConfig.java index 3041b2e..f08e8a1 100644 --- a/src/main/java/de/financer/config/FinancerConfig.java +++ b/src/main/java/de/financer/config/FinancerConfig.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Configuration; import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.Collection; import java.util.Optional; @Configuration @@ -14,6 +15,8 @@ public class FinancerConfig { private String countryCode; private String state; private String dateFormat; + private Collection mailRecipients; + private String fromAddress; /** * @return the raw country code, mostly an uppercase ISO 3166 2-letter code @@ -77,4 +80,26 @@ public class FinancerConfig { this.dateFormat = dateFormat; } + + /** + * @return a collection of email addresses that should receive mails from financer + */ + public Collection getMailRecipients() { + return mailRecipients; + } + + public void setMailRecipients(Collection mailRecipients) { + this.mailRecipients = mailRecipients; + } + + /** + * @return the from address used in emails send by financer + */ + public String getFromAddress() { + return fromAddress; + } + + public void setFromAddress(String fromAddress) { + this.fromAddress = fromAddress; + } } diff --git a/src/main/java/de/financer/controller/RecurringTransactionController.java b/src/main/java/de/financer/controller/RecurringTransactionController.java index 5b19f5d..792ea56 100644 --- a/src/main/java/de/financer/controller/RecurringTransactionController.java +++ b/src/main/java/de/financer/controller/RecurringTransactionController.java @@ -7,6 +7,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Optional; + @RestController @RequestMapping("recurringTransactions") public class RecurringTransactionController { @@ -42,7 +44,7 @@ public class RecurringTransactionController { } @RequestMapping("createTransaction") - public ResponseEntity createTransaction(String recurringTransactionId) { - return this.recurringTransactionService.createTransaction(recurringTransactionId).toResponseEntity(); + public ResponseEntity createTransaction(String recurringTransactionId, Long amount) { + return this.recurringTransactionService.createTransaction(recurringTransactionId, Optional.of(amount)).toResponseEntity(); } } diff --git a/src/main/java/de/financer/service/RecurringTransactionService.java b/src/main/java/de/financer/service/RecurringTransactionService.java index fd9a477..1e6b00e 100644 --- a/src/main/java/de/financer/service/RecurringTransactionService.java +++ b/src/main/java/de/financer/service/RecurringTransactionService.java @@ -360,7 +360,7 @@ public class RecurringTransactionService { } @Transactional(propagation = Propagation.REQUIRED) - public ResponseReason createTransaction(String recurringTransactionId) { + public ResponseReason createTransaction(String recurringTransactionId, Optional amount) { if (recurringTransactionId == null) { return ResponseReason.MISSING_RECURRING_TRANSACTION_ID; } else if (!NumberUtils.isCreatable(recurringTransactionId)) { @@ -378,7 +378,7 @@ public class RecurringTransactionService { return this.transactionService.createTransaction(recurringTransaction.getFromAccount().getKey(), recurringTransaction.getToAccount().getKey(), - recurringTransaction.getAmount(), + amount.orElseGet(() -> recurringTransaction.getAmount()), LocalDate.now().format(DateTimeFormatter.ofPattern(this.financerConfig.getDateFormat())), recurringTransaction.getDescription(), recurringTransaction); diff --git a/src/main/java/de/financer/task/SendRecurringTransactionReminderTask.java b/src/main/java/de/financer/task/SendRecurringTransactionReminderTask.java new file mode 100644 index 0000000..124379b --- /dev/null +++ b/src/main/java/de/financer/task/SendRecurringTransactionReminderTask.java @@ -0,0 +1,69 @@ +package de.financer.task; + +import de.financer.config.FinancerConfig; +import de.financer.model.RecurringTransaction; +import de.financer.service.RecurringTransactionService; +import org.apache.commons.collections4.IterableUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.MailException; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +public class SendRecurringTransactionReminderTask { + + @Autowired + private RecurringTransactionService recurringTransactionService; + + @Autowired + private FinancerConfig financerConfig; + + @Autowired + private JavaMailSender mailSender; + + @Scheduled(cron = "0 30 0 * * *") + public void sendReminder() { + final Iterable recurringTransactions = this.recurringTransactionService.getAllDueToday(); + + // If no recurring transaction is due today we don't need to send a reminder + if (IterableUtils.isEmpty(recurringTransactions)) { + return; // early return + } + + final StringBuilder reminderBuilder = new StringBuilder(); + + reminderBuilder.append("The following recurring transactions are due today:") + .append(System.lineSeparator()) + .append(System.lineSeparator()); + + IterableUtils.toList(recurringTransactions).stream().forEach((rt) -> { + reminderBuilder.append(rt.getId()) + .append("|") + .append(rt.getDescription()) + .append(System.lineSeparator()) + .append("From ") + .append(rt.getFromAccount().getKey()) + .append(" to ") + .append(rt.getToAccount().getKey()) + .append(": ") + .append(rt.getAmount().toString()) + .append(System.lineSeparator()) + .append(System.lineSeparator()); + }); + + final SimpleMailMessage msg = new SimpleMailMessage(); + + msg.setTo(this.financerConfig.getMailRecipients().toArray(new String[]{})); + msg.setFrom(this.financerConfig.getFromAddress()); + msg.setSubject("[Financer] Recurring transactions reminder"); + msg.setText(reminderBuilder.toString()); + + try { + this.mailSender.send(msg); + } catch (MailException e) { + // TODO log + } + } +} diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 9f05903..c4701ea 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -24,4 +24,15 @@ financer.countryCode=DE financer.state=sl # The date format of the client-supplied date string, used to parse the string into a proper object -financer.dateFormat=dd.MM.yyyy \ No newline at end of file +financer.dateFormat=dd.MM.yyyy + +# A collection of email addresses that should receive mails from financer +financer.mailRecipients[0]=marius@kleberonline.de + +# The from address used in emails send by financer +financer.fromAddress=financer@77zzcx7.de + +# Mail configuration +spring.mail.host=localhost +#spring.mail.username= +#spring.mail.password= diff --git a/src/main/resources/database/hsqldb/V1_0_0__init.sql b/src/main/resources/database/hsqldb/V1_0_0__init.sql index 88b281a..a8cca05 100644 --- a/src/main/resources/database/hsqldb/V1_0_0__init.sql +++ b/src/main/resources/database/hsqldb/V1_0_0__init.sql @@ -1,5 +1,5 @@ -- --- This file contains the basic initialization of the financer schema +-- This file contains the basic initialization of the financer schema and basic init data -- -- Account table @@ -42,4 +42,20 @@ CREATE TABLE "transaction" ( --escape keyword "transaction" CONSTRAINT fk_transaction_from_account FOREIGN KEY (from_account_id) REFERENCES account (id), CONSTRAINT fk_transaction_to_account FOREIGN KEY (to_account_id) REFERENCES account (id), CONSTRAINT fk_transaction_recurring_transaction FOREIGN KEY (recurring_transaction_id) REFERENCES recurring_transaction (id) -); \ No newline at end of file +); + +-- Accounts +INSERT INTO account (id, "key", type, status, current_balance) +VALUES (1, 'accounts.checkaccount', 'BANK', 'OPEN', 0); -- insert first with ID 1 so we get predictable numbering + +INSERT INTO account ("key", type, status, current_balance) +VALUES ('accounts.income', 'INCOME', 'OPEN', 0); + +INSERT INTO account ("key", type, status, current_balance) +VALUES ('accounts.cash', 'CASH', 'OPEN', 0); + +INSERT INTO account ("key", type, status, current_balance) +VALUES ('accounts.start', 'START', 'OPEN', 0); + +INSERT INTO account ("key", type, status, current_balance) +VALUES ('accounts.rent', 'EXPENSE', 'OPEN', 0); \ No newline at end of file diff --git a/src/test/java/de/financer/task/SendRecurringTransactionReminderTaskTest.java b/src/test/java/de/financer/task/SendRecurringTransactionReminderTaskTest.java new file mode 100644 index 0000000..7af9de9 --- /dev/null +++ b/src/test/java/de/financer/task/SendRecurringTransactionReminderTaskTest.java @@ -0,0 +1,72 @@ +package de.financer.task; + +import de.financer.config.FinancerConfig; +import de.financer.model.Account; +import de.financer.model.RecurringTransaction; +import de.financer.service.RecurringTransactionService; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +@RunWith(MockitoJUnitRunner.class) +public class SendRecurringTransactionReminderTaskTest { + @InjectMocks + private SendRecurringTransactionReminderTask classUnderTest; + + @Mock + private RecurringTransactionService recurringTransactionService; + + @Mock + private JavaMailSender mailSender; + + @Mock + private FinancerConfig financerConfig; + + @Test + public void test_sendReminder() { + // Arrange + final Collection recurringTransactions = Arrays.asList( + createRecurringTransaction("Test booking 1", "accounts.income", "accounts.bank", Long.valueOf(250000)), + createRecurringTransaction("Test booking 2", "accounts.bank", "accounts.rent", Long.valueOf(41500)), + createRecurringTransaction("Test booking 3", "accounts.bank", "accounts.cash", Long.valueOf(5000)) + ); + + Mockito.when(this.recurringTransactionService.getAllDueToday()).thenReturn(recurringTransactions); + Mockito.when(this.financerConfig.getMailRecipients()).thenReturn(Collections.singletonList("test@test.com")); + + // Act + this.classUnderTest.sendReminder(); + + // Assert + Mockito.verify(this.mailSender, Mockito.times(1)).send(Mockito.any(SimpleMailMessage.class)); + } + + private RecurringTransaction createRecurringTransaction(String description, String fromAccountKey, String toAccountKey, Long amount) { + final RecurringTransaction recurringTransaction = new RecurringTransaction(); + + recurringTransaction.setDescription(description); + recurringTransaction.setFromAccount(createAccount(fromAccountKey)); + recurringTransaction.setToAccount(createAccount(toAccountKey)); + recurringTransaction.setAmount(amount); + + return recurringTransaction; + } + + private Account createAccount(String key) { + final Account account = new Account(); + + account.setKey(key); + + return account; + } +} diff --git a/src/test/resources/database/hsqldb/integration/V999_99_00__testdata.sql b/src/test/resources/database/hsqldb/integration/V999_99_00__testdata.sql index 2000b85..18c37f1 100644 --- a/src/test/resources/database/hsqldb/integration/V999_99_00__testdata.sql +++ b/src/test/resources/database/hsqldb/integration/V999_99_00__testdata.sql @@ -1,22 +1,7 @@ -- Accounts -INSERT INTO account (id, "key", type, status, current_balance) -VALUES (1, 'accounts.checkaccount', 'BANK', 'OPEN', 0); -- insert first with ID 1 so we get predictable numbering - -INSERT INTO account ("key", type, status, current_balance) -VALUES ('accounts.income', 'INCOME', 'OPEN', 0); - -INSERT INTO account ("key", type, status, current_balance) -VALUES ('accounts.cash', 'CASH', 'OPEN', 0); - -INSERT INTO account ("key", type, status, current_balance) -VALUES ('accounts.start', 'START', 'OPEN', 0); - INSERT INTO account ("key", type, status, current_balance) VALUES ('accounts.convenience', 'EXPENSE', 'OPEN', 0); -INSERT INTO account ("key", type, status, current_balance) -VALUES ('accounts.rent', 'EXPENSE', 'OPEN', 0); - --Recurring transactions INSERT INTO recurring_transaction (from_account_id, to_account_id, description, amount, interval_type, first_occurrence, holiday_weekend_type) VALUES (2, 1, 'Pay', 250000, 'MONTHLY', '2019-01-15', 'NEXT_WORKDAY');