diff --git a/bpmn-process/src/.camunda/element-templates/importer.json b/bpmn-process/src/.camunda/element-templates/importer.json index 688d7c15..43dbef8e 100644 --- a/bpmn-process/src/.camunda/element-templates/importer.json +++ b/bpmn-process/src/.camunda/element-templates/importer.json @@ -294,5 +294,49 @@ } } ] + }, + { + "$schema": "https://unpkg.com/@camunda/element-templates-json-schema/resources/schema.json", + "name": "Importer: Add account in mapping", + "description": "Add the account id in the mapping configuration.", + "id": "com.jongsoft.finance.bpmn.delegate.importer.AddToAccountMapping", + "version": 1, + "appliesTo": [ + "bpmn:ServiceTask" + ], + "properties": [ + { + "label": "Implementation", + "type": "String", + "value": "${addToAccountMapping}", + "editable": false, + "binding": { + "type": "property", + "name": "camunda:delegateExpression" + } + }, + { + "label": "Account name", + "type": "String", + "binding": { + "type": "camunda:inputParameter", + "name": "name" + }, + "constraint": { + "notEmpty": true + } + }, + { + "label": "Account id", + "type": "String", + "binding": { + "type": "camunda:inputParameter", + "name": "accountId" + }, + "constraint": { + "notEmpty": true + } + } + ] } ] \ No newline at end of file diff --git a/bpmn-process/src/main/java/com/jongsoft/finance/bpmn/delegate/importer/AddToAccountMapping.java b/bpmn-process/src/main/java/com/jongsoft/finance/bpmn/delegate/importer/AddToAccountMapping.java new file mode 100644 index 00000000..40ca2272 --- /dev/null +++ b/bpmn-process/src/main/java/com/jongsoft/finance/bpmn/delegate/importer/AddToAccountMapping.java @@ -0,0 +1,32 @@ +package com.jongsoft.finance.bpmn.delegate.importer; + +import com.jongsoft.finance.core.JavaBean; +import jakarta.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.JavaDelegate; + +import java.util.Collection; +import java.util.HashSet; + +@Slf4j +@Singleton +public class AddToAccountMapping implements JavaDelegate, JavaBean { + @Override + public void execute(DelegateExecution execution) throws Exception { + var accountName = (String) execution.getVariableLocal("name"); + var accountId = (Number) execution.getVariableLocal("accountId"); + + log.debug("{}: Adding account mapping for '{}' with id {}.", + execution.getCurrentActivityName(), + accountName, + accountId); + + @SuppressWarnings("unchecked") + var mappings = new HashSet<>((Collection)execution.getVariable("accountMappings")); + mappings.removeIf(mapping -> mapping.getName().equals(accountName)); + mappings.add(new ExtractionMapping(accountName, accountId.longValue())); + + execution.setVariable("accountMappings", mappings); + } +} diff --git a/bpmn-process/src/main/java/com/jongsoft/finance/bpmn/delegate/transaction/CreateTransactionDelegate.java b/bpmn-process/src/main/java/com/jongsoft/finance/bpmn/delegate/transaction/CreateTransactionDelegate.java index 8a3512e5..42fcb83f 100644 --- a/bpmn-process/src/main/java/com/jongsoft/finance/bpmn/delegate/transaction/CreateTransactionDelegate.java +++ b/bpmn-process/src/main/java/com/jongsoft/finance/bpmn/delegate/transaction/CreateTransactionDelegate.java @@ -13,7 +13,6 @@ import lombok.extern.slf4j.Slf4j; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.camunda.bpm.engine.delegate.JavaDelegate; -import org.camunda.bpm.engine.variable.value.LongValue; /** * Delegate for creating a transaction in the system. @@ -85,8 +84,8 @@ public void execute(DelegateExecution execution) throws Exception { } private Account lookupAccount(DelegateExecution execution, String variableName) { - var accountId = execution.getVariableLocalTyped(variableName).getValue(); - return accountProvider.lookup(accountId) + var accountId = (Number) execution.getVariableLocal(variableName); + return accountProvider.lookup(accountId.longValue()) .getOrThrow(() -> new IllegalStateException("Unable to find account with id " + accountId)); } diff --git a/bpmn-process/src/main/resources/bpmn/transaction/import-job.bpmn b/bpmn-process/src/main/resources/bpmn/transaction/import-job.bpmn index f05c414e..2c3ec946 100644 --- a/bpmn-process/src/main/resources/bpmn/transaction/import-job.bpmn +++ b/bpmn-process/src/main/resources/bpmn/transaction/import-job.bpmn @@ -1,6 +1,6 @@ - + Start the import job that belongs to an import entity. When calling the flow the following variables must be set: @@ -187,7 +187,7 @@ When calling the flow the following variables must be set: ${allowGenerate == true} - + Flow_01c6qb3 @@ -248,8 +248,8 @@ When calling the flow the following variables must be set: Flow_0b5s223 - Flow_15uycxp Flow_1lz7czw + Flow_18qem83 Flow_01c6qb3 @@ -304,6 +304,17 @@ When calling the flow the following variables must be set: Flow_00vpxjj Flow_0rz5nze + + + + ${transaction.opposingName()} + ${accountId} + + + Flow_15uycxp + Flow_18qem83 + + @@ -358,12 +369,6 @@ When calling the flow the following variables must be set: - - - - - - @@ -451,7 +456,7 @@ When calling the flow the following variables must be set: - + @@ -464,9 +469,9 @@ When calling the flow the following variables must be set: - + - + @@ -479,7 +484,7 @@ When calling the flow the following variables must be set: - + @@ -494,18 +499,21 @@ When calling the flow the following variables must be set: - + - + - + + + + @@ -519,34 +527,32 @@ When calling the flow the following variables must be set: - + - + - - + + - + - - + + - + - - - - + + - - + + @@ -584,20 +590,31 @@ When calling the flow the following variables must be set: - - + + - - + + + + + + + + + + + + + diff --git a/bpmn-process/src/test/java/com/jongsoft/finance/bpmn/ImportJobIT.java b/bpmn-process/src/test/java/com/jongsoft/finance/bpmn/ImportJobIT.java index 01e23f2d..7fe08bf4 100644 --- a/bpmn-process/src/test/java/com/jongsoft/finance/bpmn/ImportJobIT.java +++ b/bpmn-process/src/test/java/com/jongsoft/finance/bpmn/ImportJobIT.java @@ -180,6 +180,14 @@ void runWithManualAccountCreate(RuntimeContext context) { process.task("user_create_account") .complete(Map.of("accountId", 4L)); + process.>yankVariable("accountMappings", mappings -> { + Assertions.assertThat(mappings) + .hasSize(3) + .anySatisfy(mapping -> { + Assertions.assertThat(mapping.getName()).isEqualTo("MW GA Pieterse"); + Assertions.assertThat(mapping.getAccountId()).isEqualTo(4L); + }); + }); context.verifyTransactions(assertion -> assertion.hasSize(4) .anySatisfy(this::verifyPostTransaction) .anySatisfy(this::verifyJanssenTransaction) diff --git a/fintrack-api/src/main/java/com/jongsoft/finance/filter/AuthenticationFilter.java b/fintrack-api/src/main/java/com/jongsoft/finance/filter/AuthenticationFilter.java index 25a8ff67..146a9c70 100644 --- a/fintrack-api/src/main/java/com/jongsoft/finance/filter/AuthenticationFilter.java +++ b/fintrack-api/src/main/java/com/jongsoft/finance/filter/AuthenticationFilter.java @@ -57,7 +57,11 @@ public Publisher> doFilter(final HttpRequest request, .recover(Throwable::getMessage).get()) .log("{}: {} in {} ms, with request body {}."); } else { - log.info("{}: {} in {} ms", request.getMethod(), request.getPath(), Duration.between(startTime, Instant.now()).toMillis()); + log.info("{}: {} in {} ms - Status Code {}.", + request.getMethod(), + request.getPath(), + Duration.between(startTime, Instant.now()).toMillis(), + response.status()); } } }); diff --git a/fintrack-api/src/main/java/com/jongsoft/finance/rest/importer/ImporterTransactionResource.java b/fintrack-api/src/main/java/com/jongsoft/finance/rest/importer/ImporterTransactionResource.java index 25f6bcf5..d94eed68 100644 --- a/fintrack-api/src/main/java/com/jongsoft/finance/rest/importer/ImporterTransactionResource.java +++ b/fintrack-api/src/main/java/com/jongsoft/finance/rest/importer/ImporterTransactionResource.java @@ -6,6 +6,7 @@ import com.jongsoft.finance.providers.TransactionProvider; import com.jongsoft.finance.rest.model.ResultPageResponse; import com.jongsoft.finance.rest.model.TransactionResponse; +import com.jongsoft.finance.rest.process.RuntimeResource; import com.jongsoft.finance.security.AuthenticationRoles; import io.micronaut.http.HttpStatus; import io.micronaut.http.annotation.*; @@ -17,6 +18,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.util.Map; + @Tag(name = "Importer") @Secured(AuthenticationRoles.IS_AUTHENTICATED) @Controller("/api/import/{batchSlug}/transactions") @@ -26,13 +29,17 @@ public class ImporterTransactionResource { private final FilterFactory filterFactory; private final TransactionProvider transactionProvider; + private final RuntimeResource runtimeResource; + public ImporterTransactionResource( SettingProvider settingProvider, FilterFactory filterFactory, - TransactionProvider transactionProvider) { + TransactionProvider transactionProvider, + RuntimeResource runtimeResource) { this.settingProvider = settingProvider; this.filterFactory = filterFactory; this.transactionProvider = transactionProvider; + this.runtimeResource = runtimeResource; } @Post @@ -56,6 +63,28 @@ ResultPageResponse search( return new ResultPageResponse<>(response); } + @Post("/run-rule-automation") + @Status(HttpStatus.NO_CONTENT) + @Operation( + summary = "Run rule automation", + operationId = "runRuleAutomation", + description = "Run rule automation on transactions created by the importer job", + parameters = @Parameter(name = "batchSlug", in = ParameterIn.PATH, schema = @Schema(implementation = String.class)) + ) + void runRuleAutomation(@PathVariable String batchSlug) { + var searchFilter = filterFactory.transaction() + .importSlug(batchSlug) + .pageSize(Integer.MAX_VALUE) + .page(0); + + transactionProvider.lookup(searchFilter) + .content() + .map(Transaction::getId) + .forEach(transactionId -> runtimeResource.startProcess( + "analyzeRule", + Map.of("transactionId", transactionId))); + } + @Delete("/{transactionId}") @Status(HttpStatus.NO_CONTENT) @Post diff --git a/fintrack-api/src/main/resources/application.yml b/fintrack-api/src/main/resources/application.yml index 68c16d7b..2d25a03a 100644 --- a/fintrack-api/src/main/resources/application.yml +++ b/fintrack-api/src/main/resources/application.yml @@ -62,6 +62,9 @@ endpoints: health: enabled: true details-visible: anonymous + loggers: + enabled: true + write-sensitive: true jackson: serialization-inclusion: non_absent diff --git a/fintrack-api/src/main/resources/i18n/messages.properties b/fintrack-api/src/main/resources/i18n/messages.properties index 75cff2be..ede09074 100644 --- a/fintrack-api/src/main/resources/i18n/messages.properties +++ b/fintrack-api/src/main/resources/i18n/messages.properties @@ -420,7 +420,8 @@ page.reports.default.top.debit=Revenue / income page.setting.overview.setting.AnalysisBudgetDeviation=Deviation budget allowed page.setting.overview.setting.AnalysisBudgetMonths=Months for budget page.setting.overview.setting.AutocompleteLimit=Hits in autocomplete -page.setting.overview.setting.RecordSetPageSize=Number of records per page +page.setting.overview.setting.ImportOutdated= +page.setting.overview.setting.RecordSetPageSize = Number of records per page page.setting.overview.setting.RegistrationOpen=Allow registrations page.settings.categories.add=Add new category page.settings.category.help=Transaction categorization aids the user to categorize or split (more than one category) a particular transaction, which will be used for expense and budget analysis. @@ -548,6 +549,7 @@ page.user.profile.currency.error=Could not update the default currency. page.user.profile.currency.success=The default currency was updated successfully. page.user.profile.export=Download configuration page.user.profile.import=Import configuration +page.user.profile.import.account.lookup=Lookup existing account page.user.profile.import.error=The importing of the user profile information failed. page.user.profile.import.explain=An import allows you to quicly restore a backup of your user account. It will not remove any existing content.
This will import the following
  • accounts
  • categories
  • rules
page.user.profile.import.success=The importing of the user profile information succeeded. @@ -583,4 +585,6 @@ validation.budget.already.closed=The budget period is already closed and cannot validation.budget.expense.add.budget.closed=Cannot add a new expense to an already closed budget period. validation.budget.expense.exceeds.income=The expenses for the budget exceed the expected income. validation.budget.income.too.low=The provided income cannot be below 0. -validation.transaction.schedule.end.before.start=Start of the scheduled transaction cannot be greater than the end date. \ No newline at end of file +validation.transaction.schedule.end.before.start=Start of the scheduled transaction cannot be greater than the end date. +page.user.profile.import.account.lookup.info=During the import an account was found that is not mapped to an account in Pledger.io. Please either add it manually or correct the mapping below. +page.settings.import.details.transactions.rules.run=Run transaction rules \ No newline at end of file diff --git a/fintrack-api/src/main/resources/i18n/messages_nl.properties b/fintrack-api/src/main/resources/i18n/messages_nl.properties index 6f1261dc..9b241214 100644 --- a/fintrack-api/src/main/resources/i18n/messages_nl.properties +++ b/fintrack-api/src/main/resources/i18n/messages_nl.properties @@ -145,6 +145,7 @@ TransactionRule.restrictive.explain=Alle condities moeten voldoen om de regel ui UserAccount.password=Wachtwoord UserAccount.twofactor.secret=Verificatie code UserAccount.username=Gebruikersnaam +bpmn.transaction.import.no-accounts-found=Er zijn geen transacties gevonden tijdens de importeer actie. common.account.balance=Saldo common.account.saldo=Huidige saldo common.action.cancel=Annuleer @@ -419,7 +420,8 @@ page.reports.default.top.debit=Inkomsten page.setting.overview.setting.AnalysisBudgetDeviation=Toegestane afwijking van budget page.setting.overview.setting.AnalysisBudgetMonths=Aantal maanden geanalyseerd page.setting.overview.setting.AutocompleteLimit=Aantal resultaten in autocomplete -page.setting.overview.setting.RecordSetPageSize=Aantal rijen per pagina +page.setting.overview.setting.ImportOutdated= +page.setting.overview.setting.RecordSetPageSize = Aantal rijen per pagina page.setting.overview.setting.RegistrationOpen=Sta registraties toe page.settings.categories.add=Voeg categorie toe page.settings.category.help=Een categorie helpt de gebruiker om een bepaalde transactie te categoriseren of te splitsen (meer dan \u00e9\u00e9n categorie), deze wordt gebruikt voor kosten- en budgetanalyse. @@ -547,6 +549,7 @@ page.user.profile.currency.error=De standaard valuta kon niet gewijzigd worden. page.user.profile.currency.success=De standaard valuta is succesvol bijgwerkt. page.user.profile.export=Download configuratie page.user.profile.import=Importeer configuratie +page.user.profile.import.account.lookup=Zoek een bestaande account op page.user.profile.import.error=Het importeren van het profiel is afgerond. page.user.profile.import.explain=Door middel van het importeren kun je snel je gegevens in Pledger.io in laden. Deze actie zal geen bestaande gegevens verwijderen.
De volgende zaken zullen worden ge\u00efmporteerd:
  • rekeningen, zowel de eigen rekeningen als wel de crediteuren en debiteuren
  • categorie\u00ebn
  • transactie regels
page.user.profile.import.success=Het importeren van het profiel is mislukt. @@ -576,10 +579,11 @@ user.password.NotEmpty=Een wachtwoord is verplicht user.password.TooShort=Wachtwoord dient minimaal 6 karakters te zijn user.username=Gebruiker user.username.AlreadyExists=Gebruikersnaam is al in gebruik -user.username.NotEmpty="Username cannot be empty" -user.username.TooShort=Username should be at least 6 characters -validation.budget.already.closed=The budget period is already closed and cannot be closed again. -validation.budget.expense.add.budget.closed=Cannot add a new expense to an already closed budget period. -validation.budget.expense.exceeds.income=The expenses for the budget exceed the expected income. -validation.budget.income.too.low=The provided income cannot be below 0. -validation.transaction.schedule.end.before.start=Start of the scheduled transaction cannot be greater than the end date. \ No newline at end of file +user.username.NotEmpty="Gebruikersnaam kan niet leeg zijn" +user.username.TooShort=Gebruikersnaam dient minimaal 6 karakters te zijn +validation.budget.already.closed=De budget periode is reeds gesloten. +validation.budget.expense.add.budget.closed=Er kan geen verwachte uitgave toegevoegd worden aan een al gesloten budget periode. +validation.budget.expense.exceeds.income=De totale uitgaven zijn hoger dan het verwachte inkomen. +validation.budget.income.too.low=Het opgegeven inkomen kan niet onder 0 zijn. +validation.transaction.schedule.end.before.start=De einddatum van de geplande overschrijving kan niet voor de startdatum zijn. +page.settings.import.details.transactions.rules.run=Pas alle regels toe op de overschrijvingen \ No newline at end of file diff --git a/fintrack-api/src/main/resources/logback.xml b/fintrack-api/src/main/resources/logback.xml index 4e64b666..236037b5 100644 --- a/fintrack-api/src/main/resources/logback.xml +++ b/fintrack-api/src/main/resources/logback.xml @@ -9,8 +9,9 @@ - - + + + diff --git a/fintrack-api/src/test/java/com/jongsoft/finance/rest/importer/ImporterTransactionResourceTest.java b/fintrack-api/src/test/java/com/jongsoft/finance/rest/importer/ImporterTransactionResourceTest.java index d3acef75..1691c86a 100644 --- a/fintrack-api/src/test/java/com/jongsoft/finance/rest/importer/ImporterTransactionResourceTest.java +++ b/fintrack-api/src/test/java/com/jongsoft/finance/rest/importer/ImporterTransactionResourceTest.java @@ -5,11 +5,11 @@ import com.jongsoft.finance.domain.transaction.Transaction; import com.jongsoft.finance.providers.TransactionProvider; import com.jongsoft.finance.rest.TestSetup; +import com.jongsoft.finance.rest.process.RuntimeResource; import com.jongsoft.lang.Collections; import com.jongsoft.lang.Control; import io.micronaut.context.annotation.Replaces; import io.micronaut.test.annotation.MockBean; -import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import io.restassured.specification.RequestSpecification; import jakarta.inject.Inject; import org.hamcrest.Matchers; @@ -18,6 +18,7 @@ import org.mockito.Mockito; import java.time.LocalDate; +import java.util.Map; @DisplayName("Import transactions resource") class ImporterTransactionResourceTest extends TestSetup { @@ -25,42 +26,25 @@ class ImporterTransactionResourceTest extends TestSetup { @Inject private TransactionProvider transactionProvider; + @Inject + private RuntimeResource runtimeResource; + @Replaces @MockBean TransactionProvider transactionProvider() { return Mockito.mock(TransactionProvider.class); } + @Replaces + @MockBean + RuntimeResource runtimeResource() { + return Mockito.mock(RuntimeResource.class); + } + @Test @DisplayName("Search transactions by batch slug") void search(RequestSpecification spec) { - Mockito.when(transactionProvider.lookup(Mockito.any())) - .thenReturn(ResultPage.of( - Transaction.builder() - .id(1L) - .description("Sample transaction") - .category("Grocery") - .currency("EUR") - .budget("Household") - .date(LocalDate.of(2019, 1, 15)) - .transactions(Collections.List( - Transaction.Part.builder() - .id(1L) - .account(Account.builder() - .id(1L) - .name("To account") - .type("checking") - .currency("EUR") - .build()) - .amount(20.00D) - .build(), - Transaction.Part.builder() - .id(2L) - .account(Account.builder().id(2L).currency("EUR").type("debtor").name("From account").build()) - .amount(-20.00D) - .build() - )) - .build())); + prepareTransactionsIntoMock(); // @formatter:off spec @@ -80,6 +64,22 @@ void search(RequestSpecification spec) { Mockito.verify(transactionProvider).lookup(Mockito.any()); } + @Test + @DisplayName("Run transaction rules") + void runTransactionRules(RequestSpecification spec) { + prepareTransactionsIntoMock(); + + // @formatter:off + spec + .when() + .post("/api/import/{batchSlug}/transactions/run-rule-automation", "ads-fasdfa-fasd") + .then() + .statusCode(204); + // @formatter:on + + Mockito.verify(runtimeResource).startProcess("analyzeRule", Map.of("transactionId", 1L)); + } + @Test @DisplayName("Delete transaction attached to batch job") void delete(RequestSpecification spec) { @@ -100,4 +100,33 @@ void delete(RequestSpecification spec) { Mockito.verify(transaction).delete(); } + private void prepareTransactionsIntoMock() { + Mockito.when(transactionProvider.lookup(Mockito.any())) + .thenReturn(ResultPage.of( + Transaction.builder() + .id(1L) + .description("Sample transaction") + .category("Grocery") + .currency("EUR") + .budget("Household") + .date(LocalDate.of(2019, 1, 15)) + .transactions(Collections.List( + Transaction.Part.builder() + .id(1L) + .account(Account.builder() + .id(1L) + .name("To account") + .type("checking") + .currency("EUR") + .build()) + .amount(20.00D) + .build(), + Transaction.Part.builder() + .id(2L) + .account(Account.builder().id(2L).currency("EUR").type("debtor").name("From account").build()) + .amount(-20.00D) + .build() + )) + .build())); + } }