From 519d8ab19af5e3ff87b0d3588611994d59a6fda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Pit=C3=A1k?= <xpitak@fi.muni.cz> Date: Wed, 24 Apr 2024 02:17:14 +0200 Subject: [PATCH] Implementation for automatic Scheduled payment --- .../controller/AccountController.java | 1 - m2m-banking-api/transaction-api/openapi.yaml | 1 + .../repository/ProcessRepositoryImpl.java | 33 ++++++-- .../repository/ProcessRepositoryJpa.java | 12 +++ .../service/ScheduledPaymentService.java | 81 +++++++++++++++++++ .../service/TransactionProcessesService.java | 8 +- .../pa165/banking/domain/process/Process.java | 14 +++- .../domain/process/ProcessFactory.java | 2 +- .../process/repository/ProcessRepository.java | 5 ++ .../domain/transaction/Transaction.java | 3 +- .../src/main/resources/application.yaml | 5 +- .../banking/domain/process/ProcessMock.java | 9 +++ .../domain/process/ProcessOperationsTest.java | 2 +- 13 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 transaction-processor/src/main/java/cz/muni/pa165/banking/application/repository/ProcessRepositoryJpa.java create mode 100644 transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ScheduledPaymentService.java diff --git a/account-management/src/main/java/cz/muni/pa165/banking/application/controller/AccountController.java b/account-management/src/main/java/cz/muni/pa165/banking/application/controller/AccountController.java index d2a767c..dc2ed11 100644 --- a/account-management/src/main/java/cz/muni/pa165/banking/application/controller/AccountController.java +++ b/account-management/src/main/java/cz/muni/pa165/banking/application/controller/AccountController.java @@ -3,7 +3,6 @@ package cz.muni.pa165.banking.application.controller; import cz.muni.pa165.banking.account.management.AccountApi; import cz.muni.pa165.banking.account.management.dto.*; import cz.muni.pa165.banking.application.facade.AccountFacade; -import cz.muni.pa165.banking.exception.EntityNotFoundException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; diff --git a/m2m-banking-api/transaction-api/openapi.yaml b/m2m-banking-api/transaction-api/openapi.yaml index f44f9df..1689d72 100644 --- a/m2m-banking-api/transaction-api/openapi.yaml +++ b/m2m-banking-api/transaction-api/openapi.yaml @@ -99,6 +99,7 @@ components: - DEPOSIT - TRANSFER - SCHEDULED + - REFUND description: Enumaration defining a type for a transaction. Each type may have different certain implementations and validations. AccountDto: diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/repository/ProcessRepositoryImpl.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/repository/ProcessRepositoryImpl.java index 8d13fe1..40ca8f0 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/repository/ProcessRepositoryImpl.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/repository/ProcessRepositoryImpl.java @@ -2,31 +2,48 @@ package cz.muni.pa165.banking.application.repository; import cz.muni.pa165.banking.domain.process.Process; import cz.muni.pa165.banking.domain.process.repository.ProcessRepository; +import cz.muni.pa165.banking.domain.process.status.Status; +import cz.muni.pa165.banking.exception.EntityNotFoundException; import org.springframework.stereotype.Repository; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; @Repository public class ProcessRepositoryImpl implements ProcessRepository { - // TODO until app has no DB connection -> Milestone2 - private final Map<UUID, Process> inmemoryDb = new HashMap<>(); + private final ProcessRepositoryJpa repository; + + public ProcessRepositoryImpl(ProcessRepositoryJpa repository) { + this.repository = repository; + } @Override public boolean idExists(UUID uuid) { - return inmemoryDb.containsKey(uuid); + return repository.existsById(uuid.toString()); } @Override public Process findById(UUID uuid) { - return inmemoryDb.get(uuid); + Optional<Process> process = repository.findById(uuid.toString()); + if (process.isEmpty()) { + throw new EntityNotFoundException(String.format("Process with UUID %s not found", uuid)); + } + return process.get(); } @Override public void save(Process process) { - inmemoryDb.put(process.uuid(), process); + repository.save(process); + } + + @Override + public List<Process> findProcessOfStatus(Status status) { + return null; + } + + @Override + public Integer invalidateStaleProcesses() { + return null; } } diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/repository/ProcessRepositoryJpa.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/repository/ProcessRepositoryJpa.java new file mode 100644 index 0000000..c018d0e --- /dev/null +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/repository/ProcessRepositoryJpa.java @@ -0,0 +1,12 @@ +package cz.muni.pa165.banking.application.repository; + +import cz.muni.pa165.banking.domain.process.Process; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProcessRepositoryJpa extends JpaRepository<Process, String> { + + // TODO find podla statusu + + // TODO update zaseknute procesy starsie nez e.g. tyzden a zmenit na failed + nastavit message nejaky + +} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ScheduledPaymentService.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ScheduledPaymentService.java new file mode 100644 index 0000000..eee4667 --- /dev/null +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ScheduledPaymentService.java @@ -0,0 +1,81 @@ +package cz.muni.pa165.banking.application.service; + +import cz.muni.pa165.banking.account.management.AccountApi; +import cz.muni.pa165.banking.account.management.dto.ScheduledPaymentDto; +import cz.muni.pa165.banking.account.management.dto.ScheduledPaymentsDto; +import cz.muni.pa165.banking.domain.account.Account; +import cz.muni.pa165.banking.domain.messaging.MessageProducer; +import cz.muni.pa165.banking.domain.money.Money; +import cz.muni.pa165.banking.domain.process.Process; +import cz.muni.pa165.banking.domain.process.ProcessFactory; +import cz.muni.pa165.banking.domain.process.repository.ProcessRepository; +import cz.muni.pa165.banking.domain.process.repository.ProcessTransactionRepository; +import cz.muni.pa165.banking.domain.transaction.Transaction; +import cz.muni.pa165.banking.domain.transaction.TransactionType; +import cz.muni.pa165.banking.exception.ServerError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.Currency; +import java.util.Objects; + +@Service +public class ScheduledPaymentService { + + private final Logger LOGGER = LoggerFactory.getLogger(ScheduledPaymentService.class); + + private final AccountApi accountApi; + + private final ProcessTransactionRepository processTransactionRepository; + + private final ProcessRepository processRepository; + + private final MessageProducer messageProducer; + + ScheduledPaymentService(AccountApi accountApi, + ProcessTransactionRepository processTransactionRepository, + ProcessRepository processRepository, + MessageProducer messageProducer) { + this.accountApi = accountApi; + this.processTransactionRepository = processTransactionRepository; + this.processRepository = processRepository; + this.messageProducer = messageProducer; + } + + @Scheduled(cron = "${scheduled-payments.cron.expression}") + public void executeScheduledPayments() { + LocalDate now = LocalDate.now(); + ResponseEntity<ScheduledPaymentsDto> response = accountApi.getScheduledPaymentsOf(now); + if (!response.getStatusCode().is2xxSuccessful()) { + throw new ServerError("Call to Account Management service unsuccessful."); + } + + ProcessFactory factory = new ProcessFactory(processTransactionRepository, processRepository); + ScheduledPaymentsDto payments = Objects.requireNonNull(response.getBody()); + for (ScheduledPaymentDto payment : payments.getScheduledPayments()) { + Transaction newTransaction = transactionForScheduledPayment(payment); + Process process = factory.create(newTransaction, messageProducer); + LOGGER.info("[Create Scheduled Payment Process] %s" + process.getUuid()); + } + + LOGGER.info("Finished creating processes for scheduled payments."); + } + + private Transaction transactionForScheduledPayment(ScheduledPaymentDto payment) { + Account source = new Account(payment.getSenderAccount()); + Account target = new Account(payment.getReceiverAccount()); + Money money = new Money(payment.getAmount(), Currency.getInstance(payment.getCurrencyCode())); + return new Transaction( + source, + target, + TransactionType.SCHEDULED, + money, + "Automatic execution of scheduled payment" + ); + } + +} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/TransactionProcessesService.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/TransactionProcessesService.java index fb378e4..04cdaa9 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/TransactionProcessesService.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/TransactionProcessesService.java @@ -1,6 +1,6 @@ package cz.muni.pa165.banking.application.service; -import cz.muni.pa165.banking.application.messaging.ProcessProducer; +import cz.muni.pa165.banking.domain.messaging.MessageProducer; import cz.muni.pa165.banking.domain.process.Process; import cz.muni.pa165.banking.domain.process.ProcessFactory; import cz.muni.pa165.banking.domain.process.ProcessTransaction; @@ -26,9 +26,11 @@ public class TransactionProcessesService { private final ProcessRepository processRepository; - private final ProcessProducer processProducer; + private final MessageProducer processProducer; - public TransactionProcessesService(ProcessTransactionRepository processTransactionRepository, ProcessRepository processRepository, ProcessProducer processProducer) { + public TransactionProcessesService(ProcessTransactionRepository processTransactionRepository, + ProcessRepository processRepository, + MessageProducer processProducer) { this.processTransactionRepository = processTransactionRepository; this.processRepository = processRepository; this.processProducer = processProducer; diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/Process.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/Process.java index 46275d0..b0e7aa0 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/Process.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/Process.java @@ -10,15 +10,21 @@ import java.util.UUID; @Entity public class Process { - private final UUID uuid; + private UUID uuid; private StatusInformation currentStatus; - Process() { - uuid = UUID.randomUUID(); - currentStatus = new StatusInformation(Instant.now(), Status.CREATED, "Process created, waiting for processing."); + public static Process createNew() { + Process process = new Process(); + process.uuid = UUID.randomUUID(); + process.currentStatus = new StatusInformation(Instant.now(), Status.CREATED, "Process created, waiting for processing."); + + return process; } + @Deprecated // hibernate + public Process() {} + /** * Return a copy of Process UUID, ensuring the UUID is not modified or replaced. */ diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/ProcessFactory.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/ProcessFactory.java index dfc8988..66f2ff7 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/ProcessFactory.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/ProcessFactory.java @@ -23,7 +23,7 @@ public class ProcessFactory { public Process create(Transaction transaction, MessageProducer messageProducer) { - Process newProcess = new Process(); + Process newProcess = Process.createNew(); processRepository.save(newProcess); ProcessTransaction assignedTransaction = new ProcessTransaction( diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/repository/ProcessRepository.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/repository/ProcessRepository.java index 12b56e5..e9ff491 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/repository/ProcessRepository.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/process/repository/ProcessRepository.java @@ -1,7 +1,9 @@ package cz.muni.pa165.banking.domain.process.repository; import cz.muni.pa165.banking.domain.process.Process; +import cz.muni.pa165.banking.domain.process.status.Status; +import java.util.List; import java.util.UUID; public interface ProcessRepository { @@ -12,4 +14,7 @@ public interface ProcessRepository { void save(Process process); + List<Process> findProcessOfStatus(Status status); + + Integer invalidateStaleProcesses(); } diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/transaction/Transaction.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/transaction/Transaction.java index 35a7d17..6b0ed78 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/transaction/Transaction.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/domain/transaction/Transaction.java @@ -20,7 +20,8 @@ public class Transaction { // Hibernate } - public Transaction(Account source, Account target, TransactionType type, Money amount, String detail) { + public Transaction(Account source, Account target, + TransactionType type, Money amount, String detail) { this.source = source; this.target = target; this.type = type; diff --git a/transaction-processor/src/main/resources/application.yaml b/transaction-processor/src/main/resources/application.yaml index 5ab2509..15fbc4b 100644 --- a/transaction-processor/src/main/resources/application.yaml +++ b/transaction-processor/src/main/resources/application.yaml @@ -1,6 +1,9 @@ db: hostname: localhost - +scheduled-payments: + cron: + expression: "00 7 * * *" + banking: apps: management: diff --git a/transaction-processor/src/test/java/cz/muni/pa165/banking/domain/process/ProcessMock.java b/transaction-processor/src/test/java/cz/muni/pa165/banking/domain/process/ProcessMock.java index 8cbf9de..9076a23 100644 --- a/transaction-processor/src/test/java/cz/muni/pa165/banking/domain/process/ProcessMock.java +++ b/transaction-processor/src/test/java/cz/muni/pa165/banking/domain/process/ProcessMock.java @@ -1,9 +1,18 @@ package cz.muni.pa165.banking.domain.process; +import java.util.UUID; + public class ProcessMock extends Process { + private UUID uuid; + public ProcessMock() { super(); + this.uuid = UUID.randomUUID(); } + @Override + public UUID getUuid() { + return this.uuid; + } } diff --git a/transaction-processor/src/test/java/cz/muni/pa165/banking/domain/process/ProcessOperationsTest.java b/transaction-processor/src/test/java/cz/muni/pa165/banking/domain/process/ProcessOperationsTest.java index 7875ab1..61593a1 100644 --- a/transaction-processor/src/test/java/cz/muni/pa165/banking/domain/process/ProcessOperationsTest.java +++ b/transaction-processor/src/test/java/cz/muni/pa165/banking/domain/process/ProcessOperationsTest.java @@ -12,7 +12,7 @@ class ProcessOperationsTest { @Test void changeState() { - Process process = new Process(); + Process process = Process.createNew(); StatusInformation newStatus = new StatusInformation( Instant.now(), -- GitLab