Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public interface JournalEntryMapper {
@Mapping(target = "debits", ignore = true)
@Mapping(target = "transactionDetails", ignore = true)
@Mapping(target = "savingTransactionId", ignore = true)
@Mapping(target = "externalAssetOwner", ignore = true)
JournalEntryData map(JournalEntry journalEntry);

@Named("entityType")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public enum JournalEntryJsonInputParams {
CHECK_NUMBER("checkNumber"), //
ROUTING_CODE("routingCode"), //
RECEIPT_NUMBER("receiptNumber"), //
BANK_NUMBER("bankNumber"); //
BANK_NUMBER("bankNumber"), //
EXTERNAL_ASSET_OWNER("externalAssetOwner"); //

private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class JournalEntryCommand {
private final SingleDebitOrCreditEntryCommand[] debits;
private final String locale;
private final String dateFormat;
private final String externalAssetOwner;

public void validateForCreate() {

Expand All @@ -75,6 +76,9 @@ public void validateForCreate() {

baseDataValidator.reset().parameter("paymentTypeId").value(this.paymentTypeId).ignoreIfNull().longGreaterThanZero();

baseDataValidator.reset().parameter(JournalEntryJsonInputParams.EXTERNAL_ASSET_OWNER.getValue()).value(this.externalAssetOwner)
.ignoreIfNull().notExceedingLengthOf(100);

// validation for credit array elements
if (this.credits != null) {
if (this.credits.length == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public class JournalEntryData {
private String routingCode;
private String receiptNumber;
private String bankNumber;
private String externalAssetOwner;
private transient Long savingTransactionId;

public JournalEntryData() {}
Expand Down Expand Up @@ -191,7 +192,7 @@ public JournalEntryData(final Long id, final Long officeId, final String officeN
final EnumOptionData entityType, final Long entityId, final Long createdByUserId, final LocalDate submittedOnDate,
final String createdByUserName, final String comments, final Boolean reversed, final String referenceNumber,
final BigDecimal officeRunningBalance, final BigDecimal organizationRunningBalance, final Boolean runningBalanceComputed,
final TransactionDetailData transactionDetailData, final CurrencyData currency) {
final TransactionDetailData transactionDetailData, final CurrencyData currency, final String externalAssetOwner) {
this.id = id;
this.officeId = officeId;
this.officeName = officeName;
Expand All @@ -218,6 +219,7 @@ public JournalEntryData(final Long id, final Long officeId, final String officeN
this.runningBalanceComputed = runningBalanceComputed;
this.transactionDetails = transactionDetailData;
this.currency = currency;
this.externalAssetOwner = externalAssetOwner;
}

public static JournalEntryData importInstance(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId,
Expand Down Expand Up @@ -259,10 +261,11 @@ public static JournalEntryData fromGLAccountData(final GLAccountData glAccountDa
final Boolean runningBalanceComputed = null;
final TransactionDetailData transactionDetailData = null;
final CurrencyData currency = null;
final String externalAssetOwner = null;
return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glAccountCode, glAccountClassification,
transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate,
createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance,
runningBalanceComputed, transactionDetailData, currency);
runningBalanceComputed, transactionDetailData, currency, externalAssetOwner);
}

public Integer getRowIndex() {
Expand All @@ -274,7 +277,6 @@ public LocalDate getTransactionDate() {
}

public void addDebits(CreditDebit debit) {

this.debits.add(debit);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public JournalEntryCommand commandFromApiJson(final String json) {
element);
final String bankNumber = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.BANK_NUMBER.getValue(), element);
final String routingCode = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.ROUTING_CODE.getValue(), element);
final String externalAssetOwner = this.fromApiJsonHelper
.extractStringNamed(JournalEntryJsonInputParams.EXTERNAL_ASSET_OWNER.getValue(), element);

SingleDebitOrCreditEntryCommand[] credits = null;
SingleDebitOrCreditEntryCommand[] debits = null;
Expand All @@ -99,7 +101,8 @@ public JournalEntryCommand commandFromApiJson(final String json) {
}
String dateFormat = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.DATE_FORMAT.getValue(), element);
return new JournalEntryCommand(officeId, currencyCode, transactionDate, comments, referenceNumber, accountingRuleId, amount,
paymentTypeId, accountNumber, checkNumber, receiptNumber, bankNumber, routingCode, credits, debits, localeStr, dateFormat);
paymentTypeId, accountNumber, checkNumber, receiptNumber, bankNumber, routingCode, credits, debits, localeStr, dateFormat,
externalAssetOwner);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
package org.apache.fineract.test.factory;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.client.models.DisbursementDetail;
import org.apache.fineract.client.models.InterestPauseRequestDto;
import org.apache.fineract.client.models.JournalEntryCommand;
import org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest;
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
Expand All @@ -34,11 +36,14 @@
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
import org.apache.fineract.client.models.PutLoansLoanIdRequest;
import org.apache.fineract.client.models.SingleDebitOrCreditEntryCommand;
import org.apache.fineract.test.data.InterestCalculationPeriodTime;
import org.apache.fineract.test.data.InterestType;
import org.apache.fineract.test.data.LoanTermFrequencyType;
import org.apache.fineract.test.data.RepaymentFrequencyType;
import org.apache.fineract.test.data.TransactionProcessingStrategyCode;
import org.apache.fineract.test.data.accounttype.AccountTypeResolver;
import org.apache.fineract.test.data.accounttype.DefaultAccountType;
import org.apache.fineract.test.data.loanproduct.DefaultLoanProduct;
import org.apache.fineract.test.data.loanproduct.LoanProductResolver;
import org.apache.fineract.test.helper.Utils;
Expand Down Expand Up @@ -87,6 +92,8 @@ public class LoanRequestFactory {
public static final String DATE_WITHDRAWN_STRING = FORMATTER.format(Utils.now().minusMonths(1L));
public static final String DEFAULT_TRANSACTION_DATE = FORMATTER.format(Utils.now().minusMonths(1L));

private final AccountTypeResolver accountTypeResolver;

public PostLoansRequest defaultLoansRequest(Long clientId) {
return new PostLoansRequest()//
.clientId(clientId)//
Expand Down Expand Up @@ -312,4 +319,18 @@ public static PostLoansLoanIdRequest defaultContractTerminationUndoRequest() {
public static PostLoansLoanIdRequest defaultLoanContractTerminationRequest() {
return new PostLoansLoanIdRequest().dateFormat(DATE_FORMAT).locale(DEFAULT_LOCALE).note("Contract Termination");
}

public JournalEntryCommand defaultManualJournalEntryRequest(BigDecimal amount) {
final Long glAccountDebit = accountTypeResolver.resolve(DefaultAccountType.LOANS_RECEIVABLE);
final Long glAccountCredit = accountTypeResolver.resolve(DefaultAccountType.SUSPENSE_CLEARING_ACCOUNT);

return new JournalEntryCommand().amount(BigDecimal.TEN).officeId(1L).currencyCode("USD").locale(DEFAULT_LOCALE)
.dateFormat("uuuu-MM-dd").transactionDate(LocalDate.of(2024, 1, 1))
.addCreditsItem(new SingleDebitOrCreditEntryCommand().glAccountId(glAccountCredit).amount(amount))
.addDebitsItem(new SingleDebitOrCreditEntryCommand().glAccountId(glAccountDebit).amount(amount));
}

public JournalEntryCommand defaultManualJournalEntryRequest(BigDecimal amount, String externalAssetOwner) {
return defaultManualJournalEntryRequest(amount).externalAssetOwner(externalAssetOwner);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
*/
package org.apache.fineract.test.stepdef.common;

import static org.apache.fineract.test.stepdef.loan.LoanRescheduleStepDef.FORMATTER_EN;
import static org.assertj.core.api.Assertions.assertThat;

import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Then;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -32,11 +35,14 @@
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
import org.apache.fineract.client.models.JournalEntryCommand;
import org.apache.fineract.client.models.JournalEntryTransactionItem;
import org.apache.fineract.client.models.PostJournalEntriesResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.services.JournalEntriesApi;
import org.apache.fineract.client.services.LoansApi;
import org.apache.fineract.test.data.TransactionType;
import org.apache.fineract.test.factory.LoanRequestFactory;
import org.apache.fineract.test.helper.ErrorHelper;
import org.apache.fineract.test.helper.ErrorMessageHelper;
import org.apache.fineract.test.stepdef.AbstractStepDef;
Expand All @@ -55,6 +61,9 @@ public class JournalEntriesStepDef extends AbstractStepDef {
@Autowired
private JournalEntriesApi journalEntriesApi;

@Autowired
private LoanRequestFactory loanRequestFactory;

@Then("Loan Transactions tab has a {string} transaction with date {string} which has the following Journal entries:")
public void journalEntryDataCheck(String transactionType, String transactionDate, DataTable table) throws IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
Expand Down Expand Up @@ -360,4 +369,131 @@ public void journalEntryNoDataCheck(String transactionType, String transactionDa

assertThat(journalLinesActualList.stream().findFirst().get().size()).isZero();
}

public Response<PostJournalEntriesResponse> addManualJournalEntryWithoutExternalAssetOwner(String amount, String date)
throws IOException {
LocalDate transactionDate = LocalDate.parse(date, FORMATTER_EN);
JournalEntryCommand journalEntriesRequest = loanRequestFactory.defaultManualJournalEntryRequest(new BigDecimal(amount))
.transactionDate(transactionDate);
Response<PostJournalEntriesResponse> journalEntriesResponse = journalEntriesApi.createGLJournalEntry("", journalEntriesRequest)
.execute();
testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_REQUEST, journalEntriesRequest);
return journalEntriesResponse;
}

public Response<PostJournalEntriesResponse> addManualJournalEntryWithExternalAssetOwner(String amount, String date,
String externalAssetOwner) throws IOException {
LocalDate transactionDate = LocalDate.parse(date, FORMATTER_EN);
JournalEntryCommand journalEntriesRequest = loanRequestFactory
.defaultManualJournalEntryRequest(new BigDecimal(amount), externalAssetOwner).transactionDate(transactionDate);
Response<PostJournalEntriesResponse> journalEntriesResponse = journalEntriesApi.createGLJournalEntry("", journalEntriesRequest)
.execute();
testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_REQUEST, journalEntriesRequest);
return journalEntriesResponse;
}

@Then("Admin creates manual Journal entry with {string} amount and {string} date and unique External Asset Owner")
public void createManualJournalEntryWithExternalAssetOwner(String amount, String date) throws IOException {
String ownerExternalIdStored = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_OWNER_EXTERNAL_ID);
Response<PostJournalEntriesResponse> journalEntriesResponse = addManualJournalEntryWithExternalAssetOwner(amount, date,
ownerExternalIdStored);

testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_RESPONSE, journalEntriesResponse);
ErrorHelper.checkSuccessfulApiCall(journalEntriesResponse);
}

@Then("Admin creates manual Journal entry with {string} amount and {string} date and empty External Asset Owner")
public void createManualJournalEntryWithEmptyExternalAssetOwner(String amount, String date) throws IOException {
Response<PostJournalEntriesResponse> journalEntriesResponse = addManualJournalEntryWithExternalAssetOwner(amount, date, "");

testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_RESPONSE, journalEntriesResponse);
ErrorHelper.checkSuccessfulApiCall(journalEntriesResponse);
}

@Then("Admin creates manual Journal entry with {string} amount and {string} date and without External Asset Owner")
public void createManualJournalEntryWithoutExternalAssetOwner(String amount, String date) throws IOException {
Response<PostJournalEntriesResponse> journalEntriesResponse = addManualJournalEntryWithoutExternalAssetOwner(amount, date);

testContext().set(TestContextKey.MANUAL_JOURNAL_ENTRIES_RESPONSE, journalEntriesResponse);
ErrorHelper.checkSuccessfulApiCall(journalEntriesResponse);
}

@Then("Verify manual Journal entry with External Asset Owner {string} and with the following Journal entries:")
public void checkManualJournalEntry(String externalAssetOwnerEnabled, DataTable table) {
Response<PostJournalEntriesResponse> journalEnriesResponse = testContext().get(TestContextKey.MANUAL_JOURNAL_ENTRIES_RESPONSE);
PostJournalEntriesResponse journalEntriesResponseBody = journalEnriesResponse.body();
String transactionId = journalEntriesResponseBody.getTransactionId();

JournalEntryCommand journalEntriesRequest = testContext().get(TestContextKey.MANUAL_JOURNAL_ENTRIES_REQUEST);

Response<GetJournalEntriesTransactionIdResponse> journalEntryDataResponse = null;
try {
journalEntryDataResponse = journalEntriesApi.retrieveAll1(//
null, //
null, //
null, //
null, //
null, //
null, //
null, //
transactionId, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
null, //
true//
).execute();
ErrorHelper.checkSuccessfulApiCall(journalEntryDataResponse);
} catch (IOException e) {
log.error("Exception", e);
}

List<List<String>> data = table.asLists();
for (int i = 1; i < data.size(); i++) {
List<List<List<String>>> possibleActualValuesList = new ArrayList<>();
List<String> expectedValues = data.get(i);
if (Boolean.parseBoolean(externalAssetOwnerEnabled)) {
expectedValues
.add(journalEntriesRequest.getExternalAssetOwner() == null ? null : journalEntriesRequest.getExternalAssetOwner());
}
boolean containsAnyExpected = false;

GetJournalEntriesTransactionIdResponse journalEntryData = journalEntryDataResponse.body();

List<JournalEntryTransactionItem> journalLinesActual = journalEntryData.getPageItems();

List<List<String>> actualValuesList = journalLinesActual.stream().map(t -> {
List<String> actualValues = new ArrayList<>();
actualValues.add(t.getGlAccountType().getValue() == null ? null : t.getGlAccountType().getValue());
actualValues.add(t.getGlAccountCode() == null ? null : t.getGlAccountCode());
actualValues.add(t.getGlAccountName() == null ? null : t.getGlAccountName());
actualValues.add("DEBIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
actualValues.add("CREDIT".equals(t.getEntryType().getValue()) ? String.valueOf(t.getAmount()) : null);
actualValues.add(String.valueOf(t.getManualEntry()).toLowerCase());
if (Boolean.parseBoolean(externalAssetOwnerEnabled)) {
actualValues.add(t.getExternalAssetOwner() == null ? null : t.getExternalAssetOwner());
}

return actualValues;
}).collect(Collectors.toList());

possibleActualValuesList.add(actualValuesList);

boolean containsExpectedValues = actualValuesList.stream().anyMatch(actualValues -> actualValues.equals(expectedValues));
if (containsExpectedValues) {
containsAnyExpected = true;
}

assertThat(containsAnyExpected)
.as(ErrorMessageHelper.wrongValueInLineInJournalEntries(transactionId, i, possibleActualValuesList, expectedValues))
.isTrue();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,6 @@ public abstract class TestContextKey {
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination";
public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse";
public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse";
public static final String MANUAL_JOURNAL_ENTRIES_REQUEST = "manualJournalEntriesRequest";
public static final String MANUAL_JOURNAL_ENTRIES_RESPONSE = "manualJournalEntriesResponse";
}
Loading
Loading