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.
This commit is contained in:
2019-03-10 23:13:55 +01:00
parent 24e9dcda35
commit 297d7a80fd
10 changed files with 209 additions and 22 deletions

View File

@@ -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);

View File

@@ -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<String> 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<String> getMailRecipients() {
return mailRecipients;
}
public void setMailRecipients(Collection<String> 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;
}
}

View File

@@ -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();
}
}

View File

@@ -360,7 +360,7 @@ public class RecurringTransactionService {
}
@Transactional(propagation = Propagation.REQUIRED)
public ResponseReason createTransaction(String recurringTransactionId) {
public ResponseReason createTransaction(String recurringTransactionId, Optional<Long> 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);

View File

@@ -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<RecurringTransaction> 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
}
}
}

View File

@@ -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
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=

View File

@@ -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)
);
);
-- 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);

View File

@@ -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<RecurringTransaction> 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;
}
}

View File

@@ -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');