This is an automated email from the ASF dual-hosted git repository. myrle pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract-cn-accounting.git
commit 6384b85d61fe9b1c4abb4b7f5a8d44d5bed1cf92 Author: mgeiss <[email protected]> AuthorDate: Tue Oct 10 11:16:08 2017 +0200 added income statement implementation added ledger total value --- .../accounting/api/v1/domain/IncomeStatement.java | 11 +- .../api/v1/domain/IncomeStatementSection.java | 17 ++- .../io/mifos/accounting/api/v1/domain/Ledger.java | 10 ++ .../io/mifos/accounting/TestIncomeStatement.java | 152 +++++++++++++++++++++ .../accounting/util/JournalEntryGenerator.java | 12 +- component-test/src/main/resources/logback-test.xml | 2 +- .../AddAmountToLedgerTotalCommand.java} | 26 ++-- .../command/handler/AccountCommandHandler.java | 18 +++ .../command/handler/LedgerCommandHandler.java | 29 +++- .../command/handler/MigrationCommandHandler.java | 53 ++++++- .../service/internal/mapper/LedgerMapper.java | 4 + .../internal/repository/AccountRepository.java | 3 + .../service/internal/repository/LedgerEntity.java | 11 ++ .../internal/repository/LedgerRepository.java | 2 + .../internal/service/IncomeStatementService.java | 81 +++++++++++ .../internal/service/TrialBalanceService.java | 57 ++++---- .../service/rest/IncomeStatementController.java | 53 +++++++ .../mariadb/V9__add_total_value_ledger.sql | 17 +++ 18 files changed, 496 insertions(+), 62 deletions(-) diff --git a/api/src/main/java/io/mifos/accounting/api/v1/domain/IncomeStatement.java b/api/src/main/java/io/mifos/accounting/api/v1/domain/IncomeStatement.java index a96c9ca..8ed5678 100644 --- a/api/src/main/java/io/mifos/accounting/api/v1/domain/IncomeStatement.java +++ b/api/src/main/java/io/mifos/accounting/api/v1/domain/IncomeStatement.java @@ -19,6 +19,7 @@ import org.hibernate.validator.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.List; public class IncomeStatement { @@ -26,7 +27,7 @@ public class IncomeStatement { @NotEmpty private String date; @NotEmpty - private List<IncomeStatementSection> incomeStatementSections; + private List<IncomeStatementSection> incomeStatementSections = new ArrayList<>(); @NotNull private BigDecimal grossProfit; @NotNull @@ -50,10 +51,6 @@ public class IncomeStatement { return this.incomeStatementSections; } - public void setIncomeStatementSections(final List<IncomeStatementSection> incomeStatementSections) { - this.incomeStatementSections = incomeStatementSections; - } - public BigDecimal getGrossProfit() { return this.grossProfit; } @@ -77,4 +74,8 @@ public class IncomeStatement { public void setNetIncome(final BigDecimal netIncome) { this.netIncome = netIncome; } + + public void add(final IncomeStatementSection incomeStatementSection) { + this.incomeStatementSections.add(incomeStatementSection); + } } diff --git a/api/src/main/java/io/mifos/accounting/api/v1/domain/IncomeStatementSection.java b/api/src/main/java/io/mifos/accounting/api/v1/domain/IncomeStatementSection.java index c1ba67d..cbe21f9 100644 --- a/api/src/main/java/io/mifos/accounting/api/v1/domain/IncomeStatementSection.java +++ b/api/src/main/java/io/mifos/accounting/api/v1/domain/IncomeStatementSection.java @@ -19,22 +19,24 @@ import org.hibernate.validator.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.List; public class IncomeStatementSection { - enum Type { + public enum Type { INCOME, EXPENSES } @NotEmpty private Type type; + @NotEmpty private String description; @NotEmpty - private List<IncomeStatementEntry> incomeStatementEntries; + private List<IncomeStatementEntry> incomeStatementEntries = new ArrayList<>(); @NotNull - private BigDecimal subtotal; + private BigDecimal subtotal = BigDecimal.ZERO; public IncomeStatementSection() { super(); @@ -60,15 +62,12 @@ public class IncomeStatementSection { return this.incomeStatementEntries; } - public void setIncomeStatementEntries(final List<IncomeStatementEntry> incomeStatementEntries) { - this.incomeStatementEntries = incomeStatementEntries; - } - public BigDecimal getSubtotal() { return this.subtotal; } - public void setSubtotal(final BigDecimal subtotal) { - this.subtotal = subtotal; + public void add(final IncomeStatementEntry incomeStatementEntry) { + this.incomeStatementEntries.add(incomeStatementEntry); + this.subtotal = this.subtotal.add(incomeStatementEntry.getValue()); } } diff --git a/api/src/main/java/io/mifos/accounting/api/v1/domain/Ledger.java b/api/src/main/java/io/mifos/accounting/api/v1/domain/Ledger.java index 3c58ad2..a9a08f3 100644 --- a/api/src/main/java/io/mifos/accounting/api/v1/domain/Ledger.java +++ b/api/src/main/java/io/mifos/accounting/api/v1/domain/Ledger.java @@ -20,6 +20,7 @@ import org.hibernate.validator.constraints.NotEmpty; import javax.validation.Valid; import javax.validation.constraints.NotNull; +import java.math.BigDecimal; import java.util.List; @SuppressWarnings({"unused"}) @@ -35,6 +36,7 @@ public final class Ledger { private String parentLedgerIdentifier; @Valid private List<Ledger> subLedgers; + private BigDecimal totalValue; private String createdOn; private String createdBy; private String lastModifiedOn; @@ -94,6 +96,14 @@ public final class Ledger { this.subLedgers = subLedgers; } + public BigDecimal getTotalValue() { + return this.totalValue; + } + + public void setTotalValue(final BigDecimal totalValue) { + this.totalValue = totalValue; + } + public String getCreatedOn() { return this.createdOn; } diff --git a/component-test/src/main/java/io/mifos/accounting/TestIncomeStatement.java b/component-test/src/main/java/io/mifos/accounting/TestIncomeStatement.java new file mode 100644 index 0000000..801c492 --- /dev/null +++ b/component-test/src/main/java/io/mifos/accounting/TestIncomeStatement.java @@ -0,0 +1,152 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.mifos.accounting; + +import io.mifos.accounting.api.v1.EventConstants; +import io.mifos.accounting.api.v1.domain.Account; +import io.mifos.accounting.api.v1.domain.AccountType; +import io.mifos.accounting.api.v1.domain.IncomeStatement; +import io.mifos.accounting.api.v1.domain.JournalEntry; +import io.mifos.accounting.api.v1.domain.Ledger; +import io.mifos.accounting.util.AccountGenerator; +import io.mifos.accounting.util.JournalEntryGenerator; +import io.mifos.accounting.util.LedgerGenerator; +import org.junit.Assert; +import org.junit.Test; + +import java.math.BigDecimal; + +public class TestIncomeStatement extends AbstractAccountingTest { + + public TestIncomeStatement() { + super(); + } + + @Test + public void shouldReturnIncomeStatement() throws Exception { + this.fixtures(); + this.sampleJournalEntries(); + + final BigDecimal expectedGrossProfit = BigDecimal.valueOf(350.00D); + final BigDecimal expectedTotalExpenses = BigDecimal.valueOf(125.00D); + final BigDecimal expectedNetIncome = expectedGrossProfit.subtract(expectedTotalExpenses); + + final IncomeStatement incomeStatement = super.testSubject.getIncomeStatement(); + Assert.assertTrue(incomeStatement.getGrossProfit().compareTo(expectedGrossProfit) == 0); + Assert.assertTrue(incomeStatement.getTotalExpenses().compareTo(expectedTotalExpenses) == 0); + Assert.assertTrue(incomeStatement.getNetIncome().compareTo(expectedNetIncome) == 0); + } + + private void fixtures() throws Exception { + final Ledger incomeLedger = LedgerGenerator.createLedger("1000", AccountType.REVENUE); + incomeLedger.setName("Income"); + super.testSubject.createLedger(incomeLedger); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, incomeLedger.getIdentifier())); + + final Ledger incomeSubLedger1100 = LedgerGenerator.createLedger("1100", AccountType.REVENUE); + incomeSubLedger1100.setParentLedgerIdentifier(incomeLedger.getParentLedgerIdentifier()); + incomeSubLedger1100.setName("Income From Loans"); + super.testSubject.addSubLedger(incomeLedger.getIdentifier(), incomeSubLedger1100); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, incomeSubLedger1100.getIdentifier())); + + final Ledger incomeSubLedger1300 = LedgerGenerator.createLedger("1300", AccountType.REVENUE); + incomeSubLedger1300.setParentLedgerIdentifier(incomeLedger.getParentLedgerIdentifier()); + incomeSubLedger1300.setName("Fees and Charges"); + super.testSubject.addSubLedger(incomeLedger.getIdentifier(), incomeSubLedger1300); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, incomeSubLedger1300.getIdentifier())); + + final Account account1110 = + AccountGenerator.createAccount(incomeSubLedger1100.getIdentifier(), "1110", AccountType.REVENUE); + super.testSubject.createAccount(account1110); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, account1110.getIdentifier())); + + final Account account1310 = + AccountGenerator.createAccount(incomeSubLedger1300.getIdentifier(), "1310", AccountType.REVENUE); + super.testSubject.createAccount(account1310); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, account1310.getIdentifier())); + + final Ledger expenseLedger = LedgerGenerator.createLedger("3000", AccountType.EXPENSE); + expenseLedger.setName("Expenses"); + super.testSubject.createLedger(expenseLedger); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, expenseLedger.getIdentifier())); + + final Ledger expenseSubLedger3500 = LedgerGenerator.createLedger("3500", AccountType.EXPENSE); + expenseSubLedger3500.setParentLedgerIdentifier(expenseLedger.getParentLedgerIdentifier()); + expenseSubLedger3500.setName("Annual Meeting Expenses"); + super.testSubject.addSubLedger(expenseLedger.getIdentifier(), expenseSubLedger3500); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, expenseSubLedger3500.getIdentifier())); + + final Ledger expenseSubLedger3700 = LedgerGenerator.createLedger("3700", AccountType.EXPENSE); + expenseSubLedger3700.setParentLedgerIdentifier(expenseLedger.getParentLedgerIdentifier()); + expenseSubLedger3700.setName("Interest (Dividend) Expense"); + super.testSubject.addSubLedger(expenseLedger.getIdentifier(), expenseSubLedger3700); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, expenseSubLedger3700.getIdentifier())); + + final Account account3510 = + AccountGenerator.createAccount(expenseSubLedger3500.getIdentifier(), "3510", AccountType.EXPENSE); + super.testSubject.createAccount(account3510); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, account3510.getIdentifier())); + + final Account account3710 = + AccountGenerator.createAccount(expenseSubLedger3700.getIdentifier(), "3710", AccountType.EXPENSE); + super.testSubject.createAccount(account3710); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, account3710.getIdentifier())); + + final Ledger assetLedger = LedgerGenerator.createLedger("7000", AccountType.ASSET); + super.testSubject.createLedger(assetLedger); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, assetLedger.getIdentifier())); + + final Account assetAccount = + AccountGenerator.createAccount(assetLedger.getIdentifier(), "7010", AccountType.ASSET); + super.testSubject.createAccount(assetAccount); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, assetAccount.getIdentifier())); + + final Ledger liabilityLedger = LedgerGenerator.createLedger("8000", AccountType.LIABILITY); + super.testSubject.createLedger(liabilityLedger); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_LEDGER, liabilityLedger.getIdentifier())); + + final Account liabilityAccount = + AccountGenerator.createAccount(liabilityLedger.getIdentifier(), "8010", AccountType.LIABILITY); + super.testSubject.createAccount(liabilityAccount); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_ACCOUNT, liabilityAccount.getIdentifier())); + } + + private void sampleJournalEntries() throws Exception { + final JournalEntry firstTransaction = + JournalEntryGenerator + .createRandomJournalEntry("7010", "150.00", "1110", "150.00"); + super.testSubject.createJournalEntry(firstTransaction); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_JOURNAL_ENTRY, firstTransaction.getTransactionIdentifier())); + + final JournalEntry secondTransaction = + JournalEntryGenerator + .createRandomJournalEntry("7010", "200.00", "1310", "200.00"); + super.testSubject.createJournalEntry(secondTransaction); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_JOURNAL_ENTRY, secondTransaction.getTransactionIdentifier())); + + final JournalEntry thirdTransaction = + JournalEntryGenerator + .createRandomJournalEntry("3510", "50.00", "8010", "50.00"); + super.testSubject.createJournalEntry(thirdTransaction); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_JOURNAL_ENTRY, thirdTransaction.getTransactionIdentifier())); + + final JournalEntry fourthTransaction = + JournalEntryGenerator + .createRandomJournalEntry("3710", "75.00", "8010", "75.00"); + super.testSubject.createJournalEntry(fourthTransaction); + Assert.assertTrue(super.eventRecorder.wait(EventConstants.POST_JOURNAL_ENTRY, fourthTransaction.getTransactionIdentifier())); + } +} diff --git a/component-test/src/main/java/io/mifos/accounting/util/JournalEntryGenerator.java b/component-test/src/main/java/io/mifos/accounting/util/JournalEntryGenerator.java index 1a3a4e5..b86f344 100644 --- a/component-test/src/main/java/io/mifos/accounting/util/JournalEntryGenerator.java +++ b/component-test/src/main/java/io/mifos/accounting/util/JournalEntryGenerator.java @@ -38,6 +38,14 @@ public class JournalEntryGenerator { final String debtorAmount, final Account creditorAccount, final String creditorAmount) { + return JournalEntryGenerator + .createRandomJournalEntry(debtorAccount.getIdentifier(), debtorAmount, creditorAccount.getIdentifier(), creditorAmount); + } + + public static JournalEntry createRandomJournalEntry(final String debtorAccount, + final String debtorAmount, + final String creditorAccount, + final String creditorAmount) { final JournalEntry journalEntry = new JournalEntry(); journalEntry.setTransactionIdentifier(RandomStringUtils.randomAlphanumeric(8)); journalEntry.setTransactionDate(ZonedDateTime.now(Clock.systemUTC()).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); @@ -45,7 +53,7 @@ public class JournalEntryGenerator { journalEntry.setClerk("clark"); if (debtorAccount != null) { final Debtor debtor = new Debtor(); - debtor.setAccountNumber(debtorAccount.getIdentifier()); + debtor.setAccountNumber(debtorAccount); debtor.setAmount(debtorAmount); journalEntry.setDebtors(new HashSet<>(Collections.singletonList(debtor))); } else { @@ -53,7 +61,7 @@ public class JournalEntryGenerator { } if (creditorAccount != null) { final Creditor creditor = new Creditor(); - creditor.setAccountNumber(creditorAccount.getIdentifier()); + creditor.setAccountNumber(creditorAccount); creditor.setAmount(creditorAmount); journalEntry.setCreditors(new HashSet<>(Collections.singletonList(creditor))); } else { diff --git a/component-test/src/main/resources/logback-test.xml b/component-test/src/main/resources/logback-test.xml index ffad21f..735b8e5 100644 --- a/component-test/src/main/resources/logback-test.xml +++ b/component-test/src/main/resources/logback-test.xml @@ -26,7 +26,7 @@ <logger name="com" level="OFF"/> <logger name="ch" level="OFF"/> - <root level="INFO"> + <root level="DEBUG"> <appender-ref ref="STDOUT"/> </root> </configuration> \ No newline at end of file diff --git a/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerRepository.java b/service/src/main/java/io/mifos/accounting/service/internal/command/AddAmountToLedgerTotalCommand.java similarity index 50% copy from service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerRepository.java copy to service/src/main/java/io/mifos/accounting/service/internal/command/AddAmountToLedgerTotalCommand.java index e0edce3..771f384 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerRepository.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/command/AddAmountToLedgerTotalCommand.java @@ -13,20 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.mifos.accounting.service.internal.repository; +package io.mifos.accounting.service.internal.command; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.stereotype.Repository; +import java.math.BigDecimal; -import java.util.List; +public class AddAmountToLedgerTotalCommand { -@Repository -public interface LedgerRepository extends JpaRepository<LedgerEntity, Long>, JpaSpecificationExecutor<LedgerEntity> { + private final String ledgerIdentifier; + private final BigDecimal amount; - List<LedgerEntity> findByParentLedgerIsNull(); + public AddAmountToLedgerTotalCommand(final String ledgerIdentifier, final BigDecimal amount) { + super(); + this.ledgerIdentifier = ledgerIdentifier; + this.amount = amount; + } - List<LedgerEntity> findByParentLedgerOrderByIdentifier(final LedgerEntity parentLedger); + public String ledgerIdentifier() { + return this.ledgerIdentifier; + } - LedgerEntity findByIdentifier(final String identifier); + public BigDecimal amount() { + return this.amount; + } } diff --git a/service/src/main/java/io/mifos/accounting/service/internal/command/handler/AccountCommandHandler.java b/service/src/main/java/io/mifos/accounting/service/internal/command/handler/AccountCommandHandler.java index f0bab24..8c30e02 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/command/handler/AccountCommandHandler.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/command/handler/AccountCommandHandler.java @@ -21,6 +21,7 @@ import io.mifos.accounting.api.v1.domain.AccountCommand; import io.mifos.accounting.api.v1.domain.AccountEntry; import io.mifos.accounting.api.v1.domain.AccountType; import io.mifos.accounting.api.v1.domain.JournalEntry; +import io.mifos.accounting.service.internal.command.AddAmountToLedgerTotalCommand; import io.mifos.accounting.service.internal.command.BookJournalEntryCommand; import io.mifos.accounting.service.internal.command.CloseAccountCommand; import io.mifos.accounting.service.internal.command.CreateAccountCommand; @@ -51,6 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.time.Clock; import java.time.LocalDateTime; import java.util.List; @@ -326,16 +328,21 @@ public class AccountCommandHandler { final String accountNumber = debtor.getAccountNumber(); final AccountEntity accountEntity = this.accountRepository.findByIdentifier(accountNumber); final AccountType accountType = AccountType.valueOf(accountEntity.getType()); + final BigDecimal amount; switch (accountType) { case ASSET: case EXPENSE: accountEntity.setBalance(accountEntity.getBalance() + debtor.getAmount()); + amount = BigDecimal.valueOf(debtor.getAmount()); break; case LIABILITY: case EQUITY: case REVENUE: accountEntity.setBalance(accountEntity.getBalance() - debtor.getAmount()); + amount = BigDecimal.valueOf(debtor.getAmount()).negate(); break; + default: + amount = BigDecimal.ZERO; } final AccountEntity savedAccountEntity = this.accountRepository.save(accountEntity); final AccountEntryEntity accountEntryEntity = new AccountEntryEntity(); @@ -346,6 +353,9 @@ public class AccountCommandHandler { accountEntryEntity.setMessage(journalEntryEntity.getMessage()); accountEntryEntity.setTransactionDate(journalEntryEntity.getTransactionDate()); this.accountEntryRepository.save(accountEntryEntity); + this.commandGateway.process( + new AddAmountToLedgerTotalCommand(savedAccountEntity.getLedger().getIdentifier(), amount) + ); }); // process all creditors journalEntryEntity.getCreditors() @@ -353,16 +363,21 @@ public class AccountCommandHandler { final String accountNumber = creditor.getAccountNumber(); final AccountEntity accountEntity = this.accountRepository.findByIdentifier(accountNumber); final AccountType accountType = AccountType.valueOf(accountEntity.getType()); + final BigDecimal amount; switch (accountType) { case ASSET: case EXPENSE: accountEntity.setBalance(accountEntity.getBalance() - creditor.getAmount()); + amount = BigDecimal.valueOf(creditor.getAmount()).negate(); break; case LIABILITY: case EQUITY: case REVENUE: accountEntity.setBalance(accountEntity.getBalance() + creditor.getAmount()); + amount = BigDecimal.valueOf(creditor.getAmount()); break; + default: + amount = BigDecimal.ZERO; } final AccountEntity savedAccountEntity = this.accountRepository.save(accountEntity); final AccountEntryEntity accountEntryEntity = new AccountEntryEntity(); @@ -373,6 +388,9 @@ public class AccountCommandHandler { accountEntryEntity.setMessage(journalEntryEntity.getMessage()); accountEntryEntity.setTransactionDate(journalEntryEntity.getTransactionDate()); this.accountEntryRepository.save(accountEntryEntity); + this.commandGateway.process( + new AddAmountToLedgerTotalCommand(savedAccountEntity.getLedger().getIdentifier(), amount) + ); }); this.commandGateway.process(new ReleaseJournalEntryCommand(transactionIdentifier)); return transactionIdentifier; diff --git a/service/src/main/java/io/mifos/accounting/service/internal/command/handler/LedgerCommandHandler.java b/service/src/main/java/io/mifos/accounting/service/internal/command/handler/LedgerCommandHandler.java index fe88d88..4d96c8e 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/command/handler/LedgerCommandHandler.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/command/handler/LedgerCommandHandler.java @@ -18,6 +18,7 @@ package io.mifos.accounting.service.internal.command.handler; import io.mifos.accounting.api.v1.EventConstants; import io.mifos.accounting.api.v1.domain.Ledger; import io.mifos.accounting.service.ServiceConstants; +import io.mifos.accounting.service.internal.command.AddAmountToLedgerTotalCommand; import io.mifos.accounting.service.internal.command.AddSubLedgerCommand; import io.mifos.accounting.service.internal.command.CreateLedgerCommand; import io.mifos.accounting.service.internal.command.DeleteLedgerCommand; @@ -29,12 +30,14 @@ import io.mifos.core.command.annotation.Aggregate; import io.mifos.core.command.annotation.CommandHandler; import io.mifos.core.command.annotation.CommandLogLevel; import io.mifos.core.command.annotation.EventEmitter; +import io.mifos.core.command.gateway.CommandGateway; import io.mifos.core.lang.ServiceException; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.time.Clock; import java.time.LocalDateTime; import java.util.Collections; @@ -46,13 +49,16 @@ public class LedgerCommandHandler { private final Logger logger; private final LedgerRepository ledgerRepository; + private final CommandGateway commandGateway; @Autowired public LedgerCommandHandler(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger, - final LedgerRepository ledgerRepository) { + final LedgerRepository ledgerRepository, + final CommandGateway commandGateway) { super(); this.logger = logger; this.ledgerRepository = ledgerRepository; + this.commandGateway = commandGateway; } @Transactional @@ -164,4 +170,25 @@ public class LedgerCommandHandler { } } } + + @Transactional + @CommandHandler + @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.PUT_LEDGER) + public String process(final AddAmountToLedgerTotalCommand addAmountToLedgerTotalCommand) { + final BigDecimal amount = addAmountToLedgerTotalCommand.amount(); + if (amount.compareTo(BigDecimal.ZERO) != 0) { + final LedgerEntity ledger = this.ledgerRepository.findByIdentifier(addAmountToLedgerTotalCommand.ledgerIdentifier()); + final BigDecimal currentTotal = ledger.getTotalValue() != null ? ledger.getTotalValue() : BigDecimal.ZERO; + ledger.setTotalValue(currentTotal.add(amount)); + final LedgerEntity savedLedger = this.ledgerRepository.save(ledger); + if (savedLedger.getParentLedger() != null) { + this.commandGateway.process( + new AddAmountToLedgerTotalCommand(savedLedger.getParentLedger().getIdentifier(), amount) + ); + } + return savedLedger.getIdentifier(); + } else { + return null; + } + } } diff --git a/service/src/main/java/io/mifos/accounting/service/internal/command/handler/MigrationCommandHandler.java b/service/src/main/java/io/mifos/accounting/service/internal/command/handler/MigrationCommandHandler.java index 82a0943..0c497bf 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/command/handler/MigrationCommandHandler.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/command/handler/MigrationCommandHandler.java @@ -18,7 +18,12 @@ package io.mifos.accounting.service.internal.command.handler; import com.datastax.driver.core.DataType; import com.datastax.driver.core.schemabuilder.SchemaBuilder; import io.mifos.accounting.api.v1.EventConstants; +import io.mifos.accounting.service.ServiceConstants; +import io.mifos.accounting.service.internal.command.AddAmountToLedgerTotalCommand; import io.mifos.accounting.service.internal.command.InitializeServiceCommand; +import io.mifos.accounting.service.internal.repository.AccountEntity; +import io.mifos.accounting.service.internal.repository.AccountRepository; +import io.mifos.accounting.service.internal.service.AccountService; import io.mifos.core.cassandra.core.CassandraJourney; import io.mifos.core.cassandra.core.CassandraJourneyFactory; import io.mifos.core.cassandra.core.CassandraJourneyRoute; @@ -27,10 +32,20 @@ import io.mifos.core.command.annotation.Aggregate; import io.mifos.core.command.annotation.CommandHandler; import io.mifos.core.command.annotation.CommandLogLevel; import io.mifos.core.command.annotation.EventEmitter; +import io.mifos.core.command.gateway.CommandGateway; import io.mifos.core.mariadb.domain.FlywayFactoryBean; +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.MigrationInfo; +import org.flywaydb.core.api.MigrationInfoService; +import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import javax.sql.DataSource; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; @SuppressWarnings({ "unused" @@ -38,28 +53,45 @@ import javax.sql.DataSource; @Aggregate public class MigrationCommandHandler { + private final Logger logger; private final DataSource dataSource; private final FlywayFactoryBean flywayFactoryBean; private final CassandraSessionProvider cassandraSessionProvider; private final CassandraJourneyFactory cassandraJourneyFactory; + private final CommandGateway commandGateway; + private final AccountRepository accountRepository; @SuppressWarnings("SpringJavaAutowiringInspection") @Autowired - public MigrationCommandHandler(final DataSource dataSource, + public MigrationCommandHandler(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger, + final DataSource dataSource, final FlywayFactoryBean flywayFactoryBean, final CassandraSessionProvider cassandraSessionProvider, - final CassandraJourneyFactory cassandraJourneyFactory) { + final CassandraJourneyFactory cassandraJourneyFactory, + final CommandGateway commandGateway, + final AccountRepository accountRepository) { super(); + this.logger = logger; this.dataSource = dataSource; this.flywayFactoryBean = flywayFactoryBean; this.cassandraSessionProvider = cassandraSessionProvider; this.cassandraJourneyFactory = cassandraJourneyFactory; + this.commandGateway = commandGateway; + this.accountRepository = accountRepository; } @CommandHandler(logStart = CommandLogLevel.DEBUG, logFinish = CommandLogLevel.DEBUG) @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.INITIALIZE) public String initialize(final InitializeServiceCommand initializeServiceCommand) { - this.flywayFactoryBean.create(this.dataSource).migrate(); + final Flyway flyway = this.flywayFactoryBean.create(this.dataSource); + + final MigrationInfoService migrationInfoService = flyway.info(); + final List<MigrationInfo> migrationInfoList = Arrays.asList(migrationInfoService.applied()); + final boolean shouldMigrateLedgerTotals = migrationInfoList + .stream() + .noneMatch(migrationInfo -> migrationInfo.getVersion().getVersion().equals("9")); + + flyway.migrate(); final String versionNumber = "1"; @@ -129,6 +161,21 @@ public class MigrationCommandHandler { cassandraJourney.start(updateRouteVersion2); cassandraJourney.start(updateRouteVersion3); + if (shouldMigrateLedgerTotals) { + this.migrateLedgerTotals(); + } + return versionNumber; } + + private void migrateLedgerTotals() { + this.logger.info("Start ledger total migration ..."); + try (final Stream<AccountEntity> accountEntityStream = this.accountRepository.findByBalanceIsNot(0.00D)) { + accountEntityStream.forEach(accountEntity -> + this.commandGateway.process( + new AddAmountToLedgerTotalCommand(accountEntity.getLedger().getIdentifier(), BigDecimal.valueOf(accountEntity.getBalance())) + ) + ); + } + } } \ No newline at end of file diff --git a/service/src/main/java/io/mifos/accounting/service/internal/mapper/LedgerMapper.java b/service/src/main/java/io/mifos/accounting/service/internal/mapper/LedgerMapper.java index 001cf4d..9971824 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/mapper/LedgerMapper.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/mapper/LedgerMapper.java @@ -19,6 +19,8 @@ import io.mifos.accounting.api.v1.domain.Ledger; import io.mifos.accounting.service.internal.repository.LedgerEntity; import io.mifos.core.lang.DateConverter; +import java.math.BigDecimal; + public class LedgerMapper { private LedgerMapper() { @@ -41,6 +43,8 @@ public class LedgerMapper { ledger.setLastModifiedOn(DateConverter.toIsoString(ledgerEntity.getLastModifiedOn())); } ledger.setShowAccountsInChart(ledgerEntity.getShowAccountsInChart()); + final BigDecimal totalValue = ledgerEntity.getTotalValue() != null ? ledgerEntity.getTotalValue() : BigDecimal.ZERO; + ledger.setTotalValue(totalValue); return ledger; } } diff --git a/service/src/main/java/io/mifos/accounting/service/internal/repository/AccountRepository.java b/service/src/main/java/io/mifos/accounting/service/internal/repository/AccountRepository.java index e83a7ec..e499d2c 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/repository/AccountRepository.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/repository/AccountRepository.java @@ -24,6 +24,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; +import java.util.stream.Stream; @Repository public interface AccountRepository extends JpaRepository<AccountEntity, Long>, JpaSpecificationExecutor<AccountEntity> { @@ -36,4 +37,6 @@ public interface AccountRepository extends JpaRepository<AccountEntity, Long>, J @Query("SELECT CASE WHEN count(a) > 0 THEN true ELSE false END FROM AccountEntity a where a.referenceAccount = :accountEntity") Boolean existsByReference(@Param("accountEntity") final AccountEntity accountEntity); + + Stream<AccountEntity> findByBalanceIsNot(final Double value); } diff --git a/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerEntity.java b/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerEntity.java index 4ee0a9f..bdfc7e8 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerEntity.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerEntity.java @@ -27,6 +27,7 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import java.math.BigDecimal; import java.time.LocalDateTime; @SuppressWarnings({"unused"}) @@ -49,6 +50,8 @@ public class LedgerEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_ledger_id") private LedgerEntity parentLedger; + @Column(name = "total_value") + private BigDecimal totalValue; @Column(name = "created_on") @Convert(converter = LocalDateTimeConverter.class) private LocalDateTime createdOn; @@ -114,6 +117,14 @@ public class LedgerEntity { this.parentLedger = parentLedger; } + public BigDecimal getTotalValue() { + return this.totalValue; + } + + public void setTotalValue(final BigDecimal totalValue) { + this.totalValue = totalValue; + } + public LocalDateTime getCreatedOn() { return this.createdOn; } diff --git a/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerRepository.java b/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerRepository.java index e0edce3..e310ecd 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerRepository.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/repository/LedgerRepository.java @@ -26,6 +26,8 @@ public interface LedgerRepository extends JpaRepository<LedgerEntity, Long>, Jpa List<LedgerEntity> findByParentLedgerIsNull(); + List<LedgerEntity> findByParentLedgerIsNullAndType(final String type); + List<LedgerEntity> findByParentLedgerOrderByIdentifier(final LedgerEntity parentLedger); LedgerEntity findByIdentifier(final String identifier); diff --git a/service/src/main/java/io/mifos/accounting/service/internal/service/IncomeStatementService.java b/service/src/main/java/io/mifos/accounting/service/internal/service/IncomeStatementService.java new file mode 100644 index 0000000..b9de9f4 --- /dev/null +++ b/service/src/main/java/io/mifos/accounting/service/internal/service/IncomeStatementService.java @@ -0,0 +1,81 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.mifos.accounting.service.internal.service; + +import io.mifos.accounting.api.v1.domain.AccountType; +import io.mifos.accounting.api.v1.domain.IncomeStatement; +import io.mifos.accounting.api.v1.domain.IncomeStatementEntry; +import io.mifos.accounting.api.v1.domain.IncomeStatementSection; +import io.mifos.accounting.service.internal.repository.LedgerRepository; +import io.mifos.core.lang.DateConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.Clock; +import java.time.LocalDateTime; + +@Service +public class IncomeStatementService { + + private final LedgerRepository ledgerRepository; + + @Autowired + public IncomeStatementService(final LedgerRepository ledgerRepository) { + super(); + this.ledgerRepository = ledgerRepository; + } + + public IncomeStatement getIncomeStatement() { + final IncomeStatement incomeStatement = new IncomeStatement(); + incomeStatement.setDate(DateConverter.toIsoString(LocalDateTime.now(Clock.systemUTC()))); + + this.createIncomeStatementSection(incomeStatement, AccountType.REVENUE, IncomeStatementSection.Type.INCOME); + this.createIncomeStatementSection(incomeStatement, AccountType.EXPENSE, IncomeStatementSection.Type.EXPENSES); + + incomeStatement.setGrossProfit(this.calculateTotal(incomeStatement, IncomeStatementSection.Type.INCOME)); + incomeStatement.setTotalExpenses(this.calculateTotal(incomeStatement, IncomeStatementSection.Type.EXPENSES)); + incomeStatement.setNetIncome(incomeStatement.getGrossProfit().subtract(incomeStatement.getTotalExpenses())); + + return incomeStatement; + } + + private void createIncomeStatementSection(final IncomeStatement incomeStatement, final AccountType accountType, + final IncomeStatementSection.Type incomeStatementType) { + this.ledgerRepository.findByParentLedgerIsNullAndType(accountType.name()).forEach(ledgerEntity -> { + final IncomeStatementSection incomeStatementSection = new IncomeStatementSection(); + incomeStatementSection.setType(incomeStatementType.name()); + incomeStatementSection.setDescription(ledgerEntity.getName()); + incomeStatement.add(incomeStatementSection); + + this.ledgerRepository.findByParentLedgerOrderByIdentifier(ledgerEntity).forEach(subLedgerEntity -> { + final IncomeStatementEntry incomeStatementEntry = new IncomeStatementEntry(); + incomeStatementEntry.setDescription(subLedgerEntity.getName()); + incomeStatementEntry.setValue(subLedgerEntity.getTotalValue()); + incomeStatementSection.add(incomeStatementEntry); + }); + }); + } + + private BigDecimal calculateTotal(final IncomeStatement incomeStatement, final IncomeStatementSection.Type incomeStatementType) { + return incomeStatement.getIncomeStatementSections() + .stream() + .filter(incomeStatementSection -> + incomeStatementSection.getType().equals(incomeStatementType.name())) + .map(IncomeStatementSection::getSubtotal) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } +} diff --git a/service/src/main/java/io/mifos/accounting/service/internal/service/TrialBalanceService.java b/service/src/main/java/io/mifos/accounting/service/internal/service/TrialBalanceService.java index ced24d7..f141dd7 100644 --- a/service/src/main/java/io/mifos/accounting/service/internal/service/TrialBalanceService.java +++ b/service/src/main/java/io/mifos/accounting/service/internal/service/TrialBalanceService.java @@ -19,15 +19,13 @@ import io.mifos.accounting.api.v1.domain.AccountType; import io.mifos.accounting.api.v1.domain.TrialBalance; import io.mifos.accounting.api.v1.domain.TrialBalanceEntry; import io.mifos.accounting.service.internal.mapper.LedgerMapper; -import io.mifos.accounting.service.internal.repository.AccountEntity; import io.mifos.accounting.service.internal.repository.AccountRepository; -import io.mifos.accounting.service.internal.repository.LedgerEntity; import io.mifos.accounting.service.internal.repository.LedgerRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.math.BigDecimal; import java.util.Comparator; -import java.util.List; @Service public class TrialBalanceService { @@ -45,39 +43,34 @@ public class TrialBalanceService { public TrialBalance getTrialBalance(final boolean includeEmptyEntries) { final TrialBalance trialBalance = new TrialBalance(); - final List<LedgerEntity> ledgers = this.ledgerRepository.findAll(); - if (ledgers != null) ledgers.forEach(ledgerEntity -> { - final List<AccountEntity> accountEntities = this.accountRepository.findByLedger(ledgerEntity); - if (accountEntities != null) { - final TrialBalanceEntry trialBalanceEntry = new TrialBalanceEntry(); - trialBalanceEntry.setLedger(LedgerMapper.map(ledgerEntity)); - switch (AccountType.valueOf(ledgerEntity.getType())) { - case ASSET: - case EXPENSE: - trialBalanceEntry.setType(TrialBalanceEntry.Type.DEBIT.name()); - break; - case LIABILITY: - case EQUITY: - case REVENUE: - trialBalanceEntry.setType(TrialBalanceEntry.Type.CREDIT.name()); - break; - } - trialBalanceEntry.setAmount(0.00D); - accountEntities.forEach(accountEntity -> - trialBalanceEntry.setAmount(trialBalanceEntry.getAmount() + accountEntity.getBalance())); - if (!includeEmptyEntries && trialBalanceEntry.getAmount() == 0.00D) { - //noinspection UnnecessaryReturnStatement - return; - } else { - trialBalance.getTrialBalanceEntries().add(trialBalanceEntry); - } + this.ledgerRepository.findAll().forEach(ledgerEntity -> { + final BigDecimal totalValue = ledgerEntity.getTotalValue() != null ? ledgerEntity.getTotalValue() : BigDecimal.ZERO; + if (!includeEmptyEntries && totalValue.compareTo(BigDecimal.ZERO) != 0) { + return; } + final TrialBalanceEntry trialBalanceEntry = new TrialBalanceEntry(); + trialBalanceEntry.setLedger(LedgerMapper.map(ledgerEntity)); + switch (AccountType.valueOf(ledgerEntity.getType())) { + case ASSET: + case EXPENSE: + trialBalanceEntry.setType(TrialBalanceEntry.Type.DEBIT.name()); + break; + case LIABILITY: + case EQUITY: + case REVENUE: + trialBalanceEntry.setType(TrialBalanceEntry.Type.CREDIT.name()); + break; + } + trialBalanceEntry.setAmount(totalValue.doubleValue()); + trialBalance.getTrialBalanceEntries().add(trialBalanceEntry); }); trialBalance.setDebitTotal( trialBalance.getTrialBalanceEntries() .stream() - .filter(trialBalanceEntry -> trialBalanceEntry.getType().equals(TrialBalanceEntry.Type.DEBIT.name())) + .filter(trialBalanceEntry -> + trialBalanceEntry.getType().equals(TrialBalanceEntry.Type.DEBIT.name()) + && trialBalanceEntry.getLedger().getParentLedgerIdentifier() == null) .mapToDouble(TrialBalanceEntry::getAmount) .sum() ); @@ -85,7 +78,9 @@ public class TrialBalanceService { trialBalance.setCreditTotal( trialBalance.getTrialBalanceEntries() .stream() - .filter(trialBalanceEntry -> trialBalanceEntry.getType().equals(TrialBalanceEntry.Type.CREDIT.name())) + .filter(trialBalanceEntry -> + trialBalanceEntry.getType().equals(TrialBalanceEntry.Type.CREDIT.name()) + && trialBalanceEntry.getLedger().getParentLedgerIdentifier() == null) .mapToDouble(TrialBalanceEntry::getAmount) .sum() ); diff --git a/service/src/main/java/io/mifos/accounting/service/rest/IncomeStatementController.java b/service/src/main/java/io/mifos/accounting/service/rest/IncomeStatementController.java new file mode 100644 index 0000000..49028da --- /dev/null +++ b/service/src/main/java/io/mifos/accounting/service/rest/IncomeStatementController.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 The Mifos Initiative. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.mifos.accounting.service.rest; + +import io.mifos.accounting.api.v1.PermittableGroupIds; +import io.mifos.accounting.api.v1.domain.IncomeStatement; +import io.mifos.accounting.service.internal.service.IncomeStatementService; +import io.mifos.anubis.annotation.AcceptedTokenType; +import io.mifos.anubis.annotation.Permittable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/incomestatement") +public class IncomeStatementController { + + private final IncomeStatementService incomeStatementService; + + @Autowired + public IncomeStatementController(final IncomeStatementService incomeStatementService) { + super(); + this.incomeStatementService = incomeStatementService; + } + + @Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.THOTH_LEDGER) + @RequestMapping( + method = RequestMethod.GET, + produces = {MediaType.APPLICATION_JSON_VALUE}, + consumes = {MediaType.ALL_VALUE} + ) + @ResponseBody + public ResponseEntity<IncomeStatement> getIncomeStatement() { + return ResponseEntity.ok(this.incomeStatementService.getIncomeStatement()); + } +} diff --git a/service/src/main/resources/db/migrations/mariadb/V9__add_total_value_ledger.sql b/service/src/main/resources/db/migrations/mariadb/V9__add_total_value_ledger.sql new file mode 100644 index 0000000..921597a --- /dev/null +++ b/service/src/main/resources/db/migrations/mariadb/V9__add_total_value_ledger.sql @@ -0,0 +1,17 @@ +-- +-- Copyright 2017 The Mifos Initiative. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +ALTER TABLE thoth_ledgers ADD COLUMN total_value NUMERIC(15,5) NULL; \ No newline at end of file -- To stop receiving notification emails like this one, please contact [email protected].
