FINERACT-1760: Enhanced external id support for loan transactions

This commit is contained in:
Adam Saghy
2022-11-24 23:08:09 +01:00
parent 838d05f987
commit 5079059405
58 changed files with 2140 additions and 518 deletions
@@ -26,7 +26,9 @@ import javax.persistence.ManyToOne;
import javax.persistence.Table; import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
@@ -62,7 +64,7 @@ public class CommandSource extends AbstractPersistableCustom {
private Long resourceId; private Long resourceId;
@Column(name = "subresource_id") @Column(name = "subresource_id")
private Long subresourceId; private Long subResourceId;
@Column(name = "command_as_json", length = 1000) @Column(name = "command_as_json", length = 1000)
private String commandAsJson; private String commandAsJson;
@@ -110,6 +112,12 @@ public class CommandSource extends AbstractPersistableCustom {
@Column(name = "idempotency_key", length = 50) @Column(name = "idempotency_key", length = 50)
private String idempotencyKey; private String idempotencyKey;
@Column(name = "resource_external_id")
private ExternalId resourceExternalId;
@Column(name = "subresource_external_id")
private ExternalId subResourceExternalId;
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker, public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker,
String idempotencyKey) { String idempotencyKey) {
return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(), return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(),
@@ -121,12 +129,12 @@ public class CommandSource extends AbstractPersistableCustom {
} }
private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId, private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId,
final Long subresourceId, final String commandSerializedAsJson, final AppUser maker, final String idempotencyKey) { final Long subResourceId, final String commandSerializedAsJson, final AppUser maker, final String idempotencyKey) {
this.actionName = actionName; this.actionName = actionName;
this.entityName = entityName; this.entityName = entityName;
this.resourceGetUrl = href; this.resourceGetUrl = href;
this.resourceId = resourceId; this.resourceId = resourceId;
this.subresourceId = subresourceId; this.subResourceId = subResourceId;
this.commandAsJson = commandSerializedAsJson; this.commandAsJson = commandSerializedAsJson;
this.maker = maker; this.maker = maker;
this.madeOnDate = DateUtils.getOffsetDateTimeOfTenant(); this.madeOnDate = DateUtils.getOffsetDateTimeOfTenant();
@@ -170,8 +178,8 @@ public class CommandSource extends AbstractPersistableCustom {
this.resourceId = resourceId; this.resourceId = resourceId;
} }
public void updateSubresourceId(final Long subresourceId) { public void updateSubResourceId(final Long subResourceId) {
this.subresourceId = subresourceId; this.subResourceId = subResourceId;
} }
public void updateJsonTo(final String json) { public void updateJsonTo(final String json) {
@@ -182,8 +190,8 @@ public class CommandSource extends AbstractPersistableCustom {
return this.resourceId; return this.resourceId;
} }
public Long subresourceId() { public Long subResourceId() {
return this.subresourceId; return this.subResourceId;
} }
public boolean hasJson() { public boolean hasJson() {
@@ -210,8 +218,8 @@ public class CommandSource extends AbstractPersistableCustom {
return this.resourceId; return this.resourceId;
} }
public Long getSubresourceId() { public Long getSubResourceId() {
return this.subresourceId; return this.subResourceId;
} }
public void markAsAwaitingApproval() { public void markAsAwaitingApproval() {
@@ -226,15 +234,18 @@ public class CommandSource extends AbstractPersistableCustom {
return false; return false;
} }
public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, public void updateForAudit(final CommandProcessingResult result) {
final Long productId, final String transactionId) { this.officeId = result.getOfficeId();
this.officeId = officeId; this.groupId = result.getGroupId();
this.groupId = groupId; this.clientId = result.getClientId();
this.clientId = clientId; this.loanId = result.getLoanId();
this.loanId = loanId; this.savingsId = result.getSavingsId();
this.savingsId = savingsId; this.productId = result.getProductId();
this.productId = productId; this.transactionId = result.getTransactionId();
this.transactionId = transactionId; this.resourceId = result.getResourceId();
this.resourceExternalId = result.getResourceExternalId();
this.subResourceId = result.getSubResourceId();
this.subResourceExternalId = result.getSubResourceExternalId();
} }
public String getResourceGetUrl() { public String getResourceGetUrl() {
@@ -82,7 +82,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
validateIsUpdateAllowed(); validateIsUpdateAllowed();
final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(makerCheckerId, commandSourceInput.getActionName(), final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(makerCheckerId, commandSourceInput.getActionName(),
commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subresourceId(), commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subResourceId(),
commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getOfficeId(), commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getOfficeId(),
commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(),
commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(),
@@ -90,7 +90,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json()); final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json());
final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand, final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand,
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(), this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
commandSourceInput.subresourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.subResourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(),
commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(),
commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getCreditBureauId(), commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getCreditBureauId(),
commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName()); commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName());
@@ -110,9 +110,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
? (requestIdempotencyKey == null ? idempotencyKeyGenerator.create() : requestIdempotencyKey) ? (requestIdempotencyKey == null ? idempotencyKeyGenerator.create() : requestIdempotencyKey)
: wrapper.getIdempotencyKey()); : wrapper.getIdempotencyKey());
} }
commandSourceResult.updateResourceId(result.getResourceId());
commandSourceResult.updateForAudit(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), commandSourceResult.updateForAudit(result);
result.getSavingsId(), result.getProductId(), result.getTransactionId());
String changesOnlyJson; String changesOnlyJson;
boolean rollBack = (rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker; boolean rollBack = (rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker;
@@ -124,4 +124,6 @@ public interface ConfigurationDomainService {
Long retrieveRepaymentDueDays(); Long retrieveRepaymentDueDays();
Long retrieveRepaymentOverdueDays(); Long retrieveRepaymentOverdueDays();
boolean isExternalIdAutoGenerationEnabled();
} }
@@ -43,6 +43,7 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
public static final String EXTERNAL_EVENTS_PURGE_DAYS = "purge-external-events-older-than-days"; public static final String EXTERNAL_EVENTS_PURGE_DAYS = "purge-external-events-older-than-days";
private static final String DAYS_BEFORE_REPAYMENT_IS_DUE = "days-before-repayment-is-due"; private static final String DAYS_BEFORE_REPAYMENT_IS_DUE = "days-before-repayment-is-due";
private static final String DAYS_AFTER_REPAYMENT_IS_OVERDUE = "days-after-repayment-is-overdue"; private static final String DAYS_AFTER_REPAYMENT_IS_OVERDUE = "days-after-repayment-is-overdue";
private static final String ENABLE_EXTERNAL_ID_AUTO_GENERATION = "enable-auto-generated-external-id";
private final PermissionRepository permissionRepository; private final PermissionRepository permissionRepository;
private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository; private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository;
private final PlatformCacheRepository cacheTypeRepository; private final PlatformCacheRepository cacheTypeRepository;
@@ -473,4 +474,10 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(DAYS_AFTER_REPAYMENT_IS_OVERDUE); final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(DAYS_AFTER_REPAYMENT_IS_OVERDUE);
return property.getValue(); return property.getValue();
} }
@Override
public boolean isExternalIdAutoGenerationEnabled() {
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(ENABLE_EXTERNAL_ID_AUTO_GENERATION);
return property.isEnabled();
}
} }
@@ -0,0 +1,50 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.configuration.service;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
* Temporary (static) configuration service container.
*
* Provide static access to the configuration service TODO: To be deleted when the code cleanup / refactor finished (of
* Loan.java) and we dont need this workaround anymore
*/
@Component
@RequiredArgsConstructor
public class TemporaryConfigurationServiceContainer implements InitializingBean {
private static volatile ConfigurationDomainService STATIC_REF_CONFIGURATION_SERVICE;
private final ConfigurationDomainService configurationDomainService;
// To avoid any abuse of this temporary solution, only the `isExternalIdAutoGenerationEnabled()` is exposed
public static boolean isExternalIdAutoGenerationEnabled() {
return TemporaryConfigurationServiceContainer.STATIC_REF_CONFIGURATION_SERVICE.isExternalIdAutoGenerationEnabled();
}
@SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
@Override
public void afterPropertiesSet() throws Exception {
TemporaryConfigurationServiceContainer.STATIC_REF_CONFIGURATION_SERVICE = configurationDomainService;
}
}
@@ -0,0 +1,42 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.core.api;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
/**
* GSON Serializer for ExternalId
*
*/
public class ExternalIdAdapter implements JsonSerializer<ExternalId> {
@Override
@SuppressWarnings("unused")
public JsonElement serialize(ExternalId src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null || src.isEmpty()) {
return null;
}
return new JsonPrimitive(src.getValue());
}
}
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
/** /**
* Represents the successful result of an REST API call that results in processing a command. * Represents the successful result of an REST API call that results in processing a command.
@@ -47,27 +48,33 @@ public class CommandProcessingResult implements Serializable {
private final Long gsimId; private final Long gsimId;
private final Long glimId; private final Long glimId;
private Boolean rollbackTransaction; private Boolean rollbackTransaction;
private final ExternalId resourceExternalId;
private final ExternalId subResourceExternalId;
public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult) { public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult) {
return new CommandProcessingResult(commandResult.commandId, commandResult.officeId, commandResult.groupId, commandResult.clientId, return new CommandProcessingResult(commandResult.commandId, commandResult.officeId, commandResult.groupId, commandResult.clientId,
commandResult.loanId, commandResult.savingsId, commandResult.resourceIdentifier, commandResult.resourceId, commandResult.loanId, commandResult.savingsId, commandResult.resourceIdentifier, commandResult.resourceId,
commandResult.transactionId, commandResult.changes, commandResult.productId, commandResult.gsimId, commandResult.glimId, commandResult.transactionId, commandResult.changes, commandResult.productId, commandResult.gsimId, commandResult.glimId,
commandResult.creditBureauReportData, commandResult.rollbackTransaction, commandResult.subResourceId); commandResult.creditBureauReportData, commandResult.rollbackTransaction, commandResult.subResourceId,
commandResult.resourceExternalId, commandResult.subResourceExternalId);
} }
public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult, final Long resourceId) { public static CommandProcessingResult fromCommandProcessingResult(CommandProcessingResult commandResult, final Long resourceId) {
return new CommandProcessingResult(commandResult.commandId, commandResult.officeId, commandResult.groupId, commandResult.clientId, return new CommandProcessingResult(commandResult.commandId, commandResult.officeId, commandResult.groupId, commandResult.clientId,
commandResult.loanId, commandResult.savingsId, commandResult.resourceIdentifier, resourceId, commandResult.transactionId, commandResult.loanId, commandResult.savingsId, commandResult.resourceIdentifier, resourceId, commandResult.transactionId,
commandResult.changes, commandResult.productId, commandResult.gsimId, commandResult.glimId, commandResult.changes, commandResult.productId, commandResult.gsimId, commandResult.glimId,
commandResult.creditBureauReportData, commandResult.rollbackTransaction, commandResult.subResourceId); commandResult.creditBureauReportData, commandResult.rollbackTransaction, commandResult.subResourceId,
commandResult.resourceExternalId, commandResult.subResourceExternalId);
} }
public static CommandProcessingResult fromDetails(final Long commandId, final Long officeId, final Long groupId, final Long clientId, public static CommandProcessingResult fromDetails(final Long commandId, final Long officeId, final Long groupId, final Long clientId,
final Long loanId, final Long savingsId, final String resourceIdentifier, final Long entityId, final Long gsimId, final Long loanId, final Long savingsId, final String resourceIdentifier, final Long entityId, final Long gsimId,
final Long glimId, final Map<String, Object> creditBureauReportData, final String transactionId, final Long glimId, final Map<String, Object> creditBureauReportData, final String transactionId,
final Map<String, Object> changes, final Long productId, final Boolean rollbackTransaction, final Long subResourceId) { final Map<String, Object> changes, final Long productId, final Boolean rollbackTransaction, final Long subResourceId,
final ExternalId resourceExternalId, final ExternalId subResourceExternalId) {
return new CommandProcessingResult(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId, return new CommandProcessingResult(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId,
transactionId, changes, productId, gsimId, glimId, creditBureauReportData, rollbackTransaction, subResourceId); transactionId, changes, productId, gsimId, glimId, creditBureauReportData, rollbackTransaction, subResourceId,
resourceExternalId, subResourceExternalId);
} }
public static CommandProcessingResult commandOnlyResult(final Long commandId) { public static CommandProcessingResult commandOnlyResult(final Long commandId) {
@@ -121,12 +128,15 @@ public class CommandProcessingResult implements Serializable {
this.glimId = null; this.glimId = null;
this.creditBureauReportData = null; this.creditBureauReportData = null;
this.subResourceId = null; this.subResourceId = null;
this.resourceExternalId = ExternalId.empty();
this.subResourceExternalId = ExternalId.empty();
} }
private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId, private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId,
final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId, final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId,
final Map<String, Object> changesOnly, final Long productId, final Long gsimId, final Long glimId, final Map<String, Object> changesOnly, final Long productId, final Long gsimId, final Long glimId,
final Map<String, Object> creditBureauReportData, Boolean rollbackTransaction, final Long subResourceId) { final Map<String, Object> creditBureauReportData, Boolean rollbackTransaction, final Long subResourceId,
final ExternalId resourceExternalId, final ExternalId subResourceExternalId) {
this.commandId = commandId; this.commandId = commandId;
this.officeId = officeId; this.officeId = officeId;
this.groupId = groupId; this.groupId = groupId;
@@ -143,6 +153,8 @@ public class CommandProcessingResult implements Serializable {
this.creditBureauReportData = creditBureauReportData; this.creditBureauReportData = creditBureauReportData;
this.rollbackTransaction = rollbackTransaction; this.rollbackTransaction = rollbackTransaction;
this.subResourceId = subResourceId; this.subResourceId = subResourceId;
this.resourceExternalId = resourceExternalId;
this.subResourceExternalId = subResourceExternalId;
} }
protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId,
@@ -166,11 +178,14 @@ public class CommandProcessingResult implements Serializable {
this.glimId = null; this.glimId = null;
this.creditBureauReportData = null; this.creditBureauReportData = null;
this.subResourceId = null; this.subResourceId = null;
this.resourceExternalId = ExternalId.empty();
this.subResourceExternalId = ExternalId.empty();
} }
protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId,
final Map<String, Object> changesOnly, long clientId) { final Map<String, Object> changesOnly, long clientId) {
this(commandId, officeId, null, clientId, null, null, null, resourceId, null, changesOnly, null, null, null, null, null, null); this(commandId, officeId, null, clientId, null, null, null, resourceId, null, changesOnly, null, null, null, null, null, null,
ExternalId.empty(), ExternalId.empty());
} }
public void setOfficeId(final Long officeId) { public void setOfficeId(final Long officeId) {
@@ -19,6 +19,7 @@
package org.apache.fineract.infrastructure.core.data; package org.apache.fineract.infrastructure.core.data;
import java.util.Map; import java.util.Map;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
/** /**
* Represents the successful result of an REST API call that results in processing a command. * Represents the successful result of an REST API call that results in processing a command.
@@ -41,11 +42,14 @@ public class CommandProcessingResultBuilder {
private Map<String, Object> creditBureauReportData; private Map<String, Object> creditBureauReportData;
private Long productId; private Long productId;
private boolean rollbackTransaction = false; private boolean rollbackTransaction = false;
private ExternalId entityExternalId = ExternalId.empty();
private ExternalId subEntityExternalId = ExternalId.empty();
public CommandProcessingResult build() { public CommandProcessingResult build() {
return CommandProcessingResult.fromDetails(this.commandId, this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, return CommandProcessingResult.fromDetails(this.commandId, this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId,
this.resourceIdentifier, this.entityId, this.gsimId, this.glimId, this.creditBureauReportData, this.transactionId, this.resourceIdentifier, this.entityId, this.gsimId, this.glimId, this.creditBureauReportData, this.transactionId,
this.changes, this.productId, this.rollbackTransaction, this.subEntityId); this.changes, this.productId, this.rollbackTransaction, this.subEntityId, this.entityExternalId, this.subEntityExternalId);
} }
public CommandProcessingResultBuilder withCommandId(final Long withCommandId) { public CommandProcessingResultBuilder withCommandId(final Long withCommandId) {
@@ -128,4 +132,14 @@ public class CommandProcessingResultBuilder {
return this; return this;
} }
public CommandProcessingResultBuilder withEntityExternalId(final ExternalId entityExternalId) {
this.entityExternalId = entityExternalId;
return this;
}
public CommandProcessingResultBuilder withSubEntityExternalId(final ExternalId subEntityExternalId) {
this.subEntityExternalId = subEntityExternalId;
return this;
}
} }
@@ -0,0 +1,84 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.core.domain;
import java.io.Serial;
import java.io.Serializable;
import java.util.UUID;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
/**
* ExternalId Value object
*/
@Getter
@EqualsAndHashCode
public class ExternalId implements Serializable {
@Serial
private static final long serialVersionUID = 1;
private static final ExternalId empty = new ExternalId();
private final String value;
private ExternalId() {
this.value = null;
}
public ExternalId(String value) {
if (StringUtils.isBlank(value)) {
throw new IllegalArgumentException("error.external.id.cannot.be.blank");
}
this.value = value;
}
/**
* @return Create a new ExternalId object where value is a newly generated UUID
*/
public static ExternalId generate() {
return new ExternalId(UUID.randomUUID().toString());
}
/**
* @return Create and return an empty ExternalId object
*/
public static ExternalId empty() {
return empty;
}
/**
* @return whether value is null for the ExternalId object (return true if value is null)
*/
public boolean isEmpty() {
return value == null;
}
/**
* Throws exception if value was not set (value is null) for this object
*
* @throws PlatformInternalServerException
* if value was not set (value is null) for this object
*/
public void throwExceptionIfEmpty() {
if (isEmpty()) {
throw new PlatformInternalServerException("error.external.id.is.not.set", "Internal state violation: External id is not set");
}
}
}
@@ -0,0 +1,38 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.core.domain;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import org.apache.commons.lang3.StringUtils;
@Converter(autoApply = true)
public class ExternalIdConverter implements AttributeConverter<ExternalId, String> {
@Override
public String convertToDatabaseColumn(ExternalId externalId) {
return externalId != null ? externalId.getValue() : null;
}
@Override
public ExternalId convertToEntityAttribute(String externalId) {
return StringUtils.isBlank(externalId) ? ExternalId.empty() : new ExternalId(externalId);
}
}
@@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.apache.fineract.infrastructure.core.api.DateAdapter; import org.apache.fineract.infrastructure.core.api.DateAdapter;
import org.apache.fineract.infrastructure.core.api.ExternalIdAdapter;
import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter; import org.apache.fineract.infrastructure.core.api.JodaDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter; import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
import org.apache.fineract.infrastructure.core.api.LocalDateAdapter; import org.apache.fineract.infrastructure.core.api.LocalDateAdapter;
@@ -39,6 +40,7 @@ import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter;
import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter; import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy; import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy; import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -122,5 +124,6 @@ public final class GoogleGsonSerializerHelper {
builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter()); builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()); builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter()); builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
builder.registerTypeAdapter(ExternalId.class, new ExternalIdAdapter());
} }
} }
@@ -0,0 +1,60 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.core.service;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ExternalIdFactory {
private final ConfigurationDomainService configurationDomainService;
public static ExternalId produce(String value) {
return StringUtils.isBlank(value) ? ExternalId.empty() : new ExternalId(value);
}
public ExternalId createFromCommand(JsonCommand command, final String externalIdKey) {
String externalIdStr = null;
if (command.parsedJson() != null) {
externalIdStr = command.stringValueOfParameterNamedAllowingNull(externalIdKey);
}
return create(externalIdStr);
}
public ExternalId create(String externalIdStr) {
if (StringUtils.isBlank(externalIdStr)) {
if (configurationDomainService.isExternalIdAutoGenerationEnabled()) {
return ExternalId.generate();
} else {
return ExternalId.empty();
}
}
return new ExternalId(externalIdStr);
}
public ExternalId create() {
return create(null);
}
}
@@ -19,8 +19,10 @@
package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan; package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan;
import org.apache.fineract.avro.loan.v1.LoanAccountDataV1; import org.apache.fineract.avro.loan.v1.LoanAccountDataV1;
import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig; import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData; import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
@@ -33,4 +35,6 @@ public interface LoanAccountDataMapper {
@Mapping(source = "topup", target = "isTopup") @Mapping(source = "topup", target = "isTopup")
@Mapping(source = "interestRecalculationEnabled", target = "isInterestRecalculationEnabled") @Mapping(source = "interestRecalculationEnabled", target = "isInterestRecalculationEnabled")
LoanAccountDataV1 map(LoanAccountData source); LoanAccountDataV1 map(LoanAccountData source);
LoanTransactionDataV1 map(LoanTransactionData source);
} }
@@ -0,0 +1,33 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.springframework.stereotype.Component;
@Component
public class AvroExternalIdMapper {
public String mapExternalId(ExternalId source) {
if (source == null) {
return null;
}
return source.getValue();
}
}
@@ -24,5 +24,5 @@ import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy; import org.mapstruct.ReportingPolicy;
@MapperConfig(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.ERROR, builder = @Builder(disableBuilder = true), uses = { @MapperConfig(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.ERROR, builder = @Builder(disableBuilder = true), uses = {
AvroDateTimeMapper.class, AvroMonthDayMapper.class }) AvroDateTimeMapper.class, AvroMonthDayMapper.class, AvroExternalIdMapper.class })
public class AvroMapperConfig {} public class AvroMapperConfig {}
@@ -72,6 +72,7 @@ public class JobSchedulerServiceImpl implements ApplicationListener<ContextRefre
schedulerDetail.setSuspended(false); schedulerDetail.setSuspended(false);
schedularWritePlatformService.updateSchedulerDetail(schedulerDetail); schedularWritePlatformService.updateSchedulerDetail(schedulerDetail);
} }
ThreadLocalContextUtil.reset();
} }
log.info("Scheduling batch jobs has finished"); log.info("Scheduling batch jobs has finished");
} }
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Locale; import java.util.Locale;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.PortfolioAccountType;
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails; import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -47,7 +48,7 @@ public class AccountTransferDTO {
private final Integer transferType; private final Integer transferType;
private final AccountTransferDetails accountTransferDetails; private final AccountTransferDetails accountTransferDetails;
private final String noteText; private final String noteText;
private final String txnExternalId; private final ExternalId txnExternalId;
private final Loan loan; private final Loan loan;
private final Loan fromLoan; private final Loan fromLoan;
private final Loan toLoan; private final Loan toLoan;
@@ -61,7 +62,7 @@ public class AccountTransferDTO {
final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt, final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt,
final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId, final PaymentDetail paymentDetail, final Integer fromTransferType, final Integer toTransferType, final Long chargeId,
Integer loanInstallmentNumber, Integer transferType, final AccountTransferDetails accountTransferDetails, final String noteText, Integer loanInstallmentNumber, Integer transferType, final AccountTransferDetails accountTransferDetails, final String noteText,
final String txnExternalId, final Loan loan, SavingsAccount toSavingsAccount, final SavingsAccount fromSavingsAccount, final ExternalId txnExternalId, final Loan loan, SavingsAccount toSavingsAccount, final SavingsAccount fromSavingsAccount,
final Boolean isRegularTransaction, Boolean isExceptionForBalanceCheck) { final Boolean isRegularTransaction, Boolean isExceptionForBalanceCheck) {
this.transactionDate = transactionDate; this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount; this.transactionAmount = transactionAmount;
@@ -93,7 +94,7 @@ public class AccountTransferDTO {
public AccountTransferDTO(final LocalDate transactionDate, final BigDecimal transactionAmount, public AccountTransferDTO(final LocalDate transactionDate, final BigDecimal transactionAmount,
final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType, final Long fromAccountId, final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType, final Long fromAccountId,
final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt, final Long toAccountId, final String description, final Locale locale, final DateTimeFormatter fmt,
final Integer fromTransferType, final Integer toTransferType, final String txnExternalId, final Loan fromLoan, final Integer fromTransferType, final Integer toTransferType, final ExternalId txnExternalId, final Loan fromLoan,
final Loan toLoan) { final Loan toLoan) {
this.transactionDate = transactionDate; this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount; this.transactionAmount = transactionAmount;
@@ -190,7 +191,7 @@ public class AccountTransferDTO {
return this.noteText; return this.noteText;
} }
public String getTxnExternalId() { public ExternalId getTxnExternalId() {
return this.txnExternalId; return this.txnExternalId;
} }
@@ -158,4 +158,8 @@ public class AccountTransferDetails extends AbstractPersistableCustom {
return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, null, toLoanAccount, return new AccountTransferDetails(fromOffice, fromClient, null, fromLoanAccount, toOffice, toClient, null, toLoanAccount,
transferType, null); transferType, null);
} }
public List<AccountTransferTransaction> getAccountTransferTransactions() {
return accountTransferTransactions;
}
} }
@@ -25,6 +25,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -107,8 +108,8 @@ public class ExecuteStandingInstructionsTasklet implements Tasklet {
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountType(), AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount, data.fromAccountType(),
data.toAccountType(), data.fromAccount().getId(), data.toAccount().getId(), data.toAccountType(), data.fromAccount().getId(), data.toAccount().getId(),
data.name() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null, data.name() + " Standing instruction trasfer ", null, null, null, null, data.toTransferType(), null, null,
data.transferType().getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction, data.transferType().getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount,
isExceptionForBalanceCheck); isRegularTransaction, isExceptionForBalanceCheck);
final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId()); final boolean transferCompleted = transferAmount(errors, accountTransferDTO, data.getId());
if (transferCompleted) { if (transferCompleted) {
@@ -31,11 +31,14 @@ import java.time.format.DateTimeFormatter;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.PortfolioAccountType;
import org.apache.fineract.portfolio.account.data.AccountTransferDTO; import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator; import org.apache.fineract.portfolio.account.data.AccountTransfersDataValidator;
@@ -63,11 +66,11 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService; import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor
public class AccountTransfersWritePlatformServiceImpl implements AccountTransfersWritePlatformService { public class AccountTransfersWritePlatformServiceImpl implements AccountTransfersWritePlatformService {
private final AccountTransfersDataValidator accountTransfersDataValidator; private final AccountTransfersDataValidator accountTransfersDataValidator;
@@ -82,28 +85,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
private final LoanReadPlatformService loanReadPlatformService; private final LoanReadPlatformService loanReadPlatformService;
private final GSIMRepositoy gsimRepository; private final GSIMRepositoy gsimRepository;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final ExternalIdFactory externalIdFactory;
@Autowired
public AccountTransfersWritePlatformServiceImpl(final AccountTransfersDataValidator accountTransfersDataValidator,
final AccountTransferAssembler accountTransferAssembler, final AccountTransferRepository accountTransferRepository,
final SavingsAccountAssembler savingsAccountAssembler, final SavingsAccountDomainService savingsAccountDomainService,
final LoanAssembler loanAssembler, final LoanAccountDomainService loanAccountDomainService,
final SavingsAccountWritePlatformService savingsAccountWritePlatformService,
final AccountTransferDetailRepository accountTransferDetailRepository, final LoanReadPlatformService loanReadPlatformService,
final GSIMRepositoy gsimRepository, ConfigurationDomainService configurationDomainService) {
this.accountTransfersDataValidator = accountTransfersDataValidator;
this.accountTransferAssembler = accountTransferAssembler;
this.accountTransferRepository = accountTransferRepository;
this.savingsAccountAssembler = savingsAccountAssembler;
this.savingsAccountDomainService = savingsAccountDomainService;
this.loanAccountAssembler = loanAssembler;
this.loanAccountDomainService = loanAccountDomainService;
this.savingsAccountWritePlatformService = savingsAccountWritePlatformService;
this.accountTransferDetailRepository = accountTransferDetailRepository;
this.loanReadPlatformService = loanReadPlatformService;
this.gsimRepository = gsimRepository;
this.configurationDomainService = configurationDomainService;
}
@Transactional @Transactional
@Override @Override
@@ -178,9 +160,11 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final HolidayDetailDTO holidayDetailDto = null; final HolidayDetailDTO holidayDetailDto = null;
final boolean isRecoveryRepayment = false; final boolean isRecoveryRepayment = false;
final String chargeRefundChargeType = null; final String chargeRefundChargeType = null;
ExternalId externalId = externalIdFactory.create();
final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT, final LoanTransaction loanRepaymentTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT,
toLoanAccount, new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null, toLoanAccount, transactionDate, transactionAmount, paymentDetail, null, externalId, isRecoveryRepayment,
isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone); chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone);
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command, final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(command,
fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction); fromSavingsAccount, toLoanAccount, withdrawal, loanRepaymentTransaction);
@@ -193,9 +177,9 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId); final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
ExternalId externalId = externalIdFactory.create();
final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefund(fromLoanAccountId, final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefund(fromLoanAccountId,
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null); new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, externalId);
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
@@ -329,12 +313,16 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), transactionBooleanValues, backdatedTxnsAllowedTill); accountTransferDTO.getPaymentDetail(), transactionBooleanValues, backdatedTxnsAllowedTill);
LoanTransaction loanTransaction = null; LoanTransaction loanTransaction;
ExternalId txnExternalId = accountTransferDTO.getTxnExternalId();
// Safety net (it might need to generate new one)
ExternalId externalId = externalIdFactory.create(txnExternalId.getValue());
if (AccountTransferType.fromInt(accountTransferDTO.getTransferType()).isChargePayment()) { if (AccountTransferType.fromInt(accountTransferDTO.getTransferType()).isChargePayment()) {
loanTransaction = this.loanAccountDomainService.makeChargePayment(toLoanAccount, accountTransferDTO.getChargeId(), loanTransaction = this.loanAccountDomainService.makeChargePayment(toLoanAccount, accountTransferDTO.getChargeId(),
accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), null, null, accountTransferDTO.getToTransferType(), accountTransferDTO.getPaymentDetail(), null, externalId, accountTransferDTO.getToTransferType(),
accountTransferDTO.getLoanInstallmentNumber()); accountTransferDTO.getLoanInstallmentNumber());
} else { } else {
@@ -343,9 +331,9 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final HolidayDetailDTO holidayDetailDto = null; final HolidayDetailDTO holidayDetailDto = null;
final String chargeRefundChargeType = null; final String chargeRefundChargeType = null;
loanTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT, toLoanAccount, loanTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT, toLoanAccount,
new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), null, null, isRecoveryRepayment, accountTransferDTO.getPaymentDetail(), null, externalId, isRecoveryRepayment, chargeRefundChargeType,
chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone); isAccountTransfer, holidayDetailDto, isHolidayValidationDone);
} }
accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount, accountTransferDetails = this.accountTransferAssembler.assembleSavingsToLoanTransfer(accountTransferDTO, fromSavingsAccount,
@@ -354,8 +342,8 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
transferTransactionId = accountTransferDetails.getId(); transferTransactionId = accountTransferDetails.getId();
} else if (isSavingsToSavingsAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) { } else if (isSavingsToSavingsAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
SavingsAccount fromSavingsAccount = null; SavingsAccount fromSavingsAccount;
SavingsAccount toSavingsAccount = null; SavingsAccount toSavingsAccount;
if (accountTransferDetails == null) { if (accountTransferDetails == null) {
if (accountTransferDTO.getFromSavingsAccount() == null) { if (accountTransferDTO.getFromSavingsAccount() == null) {
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId(), fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId(),
@@ -421,15 +409,20 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
this.savingsAccountAssembler.setHelpers(toSavingsAccount); this.savingsAccountAssembler.setHelpers(toSavingsAccount);
} }
LoanTransaction loanTransaction = null; LoanTransaction loanTransaction = null;
ExternalId txnExternalId = accountTransferDTO.getTxnExternalId();
// Safety net (it might need to generate new one)
ExternalId externalId = externalIdFactory.create(txnExternalId.getValue());
if (LoanTransactionType.DISBURSEMENT.getValue().equals(accountTransferDTO.getFromTransferType())) { if (LoanTransactionType.DISBURSEMENT.getValue().equals(accountTransferDTO.getFromTransferType())) {
loanTransaction = this.loanAccountDomainService.makeDisburseTransaction(accountTransferDTO.getFromAccountId(), loanTransaction = this.loanAccountDomainService.makeDisburseTransaction(accountTransferDTO.getFromAccountId(),
accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(), accountTransferDTO.getTxnExternalId()); accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(), externalId);
} else { } else {
loanTransaction = this.loanAccountDomainService.makeRefund(accountTransferDTO.getFromAccountId(), loanTransaction = this.loanAccountDomainService.makeRefund(accountTransferDTO.getFromAccountId(),
new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(), new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(),
accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), accountTransferDTO.getNoteText(),
accountTransferDTO.getTxnExternalId()); externalId);
} }
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
@@ -474,13 +467,18 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
this.loanAccountAssembler.setHelpers(toLoanAccount); this.loanAccountAssembler.setHelpers(toLoanAccount);
} }
ExternalId externalIdForDisbursement = accountTransferDTO.getTxnExternalId();
LoanTransaction disburseTransaction = this.loanAccountDomainService.makeDisburseTransaction(accountTransferDTO.getFromAccountId(), LoanTransaction disburseTransaction = this.loanAccountDomainService.makeDisburseTransaction(accountTransferDTO.getFromAccountId(),
accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(),
accountTransferDTO.getNoteText(), accountTransferDTO.getTxnExternalId(), true); accountTransferDTO.getNoteText(), externalIdForDisbursement, true);
final String chargeRefundChargeType = null; final String chargeRefundChargeType = null;
ExternalId externalIdForRepayment = externalIdFactory.create();
LoanTransaction repayTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT, toLoanAccount, LoanTransaction repayTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT, toLoanAccount,
new CommandProcessingResultBuilder(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getPaymentDetail(),
accountTransferDTO.getPaymentDetail(), null, null, false, chargeRefundChargeType, isAccountTransfer, null, false, true); null, externalIdForRepayment, false, chargeRefundChargeType, isAccountTransfer, null, false, true);
AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToLoanTransfer(accountTransferDTO, AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToLoanTransfer(accountTransferDTO,
fromLoanAccount, toLoanAccount, disburseTransaction, repayTransaction); fromLoanAccount, toLoanAccount, disburseTransaction, repayTransaction);
@@ -541,8 +539,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString()); throw new InvalidPaidInAdvanceAmountException(overpaid.toPlainString());
} }
ExternalId externalId = externalIdFactory.create();
final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefundForActiveLoan(fromLoanAccountId, final LoanTransaction loanRefundTransaction = this.loanAccountDomainService.makeRefundForActiveLoan(fromLoanAccountId,
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null); new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, externalId);
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
@@ -20,39 +20,21 @@ package org.apache.fineract.portfolio.collectionsheet.command;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
/** /**
* Immutable command for Single loan repayment. * Immutable command for Single loan repayment.
*/ */
@Getter
@AllArgsConstructor
public class SingleRepaymentCommand { public class SingleRepaymentCommand {
private final Long loanId; private final Long loanId;
private final ExternalId externalId;
private final BigDecimal transactionAmount; private final BigDecimal transactionAmount;
private final LocalDate transactionDate; private final LocalDate transactionDate;
private final PaymentDetail paymentDetail; private final PaymentDetail paymentDetail;
public SingleRepaymentCommand(final Long loanId, final BigDecimal transactionAmount, final LocalDate transactionDate,
final PaymentDetail paymentDetail) {
this.loanId = loanId;
this.transactionAmount = transactionAmount;
this.transactionDate = transactionDate;
this.paymentDetail = paymentDetail;
}
public Long getLoanId() {
return this.loanId;
}
public BigDecimal getTransactionAmount() {
return this.transactionAmount;
}
public LocalDate getTransactionDate() {
return this.transactionDate;
}
public PaymentDetail getPaymentDetail() {
return this.paymentDetail;
}
} }
@@ -25,10 +25,12 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Locale; import java.util.Locale;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer; import org.apache.fineract.infrastructure.core.serialization.AbstractFromApiJsonDeserializer;
import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer; import org.apache.fineract.infrastructure.core.serialization.FromApiJsonDeserializer;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
import org.apache.fineract.portfolio.collectionsheet.command.SingleRepaymentCommand; import org.apache.fineract.portfolio.collectionsheet.command.SingleRepaymentCommand;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
@@ -90,6 +92,8 @@ public final class CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer
final JsonObject loanTransactionElement = array.get(i).getAsJsonObject(); final JsonObject loanTransactionElement = array.get(i).getAsJsonObject();
final Long loanId = this.fromApiJsonHelper.extractLongNamed("loanId", loanTransactionElement); final Long loanId = this.fromApiJsonHelper.extractLongNamed("loanId", loanTransactionElement);
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", loanTransactionElement);
final ExternalId externalId = ExternalIdFactory.produce(externalIdStr);
final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalNamed("transactionAmount", final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalNamed("transactionAmount",
loanTransactionElement, locale); loanTransactionElement, locale);
PaymentDetail detail = paymentDetail; PaymentDetail detail = paymentDetail;
@@ -97,7 +101,8 @@ public final class CollectionSheetBulkRepaymentCommandFromApiJsonDeserializer
detail = this.paymentDetailAssembler.fetchPaymentDetail(loanTransactionElement); detail = this.paymentDetailAssembler.fetchPaymentDetail(loanTransactionElement);
} }
if (transactionAmount != null && transactionAmount.intValue() > 0) { if (transactionAmount != null && transactionAmount.intValue() > 0) {
loanRepaymentTransactions[i] = new SingleRepaymentCommand(loanId, transactionAmount, transactionDate, detail); loanRepaymentTransactions[i] = new SingleRepaymentCommand(loanId, externalId, transactionAmount, transactionDate,
detail);
} }
} }
} }
@@ -91,7 +91,7 @@ public class CollectionSheetWritePlatformServiceJpaRepositoryImpl implements Col
} }
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
changes.putAll(updateBulkReapayments(command, paymentDetail)); changes.putAll(updateBulkRepayments(command, paymentDetail));
changes.putAll(updateBulkDisbursals(command)); changes.putAll(updateBulkDisbursals(command));
@@ -122,7 +122,7 @@ public class CollectionSheetWritePlatformServiceJpaRepositoryImpl implements Col
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
changes.putAll(updateBulkReapayments(command, paymentDetail)); changes.putAll(updateBulkRepayments(command, paymentDetail));
changes.putAll(updateBulkDisbursals(command)); changes.putAll(updateBulkDisbursals(command));
@@ -135,7 +135,7 @@ public class CollectionSheetWritePlatformServiceJpaRepositoryImpl implements Col
.with(changes).with(changes).build(); .with(changes).with(changes).build();
} }
private Map<String, Object> updateBulkReapayments(final JsonCommand command, final PaymentDetail paymentDetail) { private Map<String, Object> updateBulkRepayments(final JsonCommand command, final PaymentDetail paymentDetail) {
final Map<String, Object> changes = new HashMap<>(); final Map<String, Object> changes = new HashMap<>();
final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand = this.bulkRepaymentCommandFromApiJsonDeserializer final CollectionSheetBulkRepaymentCommand bulkRepaymentCommand = this.bulkRepaymentCommandFromApiJsonDeserializer
.commandFromApiJson(command.json(), paymentDetail); .commandFromApiJson(command.json(), paymentDetail);
@@ -270,6 +270,12 @@ final class LoanChargesApiResourceSwagger {
public Long savingsId; public Long savingsId;
@Schema(example = "12") @Schema(example = "12")
public Long resourceId; public Long resourceId;
@Schema(example = "12")
public Long subResourceId;
@Schema(example = "786444UUUYYH7")
public String resourceExternalId;
@Schema(example = "786444UUUYYH7")
public String subResourceExternalId;
public PostLoansLoanIdChargesChargeIdChanges changes; public PostLoansLoanIdChargesChargeIdChanges changes;
static final class PostLoansLoanIdChargesChargeIdChanges { static final class PostLoansLoanIdChargesChargeIdChanges {
@@ -288,6 +294,21 @@ final class LoanChargesApiResourceSwagger {
public Double amount; public Double amount;
@Schema(example = "786444UUUYYH7") @Schema(example = "786444UUUYYH7")
public String externalId; public String externalId;
@Schema(example = "100.00")
public Double principalPortion;
@Schema(example = "100.00")
public Double interestPortion;
@Schema(example = "100.00")
public Double feeChargesPortion;
@Schema(example = "100.00")
public Double penaltyChargesPortion;
@Schema(example = "100.00")
public Double outstandingLoanBalance;
@Schema(example = "19 September 2013")
public Double date;
@Schema(example = "1")
public Long id;
} }
} }
@@ -51,11 +51,13 @@ import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException; import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.CommandParameterUtil; import org.apache.fineract.infrastructure.core.service.CommandParameterUtil;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData; import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
@@ -67,7 +69,7 @@ import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatform
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Path("/loans/{loanId}/transactions") @Path("/loans")
@Component @Component
@AllArgsConstructor @AllArgsConstructor
@Scope("singleton") @Scope("singleton")
@@ -88,7 +90,7 @@ public class LoanTransactionsApiResource {
private final LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService; private final LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService;
@GET @GET
@Path("template") @Path("{loanId}/transactions/template")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Loan Transaction Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" @Operation(summary = "Retrieve Loan Transaction Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
@@ -104,109 +106,115 @@ public class LoanTransactionsApiResource {
+ "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)") + "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)")
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
public String retrieveTransactionTemplate(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, public String retrieveTransactionTemplate(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo, @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo,
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String dateFormat, @QueryParam("dateFormat") @Parameter(description = "dateFormat") final String dateFormat,
@QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam, @QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam,
@QueryParam("locale") @Parameter(description = "locale") final String locale) { @QueryParam("locale") @Parameter(description = "locale") final String locale) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); return retrieveTransactionTemplate(loanId, null, commandParam, uriInfo, dateFormat, transactionDateParam, locale);
LoanTransactionData transactionData = null;
if (CommandParameterUtil.is(commandParam, "repayment")) {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(loanId);
} else if (CommandParameterUtil.is(commandParam, "merchantIssuedRefund")) {
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.MERCHANT_ISSUED_REFUND,
loanId, transactionDate);
} else if (CommandParameterUtil.is(commandParam, "payoutRefund")) {
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.PAYOUT_REFUND, loanId,
transactionDate);
} else if (CommandParameterUtil.is(commandParam, "goodwillCredit")) {
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.GOODWILL_CREDIT, loanId,
transactionDate);
} else if (CommandParameterUtil.is(commandParam, "waiveinterest")) {
transactionData = this.loanReadPlatformService.retrieveWaiveInterestDetails(loanId);
} else if (CommandParameterUtil.is(commandParam, "writeoff")) {
transactionData = this.loanReadPlatformService.retrieveLoanWriteoffTemplate(loanId);
} else if (CommandParameterUtil.is(commandParam, "close-rescheduled")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (CommandParameterUtil.is(commandParam, "close")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (CommandParameterUtil.is(commandParam, "disburse")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
transactionData.setNumberOfRepayments(this.loanReadPlatformService.retrieveNumberOfRepayments(loanId));
final List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallmentData = this.loanReadPlatformService
.getRepaymentDataResponse(loanId);
transactionData.setLoanRepaymentScheduleInstallments(loanRepaymentScheduleInstallmentData);
} else if (CommandParameterUtil.is(commandParam, "disburseToSavings")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false);
} else if (CommandParameterUtil.is(commandParam, "recoverypayment")) {
transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId);
} else if (CommandParameterUtil.is(commandParam, "prepayLoan")) {
LocalDate transactionDate = null;
if (transactionDateParam == null) {
transactionDate = DateUtils.getBusinessLocalDate();
} else {
transactionDate = transactionDateParam.getDate("transactionDate", dateFormat, locale);
}
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanId,
transactionDate);
} else if (CommandParameterUtil.is(commandParam, "refundbycash")) {
transactionData = this.loanReadPlatformService.retrieveRefundByCashTemplate(loanId);
} else if (CommandParameterUtil.is(commandParam, "refundbytransfer")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
} else if (CommandParameterUtil.is(commandParam, "foreclosure")) {
LocalDate transactionDate = null;
if (transactionDateParam == null) {
transactionDate = DateUtils.getBusinessLocalDate();
} else {
transactionDate = transactionDateParam.getDate("transactionDate", dateFormat, locale);
}
transactionData = this.loanReadPlatformService.retrieveLoanForeclosureTemplate(loanId, transactionDate);
} else if (CommandParameterUtil.is(commandParam, "creditBalanceRefund")) {
transactionData = this.loanReadPlatformService.retrieveCreditBalanceRefundTemplate(loanId);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transactionData, this.responseDataParameters);
} }
@GET @GET
@Path("{transactionId}") @Path("external-id/{loanExternalId}/transactions/template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Loan Transaction Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n"
+ "\n" + "Field Defaults\n" + "Allowed Value Lists\n\n" + "Example Requests:\n" + "\n"
+ "loans/1/transactions/template?command=repayment" + "loans/1/transactions/template?command=merchantIssuedRefund"
+ "loans/1/transactions/template?command=payoutRefund" + "loans/1/transactions/template?command=goodwillCredit" + "\n"
+ "loans/1/transactions/template?command=waiveinterest" + "\n" + "loans/1/transactions/template?command=writeoff" + "\n"
+ "loans/1/transactions/template?command=close-rescheduled" + "\n" + "loans/1/transactions/template?command=close" + "\n"
+ "loans/1/transactions/template?command=disburse" + "\n" + "loans/1/transactions/template?command=disburseToSavings" + "\n"
+ "loans/1/transactions/template?command=recoverypayment" + "\n" + "loans/1/transactions/template?command=prepayLoan" + "\n"
+ "loans/1/transactions/template?command=refundbycash" + "\n" + "loans/1/transactions/template?command=refundbytransfer" + "\n"
+ "loans/1/transactions/template?command=foreclosure" + "\n"
+ "loans/1/transactions/template?command=creditBalanceRefund (returned 'amount' field will have the overpaid value)")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTemplateResponse.class))) })
public String retrieveTransactionTemplate(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo,
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String dateFormat,
@QueryParam("transactionDate") @Parameter(description = "transactionDate") final DateParam transactionDateParam,
@QueryParam("locale") @Parameter(description = "locale") final String locale) {
return retrieveTransactionTemplate(null, loanExternalId, commandParam, uriInfo, dateFormat, transactionDateParam, locale);
}
@GET
@Path("{loanId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Transaction Details", description = "Retrieves a Transaction Details\n\n" + "Example Request:\n" + "\n" @Operation(summary = "Retrieve a Transaction Details", description = "Retrieves a Transaction Details\n\n" + "Example Request:\n" + "\n"
+ "loans/5/transactions/3") + "loans/5/transactions/3")
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTransactionIdResponse.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTransactionIdResponse.class))) })
public String retrieveTransaction(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, public String retrieveTransaction(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId, @PathParam("transactionId") @Parameter(description = "transactionId", required = true) final Long transactionId,
@QueryParam("fields") @Parameter(in = ParameterIn.QUERY, name = "fields", description = "Optional Loan Transaction attribute list to be in the response", required = false, example = "id,date,amount") final String fields, @QueryParam("fields") @Parameter(in = ParameterIn.QUERY, name = "fields", description = "Optional Loan Transaction attribute list to be in the response", required = false, example = "id,date,amount") final String fields,
@Context final UriInfo uriInfo) { @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); return retrieveTransaction(loanId, null, transactionId, null, uriInfo);
}
LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransaction(loanId, transactionId); @GET
transactionData.setLoanChargePaidByList(this.loanChargePaidByReadPlatformService.getLoanChargesPaidByTransactionId(transactionId)); @Path("{loanId}/transactions/external-id/{externalTransactionId}")
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); @Consumes({ MediaType.APPLICATION_JSON })
if (settings.isTemplate()) { @Produces({ MediaType.APPLICATION_JSON })
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); @Operation(summary = "Retrieve a Transaction Details", description = "Retrieves a Transaction Details\n\n" + "Example Request:\n" + "\n"
transactionData = LoanTransactionData.templateOnTop(transactionData, paymentTypeOptions); + "loans/5/transactions/external-id/5dd80a7c-ccba-4446-b378-01eb6f53e871")
} @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTransactionIdResponse.class))) })
public String retrieveTransactionByTransactionExternalId(
@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@PathParam("externalTransactionId") @Parameter(description = "externalTransactionId", required = true) final String externalTransactionId,
@QueryParam("fields") @Parameter(in = ParameterIn.QUERY, name = "fields", description = "Optional Loan Transaction attribute list to be in the response", required = false, example = "id,date,amount") final String fields,
@Context final UriInfo uriInfo) {
return this.toApiJsonSerializer.serialize(settings, transactionData, this.responseDataParameters); return retrieveTransaction(loanId, null, null, externalTransactionId, uriInfo);
}
@GET
@Path("external-id/{loanExternalId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Transaction Details", description = "Retrieves a Transaction Details\n\n" + "Example Request:\n" + "\n"
+ "loans/5/transactions/3")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTransactionIdResponse.class))) })
public String retrieveTransactionByLoanExternalIdAndTransactionId(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@PathParam("transactionId") @Parameter(description = "transactionId", required = true) final Long transactionId,
@QueryParam("fields") @Parameter(in = ParameterIn.QUERY, name = "fields", description = "Optional Loan Transaction attribute list to be in the response", required = false, example = "id,date,amount") final String fields,
@Context final UriInfo uriInfo) {
return retrieveTransaction(null, loanExternalId, transactionId, null, uriInfo);
}
@GET
@Path("external-id/{loanExternalId}/transactions/external-id/{externalTransactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a Transaction Details", description = "Retrieves a Transaction Details\n\n" + "Example Request:\n" + "\n"
+ "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions/external-id/5dd80a7c-ccba-4446-b378-01eb6f53e871")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.GetLoansLoanIdTransactionsTransactionIdResponse.class))) })
public String retrieveTransactionByLoanExternalIdAndTransactionExternalId(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@PathParam("externalTransactionId") @Parameter(description = "externalTransactionId", required = true) final String externalTransactionId,
@QueryParam("fields") @Parameter(in = ParameterIn.QUERY, name = "fields", description = "Optional Loan Transaction attribute list to be in the response", required = false, example = "id,date,amount") final String fields,
@Context final UriInfo uriInfo) {
return retrieveTransaction(null, loanExternalId, null, externalTransactionId, uriInfo);
} }
@POST @POST
@Path("{loanId}/transactions")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Significant Loan Transactions", description = "This API covers the major loan transaction functionality\n\n" @Operation(summary = "Significant Loan Transactions", description = "This API covers the major loan transaction functionality\n\n"
+ "Example Requests:\n" + "\n" + "loans/1/transactions?command=repayment" + " | Make a Repayment | \n" + "Example Requests:\n\n" + "loans/1/transactions?command=repayment" + " | Make a Repayment | \n"
+ "loans/1/transactions?command=merchantIssuedRefund" + " | Merchant Issued Refund | \n" + "loans/1/transactions?command=merchantIssuedRefund" + " | Merchant Issued Refund | \n"
+ "loans/1/transactions?command=payoutRefund" + " | Payout Refund | \n" + "loans/1/transactions?command=goodwillCredit" + "loans/1/transactions?command=payoutRefund" + " | Payout Refund | \n" + "loans/1/transactions?command=goodwillCredit"
+ " | Goodwil Credit | \n" + "loans/1/transactions?command=chargeRefund" + " | Charge Refund | \n" + " | Goodwil Credit | \n" + "loans/1/transactions?command=chargeRefund" + " | Charge Refund | \n"
@@ -220,41 +228,234 @@ public class LoanTransactionsApiResource {
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsRequest.class))) @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsRequest.class)))
@ApiResponses({ @ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsResponse.class))) }) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsResponse.class))) })
public String executeLoanTransaction(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, public String executeLoanTransaction(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@QueryParam("command") @Parameter(description = "command") final String commandParam, @QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) { @Parameter(hidden = true) final String apiRequestBodyAsJson) {
return executeTransaction(loanId, null, commandParam, apiRequestBodyAsJson);
}
@POST
@Path("external-id/{loanExternalId}/transactions")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Significant Loan Transactions", description = "This API covers the major loan transaction functionality\n\n"
+ "Example Requests:\n\n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=repayment"
+ " | Make a Repayment | \n"
+ "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=merchantIssuedRefund"
+ " | Merchant Issued Refund | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=payoutRefund"
+ " | Payout Refund | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=goodwillCredit"
+ " | Goodwil Credit | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=chargeRefund"
+ " | Charge Refund | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=waiveinterest"
+ " | Waive Interest | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=writeoff"
+ " | Write-off Loan | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=close-rescheduled"
+ " | Close Rescheduled Loan | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=close"
+ " | Close Loan | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=undowriteoff"
+ " | Undo Loan Write-off | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=recoverypayment"
+ " | Make Recovery Payment | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=refundByCash"
+ " | Make a Refund of an Active Loan by Cash | \n"
+ "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=foreclosure"
+ " | Foreclosure of an Active Loan | \n"
+ "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=creditBalanceRefund" + " | Credit Balance Refund"
+ " | \n")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsResponse.class))) })
public String executeLoanTransaction(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@QueryParam("command") @Parameter(description = "command") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
return executeTransaction(null, loanExternalId, commandParam, apiRequestBodyAsJson);
}
@POST
@Path("{loanId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Adjust a Transaction", description = "Note: there is no need to specify command={transactionType} parameter.\n\n"
+ "Mandatory Fields: transactionDate, transactionAmount")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsResponse.class))) })
public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@PathParam("transactionId") @Parameter(description = "transactionId", required = true) final Long transactionId,
@Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
return adjustTransaction(loanId, null, transactionId, null, apiRequestBodyAsJson, commandParam);
}
@POST
@Path("external-id/{loanExternalId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Adjust a Transaction", description = "Note: there is no need to specify command={transactionType} parameter.\n\n"
+ "Mandatory Fields: transactionDate, transactionAmount")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsResponse.class))) })
public String adjustLoanTransaction(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@PathParam("transactionId") @Parameter(description = "transactionId", required = true) final Long transactionId,
@Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
return adjustTransaction(null, loanExternalId, transactionId, null, apiRequestBodyAsJson, commandParam);
}
@POST
@Path("{loanId}/transactions/external-id/{externalTransactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Adjust a Transaction", description = "Note: there is no need to specify command={transactionType} parameter.\n\n"
+ "Mandatory Fields: transactionDate, transactionAmount")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsResponse.class))) })
public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@PathParam("externalTransactionId") @Parameter(description = "externalTransactionId", required = true) final String transactionExternalId,
@Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
return adjustTransaction(loanId, null, null, transactionExternalId, apiRequestBodyAsJson, commandParam);
}
@POST
@Path("external-id/{loanExternalId}/transactions/external-id/{externalTransactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Adjust a Transaction", description = "Note: there is no need to specify command={transactionType} parameter.\n\n"
+ "Mandatory Fields: transactionDate, transactionAmount")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsResponse.class))) })
public String adjustLoanTransaction(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@PathParam("externalTransactionId") @Parameter(description = "externalTransactionId", required = true) final String transactionExternalId,
@Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
return adjustTransaction(null, loanExternalId, null, transactionExternalId, apiRequestBodyAsJson, commandParam);
}
@PUT
@Path("{loanId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) })
public String undoWaiveCharge(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@PathParam("transactionId") @Parameter(description = "transactionId", required = true) final Long transactionId) {
return undoWaiveCharge(loanId, null, transactionId, null);
}
@PUT
@Path("external-id/{loanExternalId}/transactions/{transactionId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) })
public String undoWaiveCharge(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@PathParam("transactionId") @Parameter(description = "transactionId", required = true) final Long transactionId) {
return undoWaiveCharge(null, loanExternalId, transactionId, null);
}
@PUT
@Path("{loanId}/transactions/external-id/{transactionExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) })
public String undoWaiveCharge(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
@PathParam("transactionExternalId") @Parameter(description = "transactionExternalId", required = true) final String transactionExternalId) {
return undoWaiveCharge(loanId, null, null, transactionExternalId);
}
@PUT
@Path("external-id/{loanExternalId}/transactions/external-id/{transactionExternalId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) })
public String undoWaiveCharge(
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
@PathParam("transactionExternalId") @Parameter(description = "transactionExternalId", required = true) final String transactionExternalId) {
return undoWaiveCharge(null, loanExternalId, null, transactionExternalId);
}
private String retrieveTransaction(final Long loanId, final String loanExternalIdStr, final Long transactionId,
final String transactionExternalIdStr, final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
ExternalId transactionExternalId = ExternalIdFactory.produce(transactionExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
Long resolvedLoanTransactionId = getResolvedLoanTransactionId(transactionId, transactionExternalId);
LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransaction(resolvedLoanId,
resolvedLoanTransactionId);
transactionData.setLoanChargePaidByList(
this.loanChargePaidByReadPlatformService.getLoanChargesPaidByTransactionId(transactionData.getId()));
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (settings.isTemplate()) {
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
transactionData = LoanTransactionData.templateOnTop(transactionData, paymentTypeOptions);
}
return this.toApiJsonSerializer.serialize(settings, transactionData, this.responseDataParameters);
}
private String executeTransaction(final Long loanId, final String loanExternalIdStr, final String commandParam,
final String apiRequestBodyAsJson) {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
CommandWrapper commandRequest = null; CommandWrapper commandRequest = null;
if (CommandParameterUtil.is(commandParam, "repayment")) { if (CommandParameterUtil.is(commandParam, "repayment")) {
commandRequest = builder.loanRepaymentTransaction(loanId).build(); commandRequest = builder.loanRepaymentTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "merchantIssuedRefund")) { } else if (CommandParameterUtil.is(commandParam, "merchantIssuedRefund")) {
commandRequest = builder.loanMerchantIssuedRefundTransaction(loanId).build(); commandRequest = builder.loanMerchantIssuedRefundTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "payoutRefund")) { } else if (CommandParameterUtil.is(commandParam, "payoutRefund")) {
commandRequest = builder.loanPayoutRefundTransaction(loanId).build(); commandRequest = builder.loanPayoutRefundTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "goodwillCredit")) { } else if (CommandParameterUtil.is(commandParam, "goodwillCredit")) {
commandRequest = builder.loanGoodwillCreditTransaction(loanId).build(); commandRequest = builder.loanGoodwillCreditTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "chargeRefund")) { } else if (CommandParameterUtil.is(commandParam, "chargeRefund")) {
commandRequest = builder.refundLoanCharge(loanId).build(); commandRequest = builder.refundLoanCharge(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "waiveinterest")) { } else if (CommandParameterUtil.is(commandParam, "waiveinterest")) {
commandRequest = builder.waiveInterestPortionTransaction(loanId).build(); commandRequest = builder.waiveInterestPortionTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "writeoff")) { } else if (CommandParameterUtil.is(commandParam, "writeoff")) {
commandRequest = builder.writeOffLoanTransaction(loanId).build(); commandRequest = builder.writeOffLoanTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "close-rescheduled")) { } else if (CommandParameterUtil.is(commandParam, "close-rescheduled")) {
commandRequest = builder.closeLoanAsRescheduledTransaction(loanId).build(); commandRequest = builder.closeLoanAsRescheduledTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "close")) { } else if (CommandParameterUtil.is(commandParam, "close")) {
commandRequest = builder.closeLoanTransaction(loanId).build(); commandRequest = builder.closeLoanTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "undowriteoff")) { } else if (CommandParameterUtil.is(commandParam, "undowriteoff")) {
commandRequest = builder.undoWriteOffLoanTransaction(loanId).build(); commandRequest = builder.undoWriteOffLoanTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "recoverypayment")) { } else if (CommandParameterUtil.is(commandParam, "recoverypayment")) {
commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build(); commandRequest = builder.loanRecoveryPaymentTransaction(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "refundByCash")) { } else if (CommandParameterUtil.is(commandParam, "refundByCash")) {
commandRequest = builder.refundLoanTransactionByCash(loanId).build(); commandRequest = builder.refundLoanTransactionByCash(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "foreclosure")) { } else if (CommandParameterUtil.is(commandParam, "foreclosure")) {
commandRequest = builder.loanForeclosure(loanId).build(); commandRequest = builder.loanForeclosure(resolvedLoanId).build();
} else if (CommandParameterUtil.is(commandParam, "creditBalanceRefund")) { } else if (CommandParameterUtil.is(commandParam, "creditBalanceRefund")) {
commandRequest = builder.creditBalanceRefund(loanId).build(); commandRequest = builder.creditBalanceRefund(resolvedLoanId).build();
} }
if (commandRequest == null) { if (commandRequest == null) {
@@ -264,27 +465,92 @@ public class LoanTransactionsApiResource {
return this.toApiJsonSerializer.serialize(result); return this.toApiJsonSerializer.serialize(result);
} }
@POST private String retrieveTransactionTemplate(Long loanId, String loanExternalIdStr, String commandParam, UriInfo uriInfo,
@Path("{transactionId}") String dateFormat, DateParam transactionDateParam, String locale) {
@Consumes({ MediaType.APPLICATION_JSON }) this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Adjust a Transaction", description = "Note: there is no need to specify command={transactionType} parameter.\n\n"
+ "Mandatory Fields: transactionDate, transactionAmount")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdResponse.class))) })
public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId,
@Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("command") @Parameter(description = "command") final String commandParam) {
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
LoanTransactionData transactionData;
if (CommandParameterUtil.is(commandParam, "repayment")) {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId);
} else if (CommandParameterUtil.is(commandParam, "merchantIssuedRefund")) {
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.MERCHANT_ISSUED_REFUND,
resolvedLoanId, transactionDate);
} else if (CommandParameterUtil.is(commandParam, "payoutRefund")) {
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.PAYOUT_REFUND, resolvedLoanId,
transactionDate);
} else if (CommandParameterUtil.is(commandParam, "goodwillCredit")) {
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.GOODWILL_CREDIT,
resolvedLoanId, transactionDate);
} else if (CommandParameterUtil.is(commandParam, "waiveinterest")) {
transactionData = this.loanReadPlatformService.retrieveWaiveInterestDetails(resolvedLoanId);
} else if (CommandParameterUtil.is(commandParam, "writeoff")) {
transactionData = this.loanReadPlatformService.retrieveLoanWriteoffTemplate(resolvedLoanId);
} else if (CommandParameterUtil.is(commandParam, "close-rescheduled")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (CommandParameterUtil.is(commandParam, "close")) {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (CommandParameterUtil.is(commandParam, "disburse")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(resolvedLoanId, true);
transactionData.setNumberOfRepayments(this.loanReadPlatformService.retrieveNumberOfRepayments(resolvedLoanId));
final List<LoanRepaymentScheduleInstallmentData> loanRepaymentScheduleInstallmentData = this.loanReadPlatformService
.getRepaymentDataResponse(resolvedLoanId);
transactionData.setLoanRepaymentScheduleInstallments(loanRepaymentScheduleInstallmentData);
} else if (CommandParameterUtil.is(commandParam, "disburseToSavings")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(resolvedLoanId, false);
} else if (CommandParameterUtil.is(commandParam, "recoverypayment")) {
transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(resolvedLoanId);
} else if (CommandParameterUtil.is(commandParam, "prepayLoan")) {
LocalDate transactionDate;
if (transactionDateParam == null) {
transactionDate = DateUtils.getBusinessLocalDate();
} else {
transactionDate = transactionDateParam.getDate("transactionDate", dateFormat, locale);
}
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, resolvedLoanId,
transactionDate);
} else if (CommandParameterUtil.is(commandParam, "refundbycash")) {
transactionData = this.loanReadPlatformService.retrieveRefundByCashTemplate(resolvedLoanId);
} else if (CommandParameterUtil.is(commandParam, "refundbytransfer")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(resolvedLoanId, true);
} else if (CommandParameterUtil.is(commandParam, "foreclosure")) {
LocalDate transactionDate;
if (transactionDateParam == null) {
transactionDate = DateUtils.getBusinessLocalDate();
} else {
transactionDate = transactionDateParam.getDate("transactionDate", dateFormat, locale);
}
transactionData = this.loanReadPlatformService.retrieveLoanForeclosureTemplate(resolvedLoanId, transactionDate);
} else if (CommandParameterUtil.is(commandParam, "creditBalanceRefund")) {
transactionData = this.loanReadPlatformService.retrieveCreditBalanceRefundTemplate(resolvedLoanId);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, transactionData, this.responseDataParameters);
}
private String adjustTransaction(final Long loanId, final String loanExternalIdStr, final Long transactionId,
final String transactionExternalIdStr, final String apiRequestBodyAsJson, final String commandParam) {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
CommandWrapper commandRequest = null; ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
ExternalId transactionExternalId = ExternalIdFactory.produce(transactionExternalIdStr);
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
Long resolvedTransactionId = getResolvedLoanTransactionId(transactionId, transactionExternalId);
CommandWrapper commandRequest;
if (CommandParameterUtil.is(commandParam, LoanApiConstants.CHARGEBACK_TRANSACTION_COMMAND)) { if (CommandParameterUtil.is(commandParam, LoanApiConstants.CHARGEBACK_TRANSACTION_COMMAND)) {
commandRequest = builder.chargebackTransaction(loanId, transactionId).build(); commandRequest = builder.chargebackTransaction(resolvedLoanId, resolvedTransactionId).build();
} else { // Default to adjust the Loan Transaction } else { // Default to adjust the Loan Transaction
commandRequest = builder.adjustTransaction(loanId, transactionId).build(); commandRequest = builder.adjustTransaction(resolvedLoanId, resolvedTransactionId).build();
} }
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
@@ -292,19 +558,34 @@ public class LoanTransactionsApiResource {
return this.toApiJsonSerializer.serialize(result); return this.toApiJsonSerializer.serialize(result);
} }
@PUT private String undoWaiveCharge(final Long loanId, final String loanExternalIdStr, final Long transactionId,
@Path("{transactionId}") final String transactionExternalIdStr) {
@Consumes({ MediaType.APPLICATION_JSON }) ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
@Produces({ MediaType.APPLICATION_JSON }) ExternalId transactionExternalId = ExternalIdFactory.produce(transactionExternalIdStr);
@Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) })
public String undoWaiveCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
@PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().undoWaiveChargeTransaction(loanId, transactionId).build(); Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
Long resolvedTransactionId = getResolvedLoanTransactionId(transactionId, transactionExternalId);
final CommandWrapper commandRequest = new CommandWrapperBuilder().undoWaiveChargeTransaction(resolvedLoanId, resolvedTransactionId)
.build();
CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result); return this.toApiJsonSerializer.serialize(result);
} }
private Long getResolvedLoanTransactionId(final Long transactionId, final ExternalId externalTransactionId) {
Long resolvedLoanTransactionId = transactionId;
if (resolvedLoanTransactionId == null) {
externalTransactionId.throwExceptionIfEmpty();
resolvedLoanTransactionId = this.loanReadPlatformService.retrieveLoanTransactionIdByExternalId(externalTransactionId);
}
return resolvedLoanTransactionId;
}
private Long getResolvedLoanId(final Long loanId, final ExternalId loanExternalId) {
Long resolvedLoanId = loanId;
if (resolvedLoanId == null) {
loanExternalId.throwExceptionIfEmpty();
resolvedLoanId = this.loanReadPlatformService.retrieveLoanIdByExternalId(loanExternalId.getValue());
}
return resolvedLoanId;
}
} }
@@ -89,6 +89,12 @@ final class LoanTransactionsApiResourceSwagger {
@Schema(example = "2") @Schema(example = "2")
public Long id; public Long id;
@Schema(example = "3e7791ce-aa10-11ec-b909-0242ac120002")
public String externalId;
@Schema(example = "2")
public Long loanId;
@Schema(example = "3e7791ce-aa10-11ec-b909-0242ac120002")
public String externalLoanId;
@Schema(example = "loanTransactionType.repayment") @Schema(example = "loanTransactionType.repayment")
public String code; public String code;
@Schema(example = "Repayment") @Schema(example = "Repayment")
@@ -250,6 +256,8 @@ final class LoanTransactionsApiResourceSwagger {
public String bankNumber; public String bankNumber;
@Schema(example = "3") @Schema(example = "3")
public Integer loanChargeId; public Integer loanChargeId;
@Schema(example = "28 June 2022")
public String dueDate;
} }
@@ -262,8 +270,37 @@ final class LoanTransactionsApiResourceSwagger {
public Long officeId; public Long officeId;
@Schema(example = "1") @Schema(example = "1")
public Long clientId; public Long clientId;
@Schema(example = "1")
public Long loanId;
@Schema(example = "22") @Schema(example = "22")
public Long resourceId; public Long resourceId;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String resourceExternalId;
@Schema(example = "22")
public Long subResourceId;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String subResourceExternalId;
public PostLoansLoanIdTransactionsResponse.Changes changes;
static final class Changes {
@Schema(example = "en_GB")
public String locale;
@Schema(example = "dd MMMM yyyy")
public String dateFormat;
@Schema(example = "28 June 2022")
public String transactionDate;
@Schema(example = "50,000.00")
public Double transactionAmount;
@Schema(example = "An optional note about why your adjusting or changing the transaction.")
public String note;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String reversalExternalId;
@Schema(example = "1")
public Long paymentTypeId;
@Schema(example = "4ff9b1cb988b7")
public String externalId;
}
} }
@Schema(description = "PostLoansLoanIdTransactionsTransactionIdRequest") @Schema(description = "PostLoansLoanIdTransactionsTransactionIdRequest")
@@ -289,15 +326,6 @@ final class LoanTransactionsApiResourceSwagger {
public String externalId; public String externalId;
} }
@Schema(description = "PostLoansLoanIdTransactionsTransactionIdResponse")
public static final class PostLoansLoanIdTransactionsTransactionIdResponse {
private PostLoansLoanIdTransactionsTransactionIdResponse() {}
@Schema(example = "16")
public Long resourceId;
}
@Schema(description = "PutChargeTransactionChangesResponse") @Schema(description = "PutChargeTransactionChangesResponse")
public static final class PutChargeTransactionChangesResponse { public static final class PutChargeTransactionChangesResponse {
@@ -305,16 +333,41 @@ final class LoanTransactionsApiResourceSwagger {
static final class Changes { static final class Changes {
private Changes() {} @Schema(example = "10.0")
public Double amount;
@Schema(example = "10.0")
public Double principalPortion;
@Schema(example = "10.0")
public Double interestPortion;
@Schema(example = "10.0")
public Double feeChargesPortion;
@Schema(example = "10.0")
public Double penaltyChargesPortion;
@Schema(example = "10.0")
public Double outstandingLoanBalance;
@Schema(example = "1")
public Long id;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String externalId;
@Schema(example = "28 June 2022")
public String date;
@Schema(example = "amount")
public String amount;
} }
@Schema(example = "1") @Schema(example = "1")
public Long resourceId; public Long officeId;
@Schema(example = "48") @Schema(example = "1")
public Long clientId;
@Schema(example = "1")
public Long loanId; public Long loanId;
@Schema(example = "22")
public Long resourceId;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String resourceExternalId;
@Schema(example = "22")
public Long subResourceId;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
public String subResourceExternalId;
public PutChargeTransactionChangesResponse.Changes changes; public PutChargeTransactionChangesResponse.Changes changes;
} }
@@ -24,6 +24,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import lombok.Getter; import lombok.Getter;
import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.account.data.AccountTransferData; import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData; import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
@@ -56,7 +57,7 @@ public class LoanTransactionData {
private final BigDecimal penaltyChargesPortion; private final BigDecimal penaltyChargesPortion;
private final BigDecimal overpaymentPortion; private final BigDecimal overpaymentPortion;
private final BigDecimal unrecognizedIncomePortion; private final BigDecimal unrecognizedIncomePortion;
private final String externalId; private final ExternalId externalId;
private final AccountTransferData transfer; private final AccountTransferData transfer;
private final BigDecimal fixedEmiAmount; private final BigDecimal fixedEmiAmount;
private final BigDecimal outstandingLoanBalance; private final BigDecimal outstandingLoanBalance;
@@ -212,7 +213,7 @@ public class LoanTransactionData {
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount, final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
final String externalId, final AccountTransferData transfer, BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, final ExternalId externalId, final AccountTransferData transfer, BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
final BigDecimal unrecognizedIncomePortion, final boolean manuallyReversed, Long loanId, String externalLoanId) { final BigDecimal unrecognizedIncomePortion, final boolean manuallyReversed, Long loanId, String externalLoanId) {
this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount, principalPortion, this(id, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount, principalPortion,
interestPortion, feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, null, externalId, interestPortion, feeChargesPortion, penaltyChargesPortion, overpaymentPortion, unrecognizedIncomePortion, null, externalId,
@@ -223,7 +224,7 @@ public class LoanTransactionData {
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount, final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
BigDecimal unrecognizedIncomePortion, final Collection<PaymentTypeData> paymentTypeOptions, final String externalId, BigDecimal unrecognizedIncomePortion, final Collection<PaymentTypeData> paymentTypeOptions, final ExternalId externalId,
final AccountTransferData transfer, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, final AccountTransferData transfer, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
boolean manuallyReversed, Long loanId, String externalLoanId) { boolean manuallyReversed, Long loanId, String externalLoanId) {
this(id, externalLoanId, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount, this(id, externalLoanId, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount,
@@ -236,7 +237,7 @@ public class LoanTransactionData {
final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount, final PaymentDetailData paymentDetailData, final CurrencyData currency, final LocalDate date, final BigDecimal amount,
final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
final BigDecimal unrecognizedIncomePortion, final String externalId, final AccountTransferData transfer, final BigDecimal unrecognizedIncomePortion, final ExternalId externalId, final AccountTransferData transfer,
BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, LocalDate submittedOnDate, final boolean manuallyReversed, BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, LocalDate submittedOnDate, final boolean manuallyReversed,
final String reversalExternalId, final LocalDate reversedOnDate, Long loanId, String externalLoanId) { final String reversalExternalId, final LocalDate reversedOnDate, Long loanId, String externalLoanId) {
this(id, externalLoanId, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount, this(id, externalLoanId, officeId, officeName, transactionType, paymentDetailData, currency, date, amount, netDisbursalAmount,
@@ -250,7 +251,7 @@ public class LoanTransactionData {
final LocalDate date, final BigDecimal amount, final BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final LocalDate date, final BigDecimal amount, final BigDecimal netDisbursalAmount, final BigDecimal principalPortion,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
final BigDecimal overpaymentPortion, final BigDecimal unrecognizedIncomePortion, final BigDecimal overpaymentPortion, final BigDecimal unrecognizedIncomePortion,
final Collection<PaymentTypeData> paymentTypeOptions, final String externalId, final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions, final ExternalId externalId, final AccountTransferData transfer,
final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, final LocalDate submittedOnDate, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, final LocalDate submittedOnDate,
final boolean manuallyReversed, final String reversalExternalId, final LocalDate reversedOnDate, Long loanId) { final boolean manuallyReversed, final String reversalExternalId, final LocalDate reversedOnDate, Long loanId) {
this.id = id; this.id = id;
@@ -308,7 +309,7 @@ public class LoanTransactionData {
final BigDecimal feeChargesPortion = null; final BigDecimal feeChargesPortion = null;
final BigDecimal penaltyChargesPortion = null; final BigDecimal penaltyChargesPortion = null;
final BigDecimal overpaymentPortion = null; final BigDecimal overpaymentPortion = null;
final String externalId = null; final ExternalId externalId = ExternalId.empty();
final BigDecimal outstandingLoanBalance = null; final BigDecimal outstandingLoanBalance = null;
final AccountTransferData transfer = null; final AccountTransferData transfer = null;
final LocalDate submittedOnDate = null; final LocalDate submittedOnDate = null;
@@ -326,8 +327,9 @@ public class LoanTransactionData {
BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion, BigDecimal netDisbursalAmount, final BigDecimal principalPortion, final BigDecimal interestPortion,
final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion, final BigDecimal overpaymentPortion,
BigDecimal unrecognizedIncomePortion, Collection<PaymentTypeData> paymentOptions, final AccountTransferData transfer, BigDecimal unrecognizedIncomePortion, Collection<PaymentTypeData> paymentOptions, final AccountTransferData transfer,
final String externalId, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance, final LocalDate submittedOnDate, final ExternalId externalId, final BigDecimal fixedEmiAmount, BigDecimal outstandingLoanBalance,
final boolean manuallyReversed, final LocalDate possibleNextRepaymentDate, Long loanId, String externalLoanId) { final LocalDate submittedOnDate, final boolean manuallyReversed, final LocalDate possibleNextRepaymentDate, Long loanId,
String externalLoanId) {
this.id = id; this.id = id;
this.loanId = loanId; this.loanId = loanId;
this.externalLoanId = externalLoanId; this.externalLoanId = externalLoanId;
@@ -61,9 +61,11 @@ import javax.persistence.UniqueConstraint;
import javax.persistence.Version; import javax.persistence.Version;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper; import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -719,9 +721,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
transactionDate = currentDate; transactionDate = currentDate;
} }
} }
ExternalId externalId = ExternalId.empty();
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount, final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(), chargeAmount,
transactionDate, feeCharges, penaltyCharges); transactionDate, feeCharges, penaltyCharges, externalId);
Integer installmentNumber = null; Integer installmentNumber = null;
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge,
@@ -1014,7 +1019,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge) { final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge,
final ExternalId externalId) {
validateLoanIsNotClosed(loanCharge); validateLoanIsNotClosed(loanCharge);
@@ -1066,7 +1072,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived, transactionDate, final LoanTransaction waiveLoanChargeTransaction = LoanTransaction.waiveLoanCharge(this, getOffice(), amountWaived, transactionDate,
feeChargesWaived, penaltyChargesWaived, unrecognizedIncome); feeChargesWaived, penaltyChargesWaived, unrecognizedIncome, externalId);
final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge, final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(waiveLoanChargeTransaction, loanCharge,
waiveLoanChargeTransaction.getAmount(getCurrency()).getAmount(), loanInstallmentNumber); waiveLoanChargeTransaction.getAmount(getCurrency()).getAmount(), loanInstallmentNumber);
waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy); waiveLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
@@ -1332,12 +1338,18 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals) { private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals) {
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
ExternalId externalId = ExternalId.empty();
boolean isExternalIdAutoGenerationEnabled = TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled();
for (LoanTransaction loanTransaction : accruals) { for (LoanTransaction loanTransaction : accruals) {
if (loanTransaction.getInterestPortion(getCurrency()).isGreaterThanZero()) { if (loanTransaction.getInterestPortion(getCurrency()).isGreaterThanZero()) {
if (loanTransaction.getInterestPortion(getCurrency()).isNotEqualTo(interestApplied)) { if (loanTransaction.getInterestPortion(getCurrency()).isNotEqualTo(interestApplied)) {
loanTransaction.reverse(); loanTransaction.reverse();
if (isExternalIdAutoGenerationEnabled) {
externalId = ExternalId.generate();
}
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied, final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
getDisbursementDate()); getDisbursementDate(), externalId);
addLoanTransaction(interestAppliedTransaction); addLoanTransaction(interestAppliedTransaction);
} }
} else { } else {
@@ -2457,8 +2469,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (((isMultiDisburmentLoan() && getDisbursedLoanDisbursementDetails().size() == 1) || !isMultiDisburmentLoan()) if (((isMultiDisburmentLoan() && getDisbursedLoanDisbursementDetails().size() == 1) || !isMultiDisburmentLoan())
&& isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { && isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
ExternalId externalId = ExternalId.empty();
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied, final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
actualDisbursementDate); actualDisbursementDate, externalId);
addLoanTransaction(interestAppliedTransaction); addLoanTransaction(interestAppliedTransaction);
} }
@@ -3415,6 +3431,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
private void processIncomeAccrualTransactionOnLoanClosure() { private void processIncomeAccrualTransactionOnLoanClosure() {
if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction() if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
&& this.getStatus().isClosedObligationsMet()) { && this.getStatus().isClosedObligationsMet()) {
ExternalId externalId = ExternalId.empty();
boolean isExternalIdAutoGenerationEnabled = TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled();
LocalDate closedDate = this.getClosedOnDate(); LocalDate closedDate = this.getClosedOnDate();
reverseTransactionsOnOrAfter(retrieveListOfIncomePostingTransactions(), closedDate); reverseTransactionsOnOrAfter(retrieveListOfIncomePostingTransactions(), closedDate);
reverseTransactionsOnOrAfter(retrieveListOfAccrualTransactions(), closedDate); reverseTransactionsOnOrAfter(retrieveListOfAccrualTransactions(), closedDate);
@@ -3428,8 +3448,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
BigDecimal penaltyToPost = cumulativeIncomeFromInstallments.get(PENALTY) BigDecimal penaltyToPost = cumulativeIncomeFromInstallments.get(PENALTY)
.subtract(cumulativeIncomeFromIncomePosting.get(PENALTY)); .subtract(cumulativeIncomeFromIncomePosting.get(PENALTY));
BigDecimal amountToPost = interestToPost.add(feeToPost).add(penaltyToPost); BigDecimal amountToPost = interestToPost.add(feeToPost).add(penaltyToPost);
if (isExternalIdAutoGenerationEnabled) {
externalId = ExternalId.generate();
}
LoanTransaction finalIncomeTransaction = LoanTransaction.incomePosting(this, this.getOffice(), closedDate, amountToPost, LoanTransaction finalIncomeTransaction = LoanTransaction.incomePosting(this, this.getOffice(), closedDate, amountToPost,
interestToPost, feeToPost, penaltyToPost); interestToPost, feeToPost, penaltyToPost, externalId);
addLoanTransaction(finalIncomeTransaction); addLoanTransaction(finalIncomeTransaction);
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
List<LoanTransaction> updatedAccrualTransactions = retrieveListOfAccrualTransactions(); List<LoanTransaction> updatedAccrualTransactions = retrieveListOfAccrualTransactions();
@@ -3439,8 +3462,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
HashMap<String, Object> feeDetails = new HashMap<>(); HashMap<String, Object> feeDetails = new HashMap<>();
determineFeeDetails(lastAccruedDate, closedDate, feeDetails); determineFeeDetails(lastAccruedDate, closedDate, feeDetails);
if (isExternalIdAutoGenerationEnabled) {
externalId = ExternalId.generate();
}
LoanTransaction finalAccrual = LoanTransaction.accrueTransaction(this, this.getOffice(), closedDate, amountToPost, LoanTransaction finalAccrual = LoanTransaction.accrueTransaction(this, this.getOffice(), closedDate, amountToPost,
interestToPost, feeToPost, penaltyToPost); interestToPost, feeToPost, penaltyToPost, externalId);
updateLoanChargesPaidBy(finalAccrual, feeDetails, null); updateLoanChargesPaidBy(finalAccrual, feeDetails, null);
addLoanTransaction(finalAccrual); addLoanTransaction(finalAccrual);
} }
@@ -3596,13 +3622,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
return LoanTransaction.waiver(getOffice(), this, possibleInterestToWaive, transactionDate, possibleInterestToWaive, return LoanTransaction.waiver(getOffice(), this, possibleInterestToWaive, transactionDate, possibleInterestToWaive,
possibleInterestToWaive.zero()); possibleInterestToWaive.zero(), ExternalId.empty());
} }
public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail, public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final String reversalExternalId) { final ScheduleGeneratorDTO scheduleGeneratorDTO, final ExternalId reversalExternalId) {
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, transactionForAdjustment.getTransactionDate()); validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, transactionForAdjustment.getTransactionDate());
@@ -3752,11 +3778,18 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE); final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE);
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID); final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID);
ExternalId externalId = StringUtils.isBlank(txnExternalId) ? ExternalId.empty() : new ExternalId(txnExternalId);
if (externalId.isEmpty() && TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
this.closedOnDate = writtenOffOnLocalDate; this.closedOnDate = writtenOffOnLocalDate;
this.writtenOffOnDate = writtenOffOnLocalDate; this.writtenOffOnDate = writtenOffOnLocalDate;
this.closedBy = currentUser; this.closedBy = currentUser;
changes.put(CLOSED_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); changes.put(CLOSED_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE));
changes.put(WRITTEN_OFF_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); changes.put(WRITTEN_OFF_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE));
changes.put("externalId", externalId);
if (writtenOffOnLocalDate.isBefore(getDisbursementDate())) { if (writtenOffOnLocalDate.isBefore(getDisbursementDate())) {
final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: " final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: "
@@ -3772,7 +3805,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate); throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate);
} }
loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, txnExternalId); loanTransaction = LoanTransaction.writeoff(this, getOffice(), writtenOffOnLocalDate, externalId);
LocalDate lastTransactionDate = getLastUserTransactionDate(); LocalDate lastTransactionDate = getLastUserTransactionDate();
if (lastTransactionDate.isAfter(writtenOffOnLocalDate)) { if (lastTransactionDate.isAfter(writtenOffOnLocalDate)) {
final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions."; final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions.";
@@ -3839,6 +3872,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LocalDate closureDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE); final LocalDate closureDate = command.localDateValueOfParameterNamed(TRANSACTION_DATE);
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID); final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(EXTERNAL_ID);
ExternalId externalId = StringUtils.isBlank(txnExternalId) ? ExternalId.empty() : new ExternalId(txnExternalId);
if (externalId.isEmpty() && TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
this.closedOnDate = closureDate; this.closedOnDate = closureDate;
changes.put(CLOSED_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE)); changes.put(CLOSED_ON_DATE, command.stringValueOfParameterNamed(TRANSACTION_DATE));
@@ -3870,7 +3908,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); changes.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus));
} }
this.closedOnDate = closureDate; this.closedOnDate = closureDate;
loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, txnExternalId); changes.put("externalId", externalId);
loanTransaction = LoanTransaction.writeoff(this, getOffice(), closureDate, externalId);
final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, getLoanTransactions()); final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, getLoanTransactions());
if (!isLastTransaction) { if (!isLastTransaction) {
final String errorMessage = "The closing date of the loan must be on or after latest transaction date."; final String errorMessage = "The closing date of the loan must be on or after latest transaction date.";
@@ -5419,27 +5458,36 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
interest = compoundingDetail.getAmount().subtract(fee).subtract(penalties); interest = compoundingDetail.getAmount().subtract(fee).subtract(penalties);
} }
ExternalId externalId = ExternalId.empty();
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
if (existingIncomeTransaction == null) { if (existingIncomeTransaction == null) {
LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate(), LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties); compoundingDetail.getAmount(), interest, fee, penalties, externalId);
addLoanTransaction(transaction); addLoanTransaction(transaction);
} else if (existingIncomeTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) { } else if (existingIncomeTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
existingIncomeTransaction.reverse(); existingIncomeTransaction.reverse();
LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate(), LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties); compoundingDetail.getAmount(), interest, fee, penalties, externalId);
addLoanTransaction(transaction); addLoanTransaction(transaction);
} }
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
if (existingAccrualTransaction == null) { if (existingAccrualTransaction == null) {
LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(), LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties); compoundingDetail.getAmount(), interest, fee, penalties, externalId);
updateLoanChargesPaidBy(accrual, feeDetails, null); updateLoanChargesPaidBy(accrual, feeDetails, null);
addLoanTransaction(accrual); addLoanTransaction(accrual);
} else if (existingAccrualTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) { } else if (existingAccrualTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) {
existingAccrualTransaction.reverse(); existingAccrualTransaction.reverse();
LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(), LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(),
compoundingDetail.getAmount(), interest, fee, penalties); compoundingDetail.getAmount(), interest, fee, penalties, externalId);
updateLoanChargesPaidBy(accrual, feeDetails, null); updateLoanChargesPaidBy(accrual, feeDetails, null);
addLoanTransaction(accrual); addLoanTransaction(accrual);
} }
@@ -23,32 +23,33 @@ import java.time.LocalDate;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
public interface LoanAccountDomainService { public interface LoanAccountDomainService {
LoanTransaction makeRepayment(LoanTransactionType repaymentTransactionType, Loan loan, CommandProcessingResultBuilder builderResult, LoanTransaction makeRepayment(LoanTransactionType repaymentTransactionType, Loan loan, LocalDate transactionDate,
LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId, BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId,
boolean isRecoveryRepayment, String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, boolean isRecoveryRepayment, String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto,
Boolean isHolidayValidationDone); Boolean isHolidayValidationDone);
LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, LoanTransaction makeRefund(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId); BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId);
LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount, LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
PaymentDetail paymentDetail, String noteText, String txnExternalId, boolean isLoanToLoanTransfer); PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId, boolean isLoanToLoanTransfer);
void reverseTransfer(LoanTransaction loanTransaction); void reverseTransfer(LoanTransaction loanTransaction);
LoanTransaction makeChargePayment(Loan loan, Long chargeId, LocalDate transactionDate, BigDecimal transactionAmount, LoanTransaction makeChargePayment(Loan loan, Long chargeId, LocalDate transactionDate, BigDecimal transactionAmount,
PaymentDetail paymentDetail, String noteText, String txnExternalId, Integer transactionType, Integer installmentNumber); PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId, Integer transactionType, Integer installmentNumber);
LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount, LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
PaymentDetail paymentDetail, String noteText, String txnExternalId); PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId);
LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId); BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId);
void updateLoanCollateralTransaction(Set<LoanCollateralManagement> loanCollateralManagementList); void updateLoanCollateralTransaction(Set<LoanCollateralManagement> loanCollateralManagementList);
@@ -71,8 +72,8 @@ public interface LoanAccountDomainService {
*/ */
void setLoanDelinquencyTag(Loan loan, LocalDate transactionDate); void setLoanDelinquencyTag(Loan loan, LocalDate transactionDate);
LoanTransaction makeRepayment(LoanTransactionType repaymentTransactionType, Loan loan, CommandProcessingResultBuilder builderResult, LoanTransaction makeRepayment(LoanTransactionType repaymentTransactionType, Loan loan, LocalDate transactionDate,
LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId, BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId,
boolean isRecoveryRepayment, String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, boolean isRecoveryRepayment, String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto,
Boolean isHolidayValidationDone, boolean isLoanToLoanTransfer); Boolean isHolidayValidationDone, boolean isLoanToLoanTransfer);
@@ -82,7 +83,7 @@ public interface LoanAccountDomainService {
void saveLoanWithDataIntegrityViolationChecks(Loan loan); void saveLoanWithDataIntegrityViolationChecks(Loan loan);
Map<String, Object> foreCloseLoan(Loan loan, LocalDate foreClourseDate, String noteText); Map<String, Object> foreCloseLoan(Loan loan, LocalDate foreClourseDate, String noteText, ExternalId externalId);
/** /**
* Disables all standing instructions linked to a closed loan * Disables all standing instructions linked to a closed loan
@@ -94,6 +95,6 @@ public interface LoanAccountDomainService {
void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened); void recalculateAccruals(Loan loan, boolean isInterestCalcualtionHappened);
CommandProcessingResultBuilder creditBalanceRefund(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount, LoanTransaction creditBalanceRefund(Loan loan, LocalDate transactionDate, BigDecimal transactionAmount, String noteText,
String noteText, String externalId); ExternalId externalId);
} }
@@ -34,10 +34,12 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.exception.MultiException; import org.apache.fineract.infrastructure.core.exception.MultiException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargePaymentPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargePaymentPostBusinessEvent;
@@ -124,17 +126,16 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final LoanCollateralManagementRepository loanCollateralManagementRepository; private final LoanCollateralManagementRepository loanCollateralManagementRepository;
private final DelinquencyWritePlatformService delinquencyWritePlatformService; private final DelinquencyWritePlatformService delinquencyWritePlatformService;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final ExternalIdFactory externalIdFactory;
@Transactional @Transactional
@Override @Override
public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransactionType, final Loan loan, public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransactionType, final Loan loan,
final CommandProcessingResultBuilder builderResult, final LocalDate transactionDate, final BigDecimal transactionAmount, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, final boolean isRecoveryRepayment, final ExternalId txnExternalId, final boolean isRecoveryRepayment, final String chargeRefundChargeType,
final String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, Boolean isHolidayValidationDone) {
Boolean isHolidayValidationDone) { return makeRepayment(repaymentTransactionType, loan, transactionDate, transactionAmount, paymentDetail, noteText, txnExternalId,
return makeRepayment(repaymentTransactionType, loan, builderResult, transactionDate, transactionAmount, paymentDetail, noteText, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone, false);
txnExternalId, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone,
false);
} }
@Transactional @Transactional
@@ -155,10 +156,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Transactional @Transactional
@Override @Override
public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransactionType, final Loan loan, public LoanTransaction makeRepayment(final LoanTransactionType repaymentTransactionType, final Loan loan,
final CommandProcessingResultBuilder builderResult, final LocalDate transactionDate, final BigDecimal transactionAmount, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, final boolean isRecoveryRepayment, final ExternalId txnExternalId, final boolean isRecoveryRepayment, final String chargeRefundChargeType,
final String chargeRefundChargeType, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, boolean isAccountTransfer, HolidayDetailDTO holidayDetailDto, Boolean isHolidayValidationDone,
Boolean isHolidayValidationDone, final boolean isLoanToLoanTransfer) { final boolean isLoanToLoanTransfer) {
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
LoanBusinessEvent repaymentEvent = getLoanRepaymentTypeBusinessEvent(repaymentTransactionType, isRecoveryRepayment, loan); LoanBusinessEvent repaymentEvent = getLoanRepaymentTypeBusinessEvent(repaymentTransactionType, isRecoveryRepayment, loan);
@@ -177,7 +178,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount); final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
LoanTransaction newRepaymentTransaction = null; LoanTransaction newRepaymentTransaction;
if (isRecoveryRepayment) { if (isRecoveryRepayment) {
newRepaymentTransaction = LoanTransaction.recoveryRepayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate, newRepaymentTransaction = LoanTransaction.recoveryRepayment(loan.getOffice(), repaymentAmount, paymentDetail, transactionDate,
txnExternalId); txnExternalId);
@@ -238,9 +239,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
// changes to closed // changes to closed
disableStandingInstructionsLinkedToClosedLoan(loan); disableStandingInstructionsLinkedToClosedLoan(loan);
builderResult.withEntityId(newRepaymentTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId())
.withGroupId(loan.getGroupId());
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) { if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
// Mark Post Dated Check as paid. // Mark Post Dated Check as paid.
final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = newRepaymentTransaction final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = newRepaymentTransaction
@@ -358,7 +356,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override @Override
@Transactional @Transactional
public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate, public LoanTransaction makeChargePayment(final Loan loan, final Long chargeId, final LocalDate transactionDate,
final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final ExternalId txnExternalId,
final Integer transactionType, Integer installmentNumber) { final Integer transactionType, Integer installmentNumber) {
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
@@ -437,7 +435,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override @Override
public LoanTransaction makeRefund(final Long accountId, final CommandProcessingResultBuilder builderResult, public LoanTransaction makeRefund(final Long accountId, final CommandProcessingResultBuilder builderResult,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final String noteText,
final String txnExternalId) { final ExternalId txnExternalId) {
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
@@ -477,14 +475,14 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Transactional @Transactional
@Override @Override
public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount, public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount,
final PaymentDetail paymentDetail, final String noteText, final String txnExternalId) { final PaymentDetail paymentDetail, final String noteText, final ExternalId txnExternalId) {
return makeDisburseTransaction(loanId, transactionDate, transactionAmount, paymentDetail, noteText, txnExternalId, false); return makeDisburseTransaction(loanId, transactionDate, transactionAmount, paymentDetail, noteText, txnExternalId, false);
} }
@Transactional @Transactional
@Override @Override
public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount, public LoanTransaction makeDisburseTransaction(final Long loanId, final LocalDate transactionDate, final BigDecimal transactionAmount,
final PaymentDetail paymentDetail, final String noteText, final String txnExternalId, final boolean isLoanToLoanTransfer) { final PaymentDetail paymentDetail, final String noteText, final ExternalId txnExternalId, final boolean isLoanToLoanTransfer) {
final Loan loan = this.loanAccountAssembler.assembleFrom(loanId); final Loan loan = this.loanAccountAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
@@ -636,9 +634,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
@Override @Override
public CommandProcessingResultBuilder creditBalanceRefund(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount, public LoanTransaction creditBalanceRefund(final Loan loan, final LocalDate transactionDate, final BigDecimal transactionAmount,
String noteText, String externalId) { final String noteText, final ExternalId externalId) {
final Loan loan = this.loanAccountAssembler.assembleFrom(loanId);
businessEventNotifierService.notifyPreBusinessEvent(new LoanCreditBalanceRefundPreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanCreditBalanceRefundPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
@@ -663,15 +660,12 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
businessEventNotifierService businessEventNotifierService
.notifyPostBusinessEvent(new LoanCreditBalanceRefundPostBusinessEvent(newCreditBalanceRefundTransaction)); .notifyPostBusinessEvent(new LoanCreditBalanceRefundPostBusinessEvent(newCreditBalanceRefundTransaction));
return new CommandProcessingResultBuilder().withEntityId(newCreditBalanceRefundTransaction.getId()) // return newCreditBalanceRefundTransaction;
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId());
} }
@Override @Override
public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate, public LoanTransaction makeRefundForActiveLoan(Long accountId, CommandProcessingResultBuilder builderResult, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId) { BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, ExternalId txnExternalId) {
final Loan loan = this.loanAccountAssembler.assembleFrom(accountId); final Loan loan = this.loanAccountAssembler.assembleFrom(accountId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
businessEventNotifierService.notifyPreBusinessEvent(new LoanRefundPreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanRefundPreBusinessEvent(loan));
@@ -709,7 +703,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
@Override @Override
public Map<String, Object> foreCloseLoan(final Loan loan, final LocalDate foreClosureDate, final String noteText) { public Map<String, Object> foreCloseLoan(final Loan loan, final LocalDate foreClosureDate, final String noteText,
final ExternalId externalId) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanForeClosurePreBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanForeClosurePreBusinessEvent(loan));
MonetaryCurrency currency = loan.getCurrency(); MonetaryCurrency currency = loan.getCurrency();
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
@@ -730,8 +725,10 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
Money penaltyPortion = foreCloseDetail.getPenaltyChargesCharged(currency).minus(accruedReceivables[2]); Money penaltyPortion = foreCloseDetail.getPenaltyChargesCharged(currency).minus(accruedReceivables[2]);
Money total = interestPortion.plus(feePortion).plus(penaltyPortion); Money total = interestPortion.plus(feePortion).plus(penaltyPortion);
if (total.isGreaterThanZero()) { if (total.isGreaterThanZero()) {
ExternalId accrualExternalId = externalIdFactory.create();
LoanTransaction accrualTransaction = LoanTransaction.accrueTransaction(loan, loan.getOffice(), foreClosureDate, LoanTransaction accrualTransaction = LoanTransaction.accrueTransaction(loan, loan.getOffice(), foreClosureDate,
total.getAmount(), interestPortion.getAmount(), feePortion.getAmount(), penaltyPortion.getAmount()); total.getAmount(), interestPortion.getAmount(), feePortion.getAmount(), penaltyPortion.getAmount(),
accrualExternalId);
LocalDate fromDate = loan.getDisbursementDate(); LocalDate fromDate = loan.getDisbursementDate();
if (loan.getAccruedTill() != null) { if (loan.getAccruedTill() != null) {
fromDate = loan.getAccruedTill(); fromDate = loan.getAccruedTill();
@@ -761,7 +758,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
if (payPrincipal.plus(interestPayable).plus(feePayable).plus(penaltyPayable).isGreaterThanZero()) { if (payPrincipal.plus(interestPayable).plus(feePayable).plus(penaltyPayable).isGreaterThanZero()) {
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
String externalId = null;
payment = LoanTransaction.repayment(loan.getOffice(), payPrincipal.plus(interestPayable).plus(feePayable).plus(penaltyPayable), payment = LoanTransaction.repayment(loan.getOffice(), payPrincipal.plus(interestPayable).plus(feePayable).plus(penaltyPayable),
paymentDetail, foreClosureDate, externalId); paymentDetail, foreClosureDate, externalId);
payment.updateLoan(loan); payment.updateLoan(loan);
@@ -75,6 +75,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
String FIND_BY_ACCOUNT_NUMBER = "select loan from Loan loan where loan.accountNumber = :accountNumber"; String FIND_BY_ACCOUNT_NUMBER = "select loan from Loan loan where loan.accountNumber = :accountNumber";
String FIND_ID_BY_EXTERNAL_ID = "SELECT loan.id FROM Loan loan WHERE loan.externalId = :externalId";
@Query(FIND_GROUP_LOANS_DISBURSED_AFTER) @Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") LocalDate disbursementDate, @Param("groupId") Long groupId, List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") LocalDate disbursementDate, @Param("groupId") Long groupId,
@Param("loanType") Integer loanType); @Param("loanType") Integer loanType);
@@ -168,4 +170,6 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
@Query(FIND_ALL_NON_CLOSED) @Query(FIND_ALL_NON_CLOSED)
List<Long> findAllNonClosedLoanIds(); List<Long> findAllNonClosedLoanIds();
@Query(FIND_ID_BY_EXTERNAL_ID)
Long findIdByExternalId(@Param("externalId") String externalId);
} }
@@ -62,8 +62,7 @@ public class LoanRepositoryWrapper {
public Collection<Loan> findActiveLoansByLoanIdAndGroupId(Long clientId, Long groupId) { public Collection<Loan> findActiveLoansByLoanIdAndGroupId(Long clientId, Long groupId) {
final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue(), LoanStatus.OVERPAID.getValue())); LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue(), LoanStatus.OVERPAID.getValue()));
final Collection<Loan> loans = this.repository.findByClientIdAndGroupIdAndLoanStatus(clientId, groupId, loanStatuses); return this.repository.findByClientIdAndGroupIdAndLoanStatus(clientId, groupId, loanStatuses);
return loans;
} }
public Loan saveAndFlush(final Loan loan) { public Loan saveAndFlush(final Loan loan) {
@@ -114,8 +113,7 @@ public class LoanRepositoryWrapper {
public List<LoanRepaymentScheduleInstallment> getLoanRepaymentScheduleInstallments(final Long loanId) { public List<LoanRepaymentScheduleInstallment> getLoanRepaymentScheduleInstallments(final Long loanId) {
final Loan loan = this.repository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)); final Loan loan = this.repository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId));
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); return loan.getRepaymentScheduleInstallments();
return loanRepaymentScheduleInstallments;
} }
public Integer getNumberOfRepayments(final Long loanId) { public Integer getNumberOfRepayments(final Long loanId) {
@@ -253,4 +251,8 @@ public class LoanRepositoryWrapper {
} }
return loan; return loan;
} }
public Long findIdByExternalId(String externalId) {
return this.repository.findIdByExternalId(externalId);
}
} }
@@ -38,6 +38,7 @@ import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.UniqueConstraint; import javax.persistence.UniqueConstraint;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -104,10 +105,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
private boolean reversed; private boolean reversed;
@Column(name = "external_id", length = 100, nullable = true, unique = true) @Column(name = "external_id", length = 100, nullable = true, unique = true)
private String externalId; private ExternalId externalId;
@Column(name = "reversal_external_id", length = 100, nullable = true, unique = true) @Column(name = "reversal_external_id", length = 100, nullable = true, unique = true)
private String reversalExternalId; private ExternalId reversalExternalId;
@Column(name = "reversed_on_date", nullable = true) @Column(name = "reversed_on_date", nullable = true)
private LocalDate reversedOnDate; private LocalDate reversedOnDate;
@@ -136,30 +137,30 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
protected LoanTransaction() {} protected LoanTransaction() {}
public static LoanTransaction incomePosting(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount, public static LoanTransaction incomePosting(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion) { final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
final ExternalId externalId) {
final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue(); final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue();
final BigDecimal principalPortion = BigDecimal.ZERO; final BigDecimal principalPortion = BigDecimal.ZERO;
final BigDecimal overPaymentPortion = BigDecimal.ZERO; final BigDecimal overPaymentPortion = BigDecimal.ZERO;
final boolean reversed = false; final boolean reversed = false;
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
final String externalId = null;
return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion, return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion,
penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId); penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
} }
public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate disbursementDate, final String externalId) { final LocalDate disbursementDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate, return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate,
externalId); externalId);
} }
public static LoanTransaction repayment(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction repayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId) { final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId); return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, externalId);
} }
public static LoanTransaction chargeback(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction chargeback(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId) { final LocalDate paymentDate, final ExternalId externalId) {
LoanTransaction loanTransaction = new LoanTransaction(null, office, LoanTransactionType.CHARGEBACK, paymentDetail, LoanTransaction loanTransaction = new LoanTransaction(null, office, LoanTransactionType.CHARGEBACK, paymentDetail,
amount.getAmount(), paymentDate, externalId); amount.getAmount(), paymentDate, externalId);
loanTransaction.principalPortion = amount.getAmount(); loanTransaction.principalPortion = amount.getAmount();
@@ -167,13 +168,14 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
} }
public static LoanTransaction repaymentType(final LoanTransactionType repaymentType, final Office office, final Money amount, public static LoanTransaction repaymentType(final LoanTransactionType repaymentType, final Office office, final Money amount,
final PaymentDetail paymentDetail, final LocalDate paymentDate, final String externalId, final String chargeRefundChargeType) { final PaymentDetail paymentDetail, final LocalDate paymentDate, final ExternalId externalId,
final String chargeRefundChargeType) {
return new LoanTransaction(null, office, repaymentType, paymentDetail, amount.getAmount(), paymentDate, externalId, return new LoanTransaction(null, office, repaymentType, paymentDetail, amount.getAmount(), paymentDate, externalId,
chargeRefundChargeType); chargeRefundChargeType);
} }
public static LoanTransaction chargeAdjustment(final Loan loan, final BigDecimal amount, final LocalDate transactionDate, public static LoanTransaction chargeAdjustment(final Loan loan, final BigDecimal amount, final LocalDate transactionDate,
final String externalId) { final ExternalId externalId) {
return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, amount, transactionDate, externalId); return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.CHARGE_ADJUSTMENT, amount, transactionDate, externalId);
} }
@@ -189,32 +191,32 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
} }
public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction recoveryRepayment(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId) { final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate, return new LoanTransaction(null, office, LoanTransactionType.RECOVERY_REPAYMENT, paymentDetail, amount.getAmount(), paymentDate,
externalId); externalId);
} }
public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction loanPayment(final Loan loan, final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId, final LoanTransactionType transactionType) { final LocalDate paymentDate, final ExternalId externalId, final LoanTransactionType transactionType) {
return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId); return new LoanTransaction(loan, office, transactionType, paymentDetail, amount.getAmount(), paymentDate, externalId);
} }
public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction repaymentAtDisbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId) { final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(), return new LoanTransaction(null, office, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT, paymentDetail, amount.getAmount(),
paymentDate, externalId); paymentDate, externalId);
} }
public static LoanTransaction waiver(final Office office, final Loan loan, final Money amount, final LocalDate waiveDate, public static LoanTransaction waiver(final Office office, final Loan loan, final Money amount, final LocalDate waiveDate,
final Money waived, final Money unrecognizedPortion) { final Money waived, final Money unrecognizedPortion, final ExternalId externalId) {
LoanTransaction loanTransaction = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_INTEREST, amount.getAmount(), LoanTransaction loanTransaction = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_INTEREST, amount.getAmount(),
waiveDate, null); waiveDate, externalId);
loanTransaction.updateInterestComponent(waived, unrecognizedPortion); loanTransaction.updateInterestComponent(waived, unrecognizedPortion);
return loanTransaction; return loanTransaction;
} }
public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount, public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount,
final LocalDate interestAppliedDate) { final LocalDate interestAppliedDate, final ExternalId externalId) {
BigDecimal principalPortion = null; BigDecimal principalPortion = null;
BigDecimal feesPortion = null; BigDecimal feesPortion = null;
BigDecimal penaltiesPortion = null; BigDecimal penaltiesPortion = null;
@@ -222,51 +224,53 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
BigDecimal overPaymentPortion = null; BigDecimal overPaymentPortion = null;
boolean reversed = false; boolean reversed = false;
PaymentDetail paymentDetail = null; PaymentDetail paymentDetail = null;
String externalId = null;
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), interestAppliedDate, interestPortion, return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), interestAppliedDate, interestPortion,
principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId); principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
} }
public static LoanTransaction accrual(final Loan loan, final Office office, final Money amount, final Money interest, public static LoanTransaction accrual(final Loan loan, final Office office, final Money amount, final Money interest,
final Money feeCharges, final Money penaltyCharges, final LocalDate transactionDate) { final Money feeCharges, final Money penaltyCharges, final LocalDate transactionDate, final ExternalId externalId) {
return accrueTransaction(loan, office, transactionDate, amount.getAmount(), interest.getAmount(), feeCharges.getAmount(), return accrueTransaction(loan, office, transactionDate, amount.getAmount(), interest.getAmount(), feeCharges.getAmount(),
penaltyCharges.getAmount()); penaltyCharges.getAmount(), externalId);
} }
public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount, public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion) { final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
final ExternalId externalId) {
BigDecimal principalPortion = null; BigDecimal principalPortion = null;
BigDecimal overPaymentPortion = null; BigDecimal overPaymentPortion = null;
boolean reversed = false; boolean reversed = false;
PaymentDetail paymentDetail = null; PaymentDetail paymentDetail = null;
String externalId = null;
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf, amount, principalPortion, interestPortion, return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf, amount, principalPortion, interestPortion,
feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId); feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
} }
public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate) { public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final ExternalId externalId) {
return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate, return new LoanTransaction(loan, office, LoanTransactionType.INITIATE_TRANSFER.getValue(), transferDate,
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, null); loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
} }
public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate) { public static LoanTransaction approveTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final ExternalId externalId) {
return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate, return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate,
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, null); loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
} }
public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate) { public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate,
final ExternalId externalId) {
return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate, return new LoanTransaction(loan, office, LoanTransactionType.WITHDRAW_TRANSFER.getValue(), transferDate,
loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(),
loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary().getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(),
loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, null); loan.getSummary().getTotalPenaltyChargesOutstanding(), null, false, null, externalId);
} }
public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId) { final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId); return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId);
} }
@@ -278,8 +282,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
} }
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate, public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
final Money feeCharges, final Money penaltyCharges) { final Money feeCharges, final Money penaltyCharges, final ExternalId externalId) {
String externalId = null;
final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate, final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate,
externalId); externalId);
applyCharge.updateChargesComponents(feeCharges, penaltyCharges); applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
@@ -287,14 +290,14 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
} }
public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate, public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate,
final String externalId) { final ExternalId externalId) {
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND, paymentDetail, amount.getAmount(), paymentDate, return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND, paymentDetail, amount.getAmount(), paymentDate,
externalId); externalId);
} }
public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail, public static LoanTransaction refundForActiveLoan(final Office office, final Money amount, final PaymentDetail paymentDetail,
final LocalDate paymentDate, final String externalId) { final LocalDate paymentDate, final ExternalId externalId) {
return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(), paymentDate, return new LoanTransaction(null, office, LoanTransactionType.REFUND_FOR_ACTIVE_LOAN, paymentDetail, amount.getAmount(), paymentDate,
externalId); externalId);
} }
@@ -312,7 +315,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final LocalDate dateOf, final BigDecimal amount, private LoanTransaction(final Loan loan, final Office office, final Integer typeOf, final LocalDate dateOf, final BigDecimal amount,
final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal principalPortion, final BigDecimal interestPortion, final BigDecimal feeChargesPortion,
final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed, final BigDecimal penaltyChargesPortion, final BigDecimal overPaymentPortion, final boolean reversed,
final PaymentDetail paymentDetail, final String externalId) { final PaymentDetail paymentDetail, final ExternalId externalId) {
this.loan = loan; this.loan = loan;
this.typeOf = typeOf; this.typeOf = typeOf;
@@ -331,20 +334,21 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
} }
public static LoanTransaction waiveLoanCharge(final Loan loan, final Office office, final Money waived, final LocalDate waiveDate, public static LoanTransaction waiveLoanCharge(final Loan loan, final Office office, final Money waived, final LocalDate waiveDate,
final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge) { final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge, final ExternalId externalId) {
final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate, final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate,
null); externalId);
waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge); waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge);
return waiver; return waiver;
} }
public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate, final String externalId) { public static LoanTransaction writeoff(final Loan loan, final Office office, final LocalDate writeOffDate,
final ExternalId externalId) {
return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId); return new LoanTransaction(loan, office, LoanTransactionType.WRITEOFF, null, writeOffDate, externalId);
} }
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final BigDecimal amount, private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final BigDecimal amount,
final LocalDate date, final String externalId) { final LocalDate date, final ExternalId externalId) {
this.loan = loan; this.loan = loan;
this.typeOf = type.getValue(); this.typeOf = type.getValue();
this.amount = amount; this.amount = amount;
@@ -355,7 +359,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
} }
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail, private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail,
final BigDecimal amount, final LocalDate date, final String externalId) { final BigDecimal amount, final LocalDate date, final ExternalId externalId) {
this.loan = loan; this.loan = loan;
this.typeOf = type.getValue(); this.typeOf = type.getValue();
this.paymentDetail = paymentDetail; this.paymentDetail = paymentDetail;
@@ -367,7 +371,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
} }
private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail, private LoanTransaction(final Loan loan, final Office office, final LoanTransactionType type, final PaymentDetail paymentDetail,
final BigDecimal amount, final LocalDate date, final String externalId, final String chargeRefundChargeType) { final BigDecimal amount, final LocalDate date, final ExternalId externalId, final String chargeRefundChargeType) {
this.loan = loan; this.loan = loan;
this.typeOf = type.getValue(); this.typeOf = type.getValue();
this.paymentDetail = paymentDetail; this.paymentDetail = paymentDetail;
@@ -386,7 +390,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
this.loanTransactionToRepaymentScheduleMappings.clear(); this.loanTransactionToRepaymentScheduleMappings.clear();
} }
public void reverse(final String reversalExternalId) { public void reverse(final ExternalId reversalExternalId) {
this.reverse(); this.reverse();
this.reversalExternalId = reversalExternalId; this.reversalExternalId = reversalExternalId;
} }
@@ -746,7 +750,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
this.loanChargesPaid = loanChargesPaid; this.loanChargesPaid = loanChargesPaid;
} }
public String getExternalId() { public ExternalId getExternalId() {
return this.externalId; return this.externalId;
} }
@@ -758,7 +762,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return LoanTransactionType.CREDIT_BALANCE_REFUND.equals(getTypeOf()) && isNotReversed(); return LoanTransactionType.CREDIT_BALANCE_REFUND.equals(getTypeOf()) && isNotReversed();
} }
public void updateExternalId(final String externalId) { public void updateExternalId(final ExternalId externalId) {
this.externalId = externalId; this.externalId = externalId;
} }
@@ -21,13 +21,17 @@ package org.apache.fineract.portfolio.loanaccount.domain;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface LoanTransactionRepository extends JpaRepository<LoanTransaction, Long>, JpaSpecificationExecutor<LoanTransaction> { public interface LoanTransactionRepository extends JpaRepository<LoanTransaction, Long>, JpaSpecificationExecutor<LoanTransaction> {
String FIND_ID_BY_EXTERNAL_ID = "SELECT lt.id FROM LoanTransaction lt WHERE lt.externalId = :externalId";
Optional<LoanTransaction> findByIdAndLoanId(Long transactionId, Long loanId); Optional<LoanTransaction> findByIdAndLoanId(Long transactionId, Long loanId);
@Query(""" @Query("""
@@ -44,4 +48,6 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
""") """)
Collection<LoanScheduleDelinquencyData> fetchLoanTransactionsByTypeAndLessOrEqualDate(Integer transactionType, LocalDate businessDate); Collection<LoanScheduleDelinquencyData> fetchLoanTransactionsByTypeAndLessOrEqualDate(Integer transactionType, LocalDate businessDate);
@Query(FIND_ID_BY_EXTERNAL_ID)
Long findIdByExternalId(@Param("externalId") ExternalId externalId);
} }
@@ -18,8 +18,8 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.exception; package org.apache.fineract.portfolio.loanaccount.exception;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
/** /**
* A {@link RuntimeException} thrown when loan resources are not found. * A {@link RuntimeException} thrown when loan resources are not found.
@@ -30,11 +30,19 @@ public class LoanNotFoundException extends AbstractPlatformResourceNotFoundExcep
super("error.msg.loan.id.invalid", "Loan with identifier " + id + " does not exist", id); super("error.msg.loan.id.invalid", "Loan with identifier " + id + " does not exist", id);
} }
public LoanNotFoundException(Long id, EmptyResultDataAccessException e) { public LoanNotFoundException(Long id, Exception e) {
super("error.msg.loan.id.invalid", "Loan with identifier " + id + " does not exist", id, e); super("error.msg.loan.id.invalid", "Loan with identifier " + id + " does not exist", id, e);
} }
public LoanNotFoundException(String accountId) { public LoanNotFoundException(String accountId) {
super("error.msg.loan.account.id.invalid", "Loan with account ID " + accountId + " does not exist", accountId); super("error.msg.loan.account.id.invalid", "Loan with account ID " + accountId + " does not exist", accountId);
} }
public LoanNotFoundException(ExternalId externalId) {
super("error.msg.loan.external.id.invalid", "Loan with external identifier " + externalId + " does not exist", externalId);
}
public LoanNotFoundException(ExternalId externalId, Exception e) {
super("error.msg.loan.external.id.invalid", "Loan with external identifier " + externalId + " does not exist", externalId, e);
}
} }
@@ -18,6 +18,7 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.exception; package org.apache.fineract.portfolio.loanaccount.exception;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
@@ -27,15 +28,20 @@ import org.springframework.dao.EmptyResultDataAccessException;
public class LoanTransactionNotFoundException extends AbstractPlatformResourceNotFoundException { public class LoanTransactionNotFoundException extends AbstractPlatformResourceNotFoundException {
public LoanTransactionNotFoundException(final Long id) { public LoanTransactionNotFoundException(final Long id) {
super("error.msg.loan.id.invalid", "Transaction with identifier " + id + " does not exist", id); super("error.msg.loan.transaction.id.invalid", "Transaction with identifier " + id + " does not exist", id);
} }
public LoanTransactionNotFoundException(final Long id, final Long loanId) { public LoanTransactionNotFoundException(final Long id, final Long loanId) {
super("error.msg.loan.id.invalid", "Transaction with identifier " + id + " does not exist for loan with identifier " + loanId + ".", super("error.msg.loan.transaction.id.invalid",
id, loanId); "Transaction with identifier " + id + " does not exist for loan with identifier " + loanId + ".", id, loanId);
} }
public LoanTransactionNotFoundException(Long id, EmptyResultDataAccessException e) { public LoanTransactionNotFoundException(Long id, EmptyResultDataAccessException e) {
super("error.msg.loan.id.invalid", "Transaction with identifier " + id + " does not exist", id, e); super("error.msg.loan.transaction.id.invalid", "Transaction with identifier " + id + " does not exist", id, e);
}
public LoanTransactionNotFoundException(ExternalId transactionExternalId, EmptyResultDataAccessException e) {
super("error.msg.loan.transaction.id.invalid", "Transaction with external identifier " + transactionExternalId + " does not exist",
transactionExternalId, e);
} }
} }
@@ -30,6 +30,6 @@ public interface GuarantorDomainService {
void assignGuarantor(GuarantorFundingDetails guarantorFundingDetails, LocalDate transactionDate); void assignGuarantor(GuarantorFundingDetails guarantorFundingDetails, LocalDate transactionDate);
void transaferFundsFromGuarantor(Loan loan); void transferFundsFromGuarantor(Loan loan);
} }
@@ -29,10 +29,13 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.event.business.BusinessEventListener; import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanApprovedBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanApprovedBusinessEvent;
@@ -80,6 +83,8 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
private final Map<Long, Long> releaseLoanIds = new HashMap<>(2); private final Map<Long, Long> releaseLoanIds = new HashMap<>(2);
private final SavingsAccountAssembler savingsAccountAssembler; private final SavingsAccountAssembler savingsAccountAssembler;
private final ConfigurationDomainService configurationDomainService;
private final ExternalIdFactory externalIdFactory;
@PostConstruct @PostConstruct
public void addListeners() { public void addListeners() {
@@ -204,7 +209,7 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
* account to loan account and releases guarantor) * account to loan account and releases guarantor)
*/ */
@Override @Override
public void transaferFundsFromGuarantor(final Loan loan) { public void transferFundsFromGuarantor(final Loan loan) {
if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) <= 0) { if (loan.getGuaranteeAmount().compareTo(BigDecimal.ZERO) <= 0) {
return; return;
} }
@@ -227,7 +232,6 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
final AccountTransferDetails accountTransferDetails = null; final AccountTransferDetails accountTransferDetails = null;
final String noteText = null; final String noteText = null;
final String txnExternalId = null;
final SavingsAccount toSavingsAccount = null; final SavingsAccount toSavingsAccount = null;
Long loanId = loan.getId(); Long loanId = loan.getId();
@@ -245,11 +249,11 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
remainingAmount = remainingAmount.multiply(loan.getPrincipal().getAmount()).divide(loan.getGuaranteeAmount(), remainingAmount = remainingAmount.multiply(loan.getPrincipal().getAmount()).divide(loan.getGuaranteeAmount(),
MoneyHelper.getRoundingMode()); MoneyHelper.getRoundingMode());
} }
ExternalId externalId = externalIdFactory.create();
AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType, AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, remainingAmount, fromAccountType,
toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType, toAccountType, fromAccountId, toAccountId, description, locale, fmt, paymentDetail, fromTransferType,
toTransferType, chargeId, loanInstallmentNumber, transferType, accountTransferDetails, noteText, toTransferType, chargeId, loanInstallmentNumber, transferType, accountTransferDetails, noteText, externalId,
txnExternalId, loan, toSavingsAccount, fromSavingsAccount, isRegularTransaction, loan, toSavingsAccount, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
isExceptionForBalanceCheck);
transferAmount(accountTransferDTO); transferAmount(accountTransferDTO);
} finally { } finally {
releaseLoanIds.remove(loanId); releaseLoanIds.remove(loanId);
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.PortfolioAccountType;
@@ -74,7 +75,7 @@ public class TransferFeeChargeForLoansTasklet implements Tasklet {
portfolioAccountData.getId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null, portfolioAccountData.getId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(),
installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, installmentChargeData.getInstallmentNumber(), AccountTransferType.CHARGE_PAYMENT.getValue(), null, null,
null, null, null, null, isRegularTransaction, isExceptionForBalanceCheck); ExternalId.empty(), null, null, null, isRegularTransaction, isExceptionForBalanceCheck);
transferFeeCharge(accountTransferDTO, errors); transferFeeCharge(accountTransferDTO, errors);
} }
} }
@@ -86,8 +87,8 @@ public class TransferFeeChargeForLoansTasklet implements Tasklet {
chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, chargeData.getAmountOutstanding(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN,
portfolioAccountData.getId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null, portfolioAccountData.getId(), chargeData.getLoanId(), "Loan Charge Payment", null, null, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null, LoanTransactionType.CHARGE_PAYMENT.getValue(), chargeData.getId(), null,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, null, isRegularTransaction, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, ExternalId.empty(), null, null, null,
isExceptionForBalanceCheck); isRegularTransaction, isExceptionForBalanceCheck);
transferFeeCharge(accountTransferDTO, errors); transferFeeCharge(accountTransferDTO, errors);
} }
} }
@@ -1933,7 +1933,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
periods.add(disbursementPeriod); periods.add(disbursementPeriod);
principal = principal.add(disbursementData.getPrincipal()); principal = principal.add(disbursementData.getPrincipal());
} else if (!excludePastUndisbursed || disbursementData.isDisbursed() } else if (!excludePastUndisbursed || disbursementData.isDisbursed()
|| !disbursementData.disbursementDate().isBefore(DateUtils.getLocalDateOfTenant())) { || !disbursementData.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) {
/* /*
* JW: sums up amounts by disbursal date in case of side-effect issues. Original assumed that there * JW: sums up amounts by disbursal date in case of side-effect issues. Original assumed that there
* were no duplicate disbursal dates and 'put' each amount into the map keyed by date * were no duplicate disbursal dates and 'put' each amount into the map keyed by date
@@ -119,9 +119,10 @@ public final class LoanChargeApiJsonValidator {
Set<String> transactionParameters = null; Set<String> transactionParameters = null;
if (isChargeIdIncluded) { if (isChargeIdIncluded) {
transactionParameters = new HashSet<>( transactionParameters = new HashSet<>(
Arrays.asList("transactionDate", "locale", "dateFormat", "chargeId", "dueDate", "installmentNumber")); Arrays.asList("transactionDate", "locale", "dateFormat", "chargeId", "dueDate", "installmentNumber", "externalId"));
} else { } else {
transactionParameters = new HashSet<>(Arrays.asList("transactionDate", "locale", "dateFormat", "dueDate", "installmentNumber")); transactionParameters = new HashSet<>(
Arrays.asList("transactionDate", "locale", "dateFormat", "dueDate", "installmentNumber", "externalId"));
} }
final Type typeOfMap = new TypeToken<Map<String, Object>>() { final Type typeOfMap = new TypeToken<Map<String, Object>>() {
@@ -215,7 +216,8 @@ public final class LoanChargeApiJsonValidator {
if (StringUtils.isBlank(json)) { if (StringUtils.isBlank(json)) {
return; return;
} }
Set<String> transactionParameters = new HashSet<>(Arrays.asList("dueDate", "locale", "dateFormat", "installmentNumber")); Set<String> transactionParameters = new HashSet<>(
Arrays.asList("dueDate", "locale", "dateFormat", "installmentNumber", "externalId"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() { final Type typeOfMap = new TypeToken<Map<String, Object>>() {
@@ -320,7 +320,7 @@ public final class LoanEventApiJsonValidator {
} }
final Set<String> disbursementParameters = new HashSet<>( final Set<String> disbursementParameters = new HashSet<>(
Arrays.asList("transactionDate", "note", "locale", "dateFormat", "writeoffReasonId")); Arrays.asList("transactionDate", "note", "locale", "dateFormat", "writeoffReasonId", "externalId"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
@@ -335,6 +335,9 @@ public final class LoanEventApiJsonValidator {
final String note = this.fromApiJsonHelper.extractStringNamed("note", element); final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000); baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
baseDataValidator.reset().parameter("externalId").value(externalId).ignoreIfNull().notExceedingLengthOf(100);
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -482,7 +485,7 @@ public final class LoanEventApiJsonValidator {
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000); baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element); final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
baseDataValidator.reset().parameter("externalId").value(externalId).notExceedingLengthOf(100); baseDataValidator.reset().parameter("externalId").value(externalId).ignoreIfNull().notExceedingLengthOf(100);
validatePaymentDetails(baseDataValidator, element); validatePaymentDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
@@ -494,7 +497,8 @@ public final class LoanEventApiJsonValidator {
throw new InvalidJsonException(); throw new InvalidJsonException();
} }
final Set<String> foreclosureParameters = new HashSet<>(Arrays.asList("transactionDate", "note", "locale", "dateFormat")); final Set<String> foreclosureParameters = new HashSet<>(
Arrays.asList("transactionDate", "note", "locale", "dateFormat", "externalId"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType(); final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, foreclosureParameters); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, foreclosureParameters);
@@ -509,6 +513,9 @@ public final class LoanEventApiJsonValidator {
final String note = this.fromApiJsonHelper.extractStringNamed("note", element); final String note = this.fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000); baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
final String externalId = this.fromApiJsonHelper.extractStringNamed("externalId", element);
baseDataValidator.reset().parameter("externalId").value(externalId).ignoreIfNull().notExceedingLengthOf(100);
validatePaymentDetails(baseDataValidator, element); validatePaymentDetails(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
@@ -41,9 +41,11 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanApplyOverdueChargeBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanApplyOverdueChargeBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanAddChargeBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.charge.LoanAddChargeBusinessEvent;
@@ -60,6 +62,8 @@ import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.PortfolioAccountType;
import org.apache.fineract.portfolio.account.data.AccountTransferDTO; import org.apache.fineract.portfolio.account.data.AccountTransferDTO;
import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.account.data.PortfolioAccountData;
import org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository;
import org.apache.fineract.portfolio.account.domain.AccountTransferDetails;
import org.apache.fineract.portfolio.account.domain.AccountTransferType; import org.apache.fineract.portfolio.account.domain.AccountTransferType;
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService; import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
@@ -81,6 +85,7 @@ import org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollatera
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData; import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
@@ -146,6 +151,9 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final ExternalIdFactory externalIdFactory;
private final AccountTransferDetailRepository accountTransferDetailRepository;
private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) { private static boolean isPartOfThisInstallment(LoanCharge loanCharge, LoanRepaymentScheduleInstallment e) {
return e.getFromDate().isBefore(loanCharge.getDueDate()) && !loanCharge.getDueDate().isAfter(e.getDueDate()); return e.getFromDate().isBefore(loanCharge.getDueDate()) && !loanCharge.getDueDate().isAfter(e.getDueDate());
@@ -242,8 +250,12 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
businessEventNotifierService.notifyPostBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge)); businessEventNotifierService.notifyPostBusinessEvent(new LoanAddChargeBusinessEvent(loanCharge));
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loanCharge.getId()) return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
.withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId) .withEntityId(loanCharge.getId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.build(); .build();
} }
@@ -342,17 +354,21 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveChargeUndoBusinessEvent(loanCharge)); businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveChargeUndoBusinessEvent(loanCharge));
// TODO: this needs to be fixed in the future! Principal portion is bigdecimal, interest portion is money, ...
changes.put("principalPortion", loanTransaction.getPrincipalPortion()); changes.put("principalPortion", loanTransaction.getPrincipalPortion());
changes.put("interestPortion", loanTransaction.getInterestPortion(loan.getCurrency())); changes.put("interestPortion", loanTransaction.getInterestPortion(loan.getCurrency()));
changes.put("feeChargesPortion", loanTransaction.getFeeChargesPortion(loan.getCurrency())); changes.put("feeChargesPortion", loanTransaction.getFeeChargesPortion(loan.getCurrency()));
changes.put("penaltyChargesPortion", loanTransaction.getPenaltyChargesPortion(loan.getCurrency())); changes.put("penaltyChargesPortion", loanTransaction.getPenaltyChargesPortion(loan.getCurrency()));
changes.put("outstandingLoanBalance", loanTransaction.getOutstandingLoanBalance()); changes.put("outstandingLoanBalance", loanTransaction.getOutstandingLoanBalance());
changes.put("id", loanTransaction.getId()); changes.put("id", loanTransaction.getId());
changes.put("externalId", loanTransaction.getExternalId());
changes.put("date", loanTransaction.getTransactionDate()); changes.put("date", loanTransaction.getTransactionDate());
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanCharge.getId()) // .withEntityId(loanCharge.getId()) //
.withSubEntityId(loanTransaction.getId()) //
.withSubEntityExternalId(loanTransaction.getExternalId()) //
.withLoanId(loanId) // .withLoanId(loanId) //
.with(changes).build(); .with(changes).build();
} }
@@ -398,6 +414,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
this.loanChargeApiJsonValidator.validateInstallmentChargeTransaction(command.json()); this.loanChargeApiJsonValidator.validateInstallmentChargeTransaction(command.json());
final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
// Charges may be waived only when the loan associated with them are // Charges may be waived only when the loan associated with them are
@@ -442,6 +459,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
} }
final Map<String, Object> changes = new LinkedHashMap<>(3); final Map<String, Object> changes = new LinkedHashMap<>(3);
changes.put(LoanApiConstants.externalIdParameterName, externalId);
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
@@ -458,7 +476,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
} }
final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine, changes, final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine, changes,
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge); existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge,
externalId);
this.loanTransactionRepository.saveAndFlush(waiveTransaction); this.loanTransactionRepository.saveAndFlush(waiveTransaction);
this.loanRepositoryWrapper.save(loan); this.loanRepositoryWrapper.save(loan);
@@ -471,6 +490,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanChargeId) // .withEntityId(loanChargeId) //
.withSubEntityId(waiveTransaction.getId()) //
.withSubEntityExternalId(waiveTransaction.getExternalId()) //
.withOfficeId(loan.getOfficeId()) // .withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) // .withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) // .withGroupId(loan.getGroupId()) //
@@ -521,6 +542,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
// Charges may be waived only when the loan associated with them are // Charges may be waived only when the loan associated with them are
// active // active
@@ -583,12 +605,15 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS, final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, PortfolioAccountType.SAVINGS,
PortfolioAccountType.LOAN, portfolioAccountData.getId(), loanId, "Loan Charge Payment", locale, fmt, null, null, PortfolioAccountType.LOAN, portfolioAccountData.getId(), loanId, "Loan Charge Payment", locale, fmt, null, null,
LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber, LoanTransactionType.CHARGE_PAYMENT.getValue(), loanChargeId, loanInstallmentNumber,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, externalId, null, null, fromSavingsAccount, isRegularTransaction,
isExceptionForBalanceCheck); isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); Long transferTransactionId = this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
AccountTransferDetails transferDetails = this.accountTransferDetailRepository.findById(transferTransactionId).get();
LoanTransaction loanTransaction = transferDetails.getAccountTransferTransactions().get(0).getToLoanTransaction();
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanChargeId) // .withEntityId(loanChargeId) //
.withSubEntityId(loanTransaction.getId()).withSubEntityExternalId(loanTransaction.getExternalId())
.withOfficeId(loan.getOfficeId()) // .withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) // .withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) // .withGroupId(loan.getGroupId()) //
@@ -604,7 +629,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId); final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);
final LocalDate transactionDate = DateUtils.getBusinessLocalDate(); final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("amount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("amount");
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, "externalId");
final String locale = command.locale(); final String locale = command.locale();
loanChargeAdjustmentEntranceValidation(loanCharge, transactionAmount); loanChargeAdjustmentEntranceValidation(loanCharge, transactionAmount);
@@ -639,44 +664,12 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
changes.put("locale", locale); changes.put("locale", locale);
return commandProcessingResultBuilder.withCommandId(command.commandId()) // return commandProcessingResultBuilder.withCommandId(command.commandId()) //
.withLoanId(loanId) // .withLoanId(loanId) //
.withEntityId(loanTransaction.getId()).with(changes) // .withEntityId(loanChargeId).withSubEntityId(loanTransaction.getId()) //
.withSubEntityExternalId(loanTransaction.getExternalId()) //
.with(changes) //
.build(); .build();
} }
private LoanTransaction applyChargeAdjustment(final Loan loan, final LoanCharge loanCharge, final BigDecimal transactionAmount,
final LocalDate transactionDate, final String txnExternalId) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargeAdjustmentPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
LoanTransaction loanChargeAdjustmentTransaction = LoanTransaction.chargeAdjustment(loan, transactionAmount, transactionDate,
txnExternalId);
LoanTransactionRelation loanTransactionRelation = LoanTransactionRelation.linkToCharge(loanChargeAdjustmentTransaction, loanCharge,
LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT);
loanChargeAdjustmentTransaction.getLoanTransactionRelations().add(loanTransactionRelation);
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(loanChargeAdjustmentTransaction);
defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanChargeAdjustmentTransaction, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
loan.addLoanTransaction(loanChargeAdjustmentTransaction);
loan.updateLoanSummaryAndStatus();
loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(loan.getCurrency().getCode(),
existingTransactionIds, existingReversedTransactionIds, false);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
return loanChargeAdjustmentTransaction;
}
@Transactional @Transactional
@Override @Override
public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList) { public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDataList) {
@@ -744,6 +737,40 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
} }
} }
private LoanTransaction applyChargeAdjustment(final Loan loan, final LoanCharge loanCharge, final BigDecimal transactionAmount,
final LocalDate transactionDate, final ExternalId txnExternalId) {
businessEventNotifierService.notifyPreBusinessEvent(new LoanChargeAdjustmentPreBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
LoanTransaction loanChargeAdjustmentTransaction = LoanTransaction.chargeAdjustment(loan, transactionAmount, transactionDate,
txnExternalId);
LoanTransactionRelation loanTransactionRelation = LoanTransactionRelation.linkToCharge(loanChargeAdjustmentTransaction, loanCharge,
LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT);
loanChargeAdjustmentTransaction.getLoanTransactionRelations().add(loanTransactionRelation);
loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(loanChargeAdjustmentTransaction);
defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanChargeAdjustmentTransaction, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
loan.addLoanTransaction(loanChargeAdjustmentTransaction);
loan.updateLoanSummaryAndStatus();
loanAccountDomainService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(loan.getCurrency().getCode(),
existingTransactionIds, existingReversedTransactionIds, false);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
loanAccountDomainService.setLoanDelinquencyTag(loan, transactionDate);
return loanChargeAdjustmentTransaction;
}
private void undoWaivedCharge(final Map<String, Object> changes, final Loan loan, final LoanTransaction loanTransaction, private void undoWaivedCharge(final Map<String, Object> changes, final Loan loan, final LoanTransaction loanTransaction,
final LoanChargePaidBy loanChargePaidBy) { final LoanChargePaidBy loanChargePaidBy) {
switch (loanChargePaidBy.getLoanCharge().getChargeTimeType()) { switch (loanChargePaidBy.getLoanCharge().getChargeTimeType()) {
@@ -1057,7 +1084,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
jsonObject.addProperty("dateFormat", dateFormat); jsonObject.addProperty("dateFormat", dateFormat);
} }
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateFormat); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateFormat);
LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); LocalDate transactionDate = DateUtils.getBusinessLocalDate();
String transactionDateString = transactionDate.format(dateTimeFormatter); String transactionDateString = transactionDate.format(dateTimeFormatter);
jsonObject.addProperty("transactionDate", transactionDateString); jsonObject.addProperty("transactionDate", transactionDateString);
if (!this.fromApiJsonHelper.parameterExists("transactionAmount", jsonObject)) { if (!this.fromApiJsonHelper.parameterExists("transactionAmount", jsonObject)) {
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.organisation.staff.data.StaffData; import org.apache.fineract.organisation.staff.data.StaffData;
@@ -155,4 +156,7 @@ public interface LoanReadPlatformService {
List<LoanTransactionRelationData> retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId); List<LoanTransactionRelationData> retrieveLoanTransactionRelationsByLoanTransactionId(Long loanTransactionId);
Long retrieveLoanTransactionIdByExternalId(ExternalId externalId);
Long retrieveLoanIdByExternalId(String externalId);
} }
@@ -41,6 +41,7 @@ import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
@@ -1372,7 +1373,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal overPaymentPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overpayment"); final BigDecimal overPaymentPortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "overpayment");
final BigDecimal unrecognizedIncomePortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "unrecognizedIncome"); final BigDecimal unrecognizedIncomePortion = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "unrecognizedIncome");
final BigDecimal outstandingLoanBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "outstandingLoanBalance"); final BigDecimal outstandingLoanBalance = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "outstandingLoanBalance");
final String externalId = rs.getString("externalId"); final String externalIdStr = rs.getString("externalId");
final ExternalId externalId = StringUtils.isBlank(externalIdStr) ? ExternalId.empty() : new ExternalId(externalIdStr);
final String reversalExternalId = rs.getString("reversalExternalId"); final String reversalExternalId = rs.getString("reversalExternalId");
final LocalDate reversedOnDate = JdbcSupport.getLocalDate(rs, "reversedOnDate"); final LocalDate reversedOnDate = JdbcSupport.getLocalDate(rs, "reversedOnDate");
@@ -2186,7 +2188,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(loanTransactionType); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(loanTransactionType);
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, currentDate, transactionAmount, null, return new LoanTransactionData(null, null, null, transactionType, null, currencyData, currentDate, transactionAmount, null,
netDisbursal, null, null, null, null, null, paymentOptions, null, null, null, null, false, loanId, externalLoanId); netDisbursal, null, null, null, null, null, paymentOptions, ExternalId.empty(), null, null, null, false, loanId,
externalLoanId);
} }
@Override @Override
@@ -2308,6 +2311,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
} }
public String schema() { public String schema() {
// TODO: investigate whether it can be refactored to be more efficient
StringBuilder sqlBuilder = new StringBuilder(); StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("(CASE "); sqlBuilder.append("(CASE ");
sqlBuilder.append( sqlBuilder.append(
@@ -2358,11 +2362,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final String officeName = null; final String officeName = null;
boolean manuallyReversed = false; boolean manuallyReversed = false;
final PaymentDetailData paymentDetailData = null; final PaymentDetailData paymentDetailData = null;
final String externalId = null;
final AccountTransferData transfer = null; final AccountTransferData transfer = null;
final BigDecimal fixedEmiAmount = null; final BigDecimal fixedEmiAmount = null;
return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currencyData, date, totalDue, return new LoanTransactionData(id, officeId, officeName, transactionType, paymentDetailData, currencyData, date, totalDue,
netDisbursalAmount, principalPortion, interestDue, feeDue, penaltyDue, overPaymentPortion, externalId, transfer, netDisbursalAmount, principalPortion, interestDue, feeDue, penaltyDue, overPaymentPortion, ExternalId.empty(), transfer,
fixedEmiAmount, outstandingLoanBalance, unrecognizedIncomePortion, manuallyReversed, loanId, externalLoanId); fixedEmiAmount, outstandingLoanBalance, unrecognizedIncomePortion, manuallyReversed, loanId, externalLoanId);
} }
@@ -2402,4 +2405,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
return loanTransactionRelationMapper.map(loanTransactionRelations); return loanTransactionRelationMapper.map(loanTransactionRelations);
} }
@Override
public Long retrieveLoanTransactionIdByExternalId(ExternalId externalId) {
return loanTransactionRepository.findIdByExternalId(externalId);
}
@Override
public Long retrieveLoanIdByExternalId(String externalId) {
return loanRepositoryWrapper.findIdByExternalId(externalId);
}
} }
@@ -49,11 +49,13 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables; import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum; import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService; import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
@@ -244,6 +246,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine; private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final LoanAccountLockService loanAccountLockService; private final LoanAccountLockService loanAccountLockService;
private final ExternalIdFactory externalIdFactory;
@Transactional @Transactional
@Override @Override
public CommandProcessingResult disburseGLIMLoan(final Long loanId, final JsonCommand command) { public CommandProcessingResult disburseGLIMLoan(final Long loanId, final JsonCommand command) {
@@ -382,7 +386,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate); Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
Money amountToDisburse = disburseAmount.copy(); Money amountToDisburse = disburseAmount.copy();
boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincipal()); boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincipal());
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
if (loan.isTopup() && loan.getClientId() != null) { if (loan.isTopup() && loan.getClientId() != null) {
final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose(); final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose();
@@ -486,8 +490,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.getId(), loanId, "Loan Charge Payment", PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.getId(), loanId, "Loan Charge Payment",
locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null, locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), entrySet.getKey(), null,
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, ExternalId.empty(), null, null, fromSavingsAccount,
isExceptionForBalanceCheck); isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} }
@@ -505,17 +509,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalBusinessEvent(loan));
Long entityId = loan.getId(); Long entityId = loan.getId();
ExternalId externalId = ExternalId.empty();
// During a disbursement, the entityId should be the disbursement transaction id // During a disbursement, the entityId should be the disbursement transaction id
if (!isAccountTransfer) { if (!isAccountTransfer) {
LoanTransaction disbursalTransaction = loan.getLoanTransactions().get(loan.getLoanTransactions().size() - 1); LoanTransaction disbursalTransaction = loan.getLoanTransactions().get(loan.getLoanTransactions().size() - 1);
entityId = disbursalTransaction.getId(); entityId = disbursalTransaction.getId();
externalId = disbursalTransaction.getExternalId();
businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction)); businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
} }
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(entityId) // .withEntityId(entityId) //
.withEntityExternalId(externalId) //
.withOfficeId(loan.getOfficeId()) // .withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) // .withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) // .withGroupId(loan.getGroupId()) //
@@ -608,7 +615,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) { if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique"); baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).failWithCode("value.must.be.unique");
} }
if (!dataValidationErrors.isEmpty()) { if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
@@ -626,7 +633,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction"); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) { if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique"); baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).failWithCode("value.must.be.unique");
} }
if (!dataValidationErrors.isEmpty()) { if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
@@ -687,7 +694,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Money amountBeforeAdjust = loan.getPrincipal(); Money amountBeforeAdjust = loan.getPrincipal();
Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate); Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate);
boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincipal()); boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincipal());
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
if (isAccountTransfer) { if (isAccountTransfer) {
disburseLoanToSavings(loan, command, disburseAmount, paymentDetail); disburseLoanToSavings(loan, command, disburseAmount, paymentDetail);
existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingTransactionIds.addAll(loan.findExistingTransactionIds());
@@ -753,7 +760,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(actualDisbursementDate, entrySet.getValue(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.getId(), loan.getId(), PortfolioAccountType.SAVINGS, PortfolioAccountType.LOAN, savingAccountData.getId(), loan.getId(),
"Loan Charge Payment", locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(), "Loan Charge Payment", locale, fmt, null, null, LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.getValue(),
entrySet.getKey(), null, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, entrySet.getKey(), null, AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, ExternalId.empty(), null, null,
fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck); fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} }
@@ -907,7 +914,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
@@ -920,15 +927,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText); changes.put("note", noteText);
} }
if (!txnExternalId.isEmpty()) {
changes.put(LoanApiConstants.externalIdParameterName, txnExternalId);
}
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
final Boolean isHolidayValidationDone = false; final Boolean isHolidayValidationDone = false;
final HolidayDetailDTO holidayDetailDto = null; final HolidayDetailDTO holidayDetailDto = null;
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(repaymentTransactionType, loan, LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(repaymentTransactionType, loan, transactionDate,
commandProcessingResultBuilder, transactionDate, transactionAmount, paymentDetail, noteText, txnExternalId, transactionAmount, paymentDetail, noteText, txnExternalId, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer,
isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, holidayDetailDto, isHolidayValidationDone); holidayDetailDto, isHolidayValidationDone);
// Update loan transaction on repayment. // Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) { if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
@@ -947,8 +957,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.updateLoanCollateralTransaction(loanCollateralManagements); this.loanAccountDomainService.updateLoanCollateralTransaction(loanCollateralManagements);
} }
return commandProcessingResultBuilder.withCommandId(command.commandId()) // return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
.withLoanId(loanId) // .withLoanId(loan.getId()) //
.withEntityId(loanTransaction.getId()) //
.withEntityExternalId(loanTransaction.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.with(changes) // .with(changes) //
.build(); .build();
} }
@@ -993,15 +1008,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (singleLoanRepaymentCommand != null) { if (singleLoanRepaymentCommand != null) {
final Loan loan = this.loanAssembler.assembleFrom(singleLoanRepaymentCommand.getLoanId()); final Loan loan = this.loanAssembler.assembleFrom(singleLoanRepaymentCommand.getLoanId());
final PaymentDetail paymentDetail = singleLoanRepaymentCommand.getPaymentDetail(); final PaymentDetail paymentDetail = singleLoanRepaymentCommand.getPaymentDetail();
ExternalId externalId = singleLoanRepaymentCommand.getExternalId();
if (externalId.isEmpty() && configurationDomainService.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
if (paymentDetail != null && paymentDetail.getId() == null) { if (paymentDetail != null && paymentDetail.getId() == null) {
this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail); this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
} }
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
final String chargeRefundChargeType = null; final String chargeRefundChargeType = null;
LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT, loan, LoanTransaction loanTransaction = this.loanAccountDomainService.makeRepayment(LoanTransactionType.REPAYMENT, loan,
commandProcessingResultBuilder, bulkRepaymentCommand.getTransactionDate(), bulkRepaymentCommand.getTransactionDate(), singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail,
singleLoanRepaymentCommand.getTransactionAmount(), paymentDetail, bulkRepaymentCommand.getNote(), null, bulkRepaymentCommand.getNote(), externalId, isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer,
isRecoveryRepayment, chargeRefundChargeType, isAccountTransfer, holidayDetailDTO, isHolidayValidationDone); holidayDetailDTO, isHolidayValidationDone);
transactionIds.add(loanTransaction.getId()); transactionIds.add(loanTransaction.getId());
} }
} }
@@ -1014,6 +1032,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) { public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) {
this.loanEventApiJsonValidator.validateTransaction(command.json()); this.loanEventApiJsonValidator.validateTransaction(command.json());
LoanTransaction transactionToAdjust = this.loanTransactionRepository.findByIdAndLoanId(command.entityId(), command.getLoanId())
.orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId(), command.getLoanId()));
Loan loan = this.loanAssembler.assembleFrom(loanId); Loan loan = this.loanAssembler.assembleFrom(loanId);
if (loan.getStatus().isClosed() && loan.getLoanSubStatus() != null if (loan.getStatus().isClosed() && loan.getLoanSubStatus() != null
@@ -1022,8 +1042,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LoanForeclosureException("loan.cannot.be.reopened.as.it.is.foreclosured", defaultUserMessage, loanId); throw new LoanForeclosureException("loan.cannot.be.reopened.as.it.is.foreclosured", defaultUserMessage, loanId);
} }
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
final LoanTransaction transactionToAdjust = this.loanTransactionRepository.findById(transactionId)
.orElseThrow(() -> new LoanTransactionNotFoundException(transactionId));
businessEventNotifierService.notifyPreBusinessEvent( businessEventNotifierService.notifyPreBusinessEvent(
new LoanAdjustTransactionBusinessEvent(new LoanAdjustTransactionBusinessEvent.Data(transactionToAdjust))); new LoanAdjustTransactionBusinessEvent(new LoanAdjustTransactionBusinessEvent.Data(transactionToAdjust)));
if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) { if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) {
@@ -1049,8 +1068,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final String reversalExternalId = command.stringValueOfParameterNamedAllowingNull(LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME); // TODO: can it be refactored to use the 'externalId' for reversal?
final ExternalId reversalTxnExternalId = externalIdFactory.createFromCommand(command,
LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME);
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
@@ -1077,7 +1098,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
newTransactionDetail = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, transactionDate, newTransactionDetail = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, transactionDate,
interestComponent, unrecognizedIncome); interestComponent, unrecognizedIncome, txnExternalId);
} }
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
@@ -1091,9 +1112,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail,
defaultLoanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, defaultLoanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
scheduleGeneratorDTO, reversalExternalId); scheduleGeneratorDTO, reversalTxnExternalId);
if (newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency())) { boolean thereIsNewTransaction = newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency());
if (thereIsNewTransaction) {
if (paymentDetail != null) { if (paymentDetail != null) {
this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail); this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail);
} }
@@ -1122,7 +1144,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
/** /**
* If a new transaction is not created, associate note with the transaction to be adjusted * If a new transaction is not created, associate note with the transaction to be adjusted
**/ **/
if (newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency())) { if (thereIsNewTransaction) {
note = Note.loanTransactionNote(loan, newTransactionDetail, noteText); note = Note.loanTransactionNote(loan, newTransactionDetail, noteText);
} else { } else {
note = Note.loanTransactionNote(loan, transactionToAdjust, noteText); note = Note.loanTransactionNote(loan, transactionToAdjust, noteText);
@@ -1151,14 +1173,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); this.loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
LoanAdjustTransactionBusinessEvent.Data eventData = new LoanAdjustTransactionBusinessEvent.Data(transactionToAdjust); LoanAdjustTransactionBusinessEvent.Data eventData = new LoanAdjustTransactionBusinessEvent.Data(transactionToAdjust);
if (newTransactionDetail.isRepaymentType() && newTransactionDetail.isGreaterThanZero(loan.getPrincipal().getCurrency())) { if (newTransactionDetail.isRepaymentType() && thereIsNewTransaction) {
eventData.setNewTransactionDetail(newTransactionDetail); eventData.setNewTransactionDetail(newTransactionDetail);
} }
Long entityId = transactionToAdjust.getId();
ExternalId entityExternalId = transactionToAdjust.getExternalId();
if (thereIsNewTransaction) {
entityId = newTransactionDetail.getId();
entityExternalId = newTransactionDetail.getExternalId();
}
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData)); businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(eventData));
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(transactionId) return new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId) .withCommandId(command.commandId()) //
.withEntityId(entityId) //
.withEntityExternalId(entityExternalId) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes).build(); .with(changes).build();
} }
@@ -1167,6 +1202,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public CommandProcessingResult chargebackLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) { public CommandProcessingResult chargebackLoanTransaction(final Long loanId, final Long transactionId, final JsonCommand command) {
this.loanEventApiJsonValidator.validateChargebackTransaction(command.json()); this.loanEventApiJsonValidator.validateChargebackTransaction(command.json());
LoanTransaction loanTransaction = this.loanTransactionRepository.findByIdAndLoanId(command.entityId(), command.getLoanId())
.orElseThrow(() -> new LoanTransactionNotFoundException(command.entityId(), command.getLoanId()));
Loan loan = this.loanAssembler.assembleFrom(loanId); Loan loan = this.loanAssembler.assembleFrom(loanId);
if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) { if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.LOAN)) {
throw new PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed", throw new PlatformServiceUnavailableException("error.msg.loan.transfer.transaction.update.not.allowed",
@@ -1187,9 +1225,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
LoanTransaction loanTransaction = this.loanTransactionRepository.findById(transactionId)
.orElseThrow(() -> new LoanTransactionNotFoundException(transactionId));
if (loanTransaction.isReversed()) { if (loanTransaction.isReversed()) {
throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed", throw new PlatformServiceUnavailableException("error.msg.loan.chargeback.operation.not.allowed",
"Loan transaction:" + transactionId + " chargeback not allowed as loan transaction repayment is reversed", "Loan transaction:" + transactionId + " chargeback not allowed as loan transaction repayment is reversed",
@@ -1207,7 +1242,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LocalDate transactionDate = DateUtils.getBusinessLocalDate(); final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME);
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull(LoanApiConstants.externalIdParameterName); final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionAmount", command.stringValueOfParameterNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME)); changes.put("transactionAmount", command.stringValueOfParameterNamed(LoanApiConstants.TRANSACTION_AMOUNT_PARAMNAME));
@@ -1247,8 +1282,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new LoanChargebackTransactionBusinessEvent(loanTransaction)); businessEventNotifierService.notifyPostBusinessEvent(new LoanChargebackTransactionBusinessEvent(loanTransaction));
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(newTransaction.getId()) return new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId) .withCommandId(command.commandId()) //
.withEntityId(newTransaction.getId()) //
.withEntityExternalId(newTransaction.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes).build(); .with(changes).build();
} }
@@ -1280,6 +1321,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changes.put("dateFormat", command.dateFormat()); changes.put("dateFormat", command.dateFormat());
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
Loan loan = this.loanAssembler.assembleFrom(loanId); Loan loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
@@ -1298,7 +1340,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney,
transactionDate, interestComponent, unrecognizedIncome); transactionDate, interestComponent, unrecognizedIncome, externalId);
businessEventNotifierService.notifyPreBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction)); businessEventNotifierService.notifyPreBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction));
LocalDate recalculateFrom = null; LocalDate recalculateFrom = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -1339,8 +1381,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction)); businessEventNotifierService.notifyPostBusinessEvent(new LoanWaiveInterestBusinessEvent(waiveInterestTransaction));
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(waiveInterestTransaction.getId()) return new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId) .withCommandId(command.commandId()) //
.withEntityId(waiveInterestTransaction.getId()) //
.withEntityExternalId(waiveInterestTransaction.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes).build(); .with(changes).build();
} }
@@ -1404,8 +1452,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate()); loanAccountDomainService.setLoanDelinquencyTag(loan, DateUtils.getBusinessLocalDate());
businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff)); businessEventNotifierService.notifyPostBusinessEvent(new LoanWrittenOffPostBusinessEvent(writeOff));
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(writeOff.getId()) return new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId) .withCommandId(command.commandId()) //
.withEntityId(writeOff.getId()) //
.withEntityExternalId(writeOff.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes).build(); .with(changes).build();
} }
@@ -1485,12 +1539,23 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
CommandProcessingResult result; CommandProcessingResult result;
if (possibleClosingTransaction != null) { if (possibleClosingTransaction != null) {
result = new CommandProcessingResultBuilder().withCommandId(command.commandId()) result = new CommandProcessingResultBuilder() //
.withEntityId(possibleClosingTransaction.getId()).withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()) .withCommandId(command.commandId()) //
.withGroupId(loan.getGroupId()).withLoanId(loanId).with(changes).build(); .withEntityId(possibleClosingTransaction.getId()) //
.withEntityExternalId(possibleClosingTransaction.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes).build();
} else { } else {
result = new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loanId) result = new CommandProcessingResultBuilder() //
.withOfficeId(loan.getOfficeId()).withClientId(loan.getClientId()).withGroupId(loan.getGroupId()).withLoanId(loanId) .withCommandId(command.commandId()) //
.withEntityId(loanId) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withLoanId(loanId) //
.with(changes).build(); .with(changes).build();
} }
@@ -1558,7 +1623,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private void disburseLoanToLoan(final Loan loan, final JsonCommand command, final BigDecimal amount) { private void disburseLoanToLoan(final Loan loan, final JsonCommand command, final BigDecimal amount) {
final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
@@ -1573,7 +1638,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) { private void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) {
final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
@@ -1606,8 +1671,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
ExternalId externalId = externalIdFactory.create();
final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate); final LoanTransaction newTransferTransaction = LoanTransaction.initiateTransfer(loan.getOffice(), loan, transferDate, externalId);
loan.addLoanTransaction(newTransferTransaction); loan.addLoanTransaction(newTransferTransaction);
LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine; LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine;
loanLifecycleStateMachine.transition(LoanEvent.LOAN_INITIATE_TRANSFER, loan); loanLifecycleStateMachine.transition(LoanEvent.LOAN_INITIATE_TRANSFER, loan);
@@ -1628,8 +1693,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPreBusinessEvent(new LoanAcceptTransferBusinessEvent(loan)); businessEventNotifierService.notifyPreBusinessEvent(new LoanAcceptTransferBusinessEvent(loan));
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
ExternalId externalId = externalIdFactory.create();
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate); final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate,
externalId);
loan.addLoanTransaction(newTransferAcceptanceTransaction); loan.addLoanTransaction(newTransferAcceptanceTransaction);
LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine; LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine;
if (loan.getTotalOverpaid() != null) { if (loan.getTotalOverpaid() != null) {
@@ -1659,7 +1725,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate); ExternalId externalId = externalIdFactory.create();
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, transferDate,
externalId);
loan.addLoanTransaction(newTransferAcceptanceTransaction); loan.addLoanTransaction(newTransferAcceptanceTransaction);
LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine; LoanLifecycleStateMachine loanLifecycleStateMachine = defaultLoanLifecycleStateMachine;
loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAW_TRANSFER, loan); loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAW_TRANSFER, loan);
@@ -2076,6 +2145,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.withClientId(loan.getClientId()) // .withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) // .withGroupId(loan.getGroupId()) //
.withLoanId(loanId) // .withLoanId(loanId) //
.withEntityId(writeOffTransaction.getId()) //
.withEntityExternalId(writeOffTransaction.getExternalId()) //
.build(); .build();
} }
@@ -2266,7 +2337,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Override @Override
public CommandProcessingResult recoverFromGuarantor(final Long loanId) { public CommandProcessingResult recoverFromGuarantor(final Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
this.guarantorDomainService.transaferFundsFromGuarantor(loan); this.guarantorDomainService.transferFundsFromGuarantor(loan);
return new CommandProcessingResultBuilder().withLoanId(loanId).build(); return new CommandProcessingResultBuilder().withLoanId(loanId).build();
} }
@@ -2333,10 +2404,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand command) { public CommandProcessingResult creditBalanceRefund(Long loanId, JsonCommand command) {
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json()); this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
Loan loan = this.loanAssembler.assembleFrom(loanId);
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
final String noteText = command.stringValueOfParameterNamedAllowingNull("note"); final String noteText = command.stringValueOfParameterNamedAllowingNull("note");
final String externalId = command.stringValueOfParameterNamedAllowingNull("externalId"); final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
@@ -2347,15 +2419,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
changes.put("note", noteText); changes.put("note", noteText);
} }
if (StringUtils.isNotBlank(externalId)) { if (!externalId.isEmpty()) {
changes.put("externalId", externalId); changes.put(LoanApiConstants.externalIdParameterName, externalId);
} }
final CommandProcessingResultBuilder commandProcessingResultBuilder = this.loanAccountDomainService.creditBalanceRefund(loanId, final LoanTransaction loanTransaction = this.loanAccountDomainService.creditBalanceRefund(loan, transactionDate, transactionAmount,
transactionDate, transactionAmount, noteText, externalId); noteText, externalId);
return commandProcessingResultBuilder // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()).with(changes) // .withEntityId(loanTransaction.getId()) //
.withEntityExternalId(loanTransaction.getExternalId()) //
.withOfficeId(loan.getOfficeId()) //
.withClientId(loan.getClientId()) //
.withGroupId(loan.getGroupId()) //
.withCommandId(command.commandId()) //
.with(changes) //
.build(); .build();
} }
@@ -2398,6 +2476,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json()); this.loanEventApiJsonValidator.validateNewRefundTransaction(command.json());
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
// checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate); // checkRefundDateIsAfterAtLeastOneRepayment(loanId, transactionDate);
@@ -2409,6 +2488,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount")); changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
changes.put("locale", command.locale()); changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat()); changes.put("dateFormat", command.dateFormat());
changes.put(LoanApiConstants.externalIdParameterName, externalId);
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
@@ -2419,11 +2499,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
this.loanAccountDomainService.makeRefundForActiveLoan(loanId, commandProcessingResultBuilder, transactionDate, transactionAmount, LoanTransaction loanTransaction = this.loanAccountDomainService.makeRefundForActiveLoan(loanId, commandProcessingResultBuilder,
paymentDetail, noteText, null); transactionDate, transactionAmount, paymentDetail, noteText, externalId);
return commandProcessingResultBuilder.withCommandId(command.commandId()) // return commandProcessingResultBuilder //
.withCommandId(command.commandId()) //
.withLoanId(loanId) // .withLoanId(loanId) //
.withEntityId(loanTransaction.getId()) //
.withEntityExternalId(loanTransaction.getExternalId()) //
.with(changes) // .with(changes) //
.build(); .build();
@@ -2502,9 +2585,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final JsonElement element = fromApiJsonHelper.parse(json); final JsonElement element = fromApiJsonHelper.parse(json);
final Loan loan = this.loanAssembler.assembleFrom(loanId); final Loan loan = this.loanAssembler.assembleFrom(loanId);
final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.transactionDateParamName, element); final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.transactionDateParamName, element);
final ExternalId externalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
this.loanEventApiJsonValidator.validateLoanForeclosure(command.json()); this.loanEventApiJsonValidator.validateLoanForeclosure(command.json());
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
changes.put("transactionDate", transactionDate); changes.put("transactionDate", transactionDate);
changes.put("externalId", externalId);
String noteText = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParamName, element); String noteText = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.noteParamName, element);
LoanRescheduleRequest loanRescheduleRequest = null; LoanRescheduleRequest loanRescheduleRequest = null;
@@ -2519,11 +2604,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan,
loanRescheduleRequest); loanRescheduleRequest);
final Map<String, Object> modifications = this.loanAccountDomainService.foreCloseLoan(loan, transactionDate, noteText); final Map<String, Object> modifications = this.loanAccountDomainService.foreCloseLoan(loan, transactionDate, noteText, externalId);
changes.putAll(modifications); changes.putAll(modifications);
final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder(); final CommandProcessingResultBuilder commandProcessingResultBuilder = new CommandProcessingResultBuilder();
return commandProcessingResultBuilder.withLoanId(loanId) // return commandProcessingResultBuilder //
.withLoanId(loanId) //
.with(changes) // .with(changes) //
.build(); .build();
} }
@@ -2566,5 +2652,4 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
} }
@@ -37,6 +37,7 @@ import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumb
import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType; import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -242,8 +243,8 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final boolean isExceptionForBalanceCheck = false; final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null,
isAccountTransfer, isExceptionForBalanceCheck); toSavingsAccount, account, isAccountTransfer, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
updateAlreadyPostedTransactions(existingTransactionIds, account); updateAlreadyPostedTransactions(existingTransactionIds, account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
@@ -315,8 +316,8 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final boolean isExceptionForBalanceCheck = false; final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, null, fmt, null, null, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, null, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null,
isAccountTransfer, isExceptionForBalanceCheck); toSavingsAccount, account, isAccountTransfer, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
updateAlreadyPostedTransactions(existingTransactionIds, account); updateAlreadyPostedTransactions(existingTransactionIds, account);
account.updateClosedStatus(); account.updateClosedStatus();
@@ -400,8 +401,8 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final boolean isExceptionForBalanceCheck = false; final boolean isExceptionForBalanceCheck = false;
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, transactionAmount, final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, transactionAmount,
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null,
isRegularTransaction, isExceptionForBalanceCheck); toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
updateAlreadyPostedTransactions(existingTransactionIds, account); updateAlreadyPostedTransactions(existingTransactionIds, account);
} else { } else {
@@ -491,8 +492,8 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
DepositAccountType.SAVINGS_DEPOSIT); DepositAccountType.SAVINGS_DEPOSIT);
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null,
isRegularTransaction, isExceptionForBalanceCheck); toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
updateAlreadyPostedTransactions(existingTransactionIds, account); updateAlreadyPostedTransactions(existingTransactionIds, account);
} else { } else {
@@ -547,8 +548,8 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
DepositAccountType.SAVINGS_DEPOSIT); DepositAccountType.SAVINGS_DEPOSIT);
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, toSavingsAccount, account, null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null,
isRegularTransaction, isExceptionForBalanceCheck); toSavingsAccount, account, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
updateAlreadyPostedTransactions(existingTransactionIds, account); updateAlreadyPostedTransactions(existingTransactionIds, account);
} else { } else {
@@ -32,6 +32,7 @@ import java.util.Set;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.data.PaginationParameters; import org.apache.fineract.infrastructure.core.data.PaginationParameters;
import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator; import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
@@ -1700,8 +1701,8 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, TRANSACTION_DATE); final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, TRANSACTION_DATE);
return new AccountTransferDTO(transactionDate, transactionAmount, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, return new AccountTransferDTO(transactionDate, transactionAmount, PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
fromAccountId, toAccountId, TRANSFER_INTEREST_TO_SAVINGS, null, null, null, null, null, null, null, fromAccountId, toAccountId, TRANSFER_INTEREST_TO_SAVINGS, null, null, null, null, null, null, null,
AccountTransferType.INTEREST_TRANSFER.getValue(), null, null, null, null, null, null, isRegularTransaction, AccountTransferType.INTEREST_TRANSFER.getValue(), null, null, ExternalId.empty(), null, null, null,
isExceptionForBalanceCheck); isRegularTransaction, isExceptionForBalanceCheck);
} }
} }
@@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -181,8 +182,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(),
amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
portfolioAccountData.getId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null, null, portfolioAccountData.getId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null, null,
AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, account, fromSavingsAccount, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null, account,
isRegularTransaction, isExceptionForBalanceCheck); fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} }
final boolean isInterestTransfer = false; final boolean isInterestTransfer = false;
@@ -285,8 +286,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(), final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(account.getActivationLocalDate(),
amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, amountForDeposit.getAmount(), PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS,
portfolioAccountData.getId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null, null, portfolioAccountData.getId(), account.getId(), "Account Transfer", locale, fmt, null, null, null, null, null,
AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, null, null, account, fromSavingsAccount, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null, account,
isRegularTransaction, isExceptionForBalanceCheck); fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
} }
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -90,4 +90,5 @@
<include file="parts/0068_loan_charge_adjustment.xml" relativeToChangelogFile="true"/> <include file="parts/0068_loan_charge_adjustment.xml" relativeToChangelogFile="true"/>
<include file="parts/0069_add_unique_constraint_for_reversal_external_id_of_loan_transactions.xml" relativeToChangelogFile="true"/> <include file="parts/0069_add_unique_constraint_for_reversal_external_id_of_loan_transactions.xml" relativeToChangelogFile="true"/>
<include file="parts/0070_add_event_configuration_for_delinquency_range_change_event.xml" relativeToChangelogFile="true"/> <include file="parts/0070_add_event_configuration_for_delinquency_range_change_event.xml" relativeToChangelogFile="true"/>
<include file="parts/0071_add_external_id_support_for_loan_transaction.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<changeSet author="fineract" id="1">
<insert tableName="c_configuration">
<column name="id" valueNumeric="50"/>
<column name="name" value="enable-auto-generated-external-id"/>
<column name="value"/>
<column name="date_value"/>
<column name="string_value"/>
<column name="enabled" valueBoolean="false"/>
<column name="is_trap_door" valueBoolean="false"/>
<column name="description"/>
</insert>
</changeSet>
<changeSet id="2" author="fineract">
<addColumn tableName="m_portfolio_command_source">
<column name="resource_external_id" type="VARCHAR(100)"/>
<column name="subresource_external_id" type="VARCHAR(100)"/>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -5913,6 +5913,7 @@ public class ClientLoanIntegrationTest {
this.loanTransactionHelper.makeRepayment(loanPrepaymentDate, Float.parseFloat(prepayAmount), loanID); this.loanTransactionHelper.makeRepayment(loanPrepaymentDate, Float.parseFloat(prepayAmount), loanID);
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", false);
} }
@Test @Test
@@ -5984,6 +5985,7 @@ public class ClientLoanIntegrationTest {
HashMap savingsAccountErrorData = validationErrorHelper.makeRepayment(retrieveDueDate, amount, loanID); HashMap savingsAccountErrorData = validationErrorHelper.makeRepayment(retrieveDueDate, amount, loanID);
ArrayList<HashMap> error = (ArrayList<HashMap>) savingsAccountErrorData.get("errors"); ArrayList<HashMap> error = (ArrayList<HashMap>) savingsAccountErrorData.get("errors");
assertEquals("error.msg.loan.transaction.cannot.be.a.future.date", error.get(0).get("userMessageGlobalisationCode")); assertEquals("error.msg.loan.transaction.cannot.be.a.future.date", error.get(0).get("userMessageGlobalisationCode"));
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", false);
} }
@Test @Test
@@ -6016,7 +6018,8 @@ public class ClientLoanIntegrationTest {
ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(404).build(); ResponseSpecification responseSpec = new ResponseSpecBuilder().expectStatusCode(404).build();
LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, responseSpec); LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, responseSpec);
HashMap response = loanTransactionHelper.undoWaiveChargesForLoan(-1, -2, ""); HashMap response = loanTransactionHelper.undoWaiveChargesForLoan(-1, -2, "");
assertEquals("error.msg.loan.id.invalid", ((Map) ((List) response.get("errors")).get(0)).get("userMessageGlobalisationCode")); assertEquals("error.msg.loan.transaction.id.invalid",
((Map) ((List) response.get("errors")).get(0)).get("userMessageGlobalisationCode"));
assertEquals("Transaction with identifier -2 does not exist for loan with identifier -1.", assertEquals("Transaction with identifier -2 does not exist for loan with identifier -1.",
((Map) ((List) response.get("errors")).get(0)).get("defaultUserMessage")); ((Map) ((List) response.get("errors")).get(0)).get("defaultUserMessage"));
} }
@@ -6176,13 +6179,13 @@ public class ClientLoanIntegrationTest {
assertEquals(1000.0f, loanSummary.get("totalOutstanding")); assertEquals(1000.0f, loanSummary.get("totalOutstanding"));
GetLoansLoanIdTransactionsTransactionIdResponse chargeAdjustmentTransaction = this.loanTransactionHelper GetLoansLoanIdTransactionsTransactionIdResponse chargeAdjustmentTransaction = this.loanTransactionHelper
.getLoanTransactionDetails((long) loanID, chargeAdjustmentResponse.getResourceId()); .getLoanTransactionDetails((long) loanID, chargeAdjustmentResponse.getSubResourceId());
assertEquals(10.0, chargeAdjustmentTransaction.getAmount()); assertEquals(10.0, chargeAdjustmentTransaction.getAmount());
assertEquals(10.0, chargeAdjustmentTransaction.getPenaltyChargesPortion()); assertEquals(10.0, chargeAdjustmentTransaction.getPenaltyChargesPortion());
assertEquals("loanTransactionType.chargeAdjustment", chargeAdjustmentTransaction.getType().getCode()); assertEquals("loanTransactionType.chargeAdjustment", chargeAdjustmentTransaction.getType().getCode());
assertEquals(externalId, chargeAdjustmentTransaction.getExternalId()); assertEquals(externalId, chargeAdjustmentTransaction.getExternalId());
GetLoanTransactionRelation transactionRelation = chargeAdjustmentTransaction.getTransactionRelations().iterator().next(); GetLoanTransactionRelation transactionRelation = chargeAdjustmentTransaction.getTransactionRelations().iterator().next();
assertEquals(chargeAdjustmentResponse.getResourceId(), transactionRelation.getFromLoanTransaction()); assertEquals(chargeAdjustmentResponse.getSubResourceId(), transactionRelation.getFromLoanTransaction());
assertEquals((long) penalty1LoanChargeId, transactionRelation.getToLoanCharge()); assertEquals((long) penalty1LoanChargeId, transactionRelation.getToLoanCharge());
assertEquals("CHARGE_ADJUSTMENT", transactionRelation.getRelationType()); assertEquals("CHARGE_ADJUSTMENT", transactionRelation.getRelationType());
@@ -0,0 +1,431 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.HashMap;
import java.util.UUID;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.integrationtests.client.IntegrationTest;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class ExternalIdSupportIntegrationTest extends IntegrationTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private ClientHelper clientHelper;
private AccountHelper accountHelper;
private LoanTransactionHelper loanTransactionHelper;
private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
@BeforeAll
public void init() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
}
@Test
public void test() {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 50, true);
final Account assetAccount = this.accountHelper.createAssetAccount();
final Account assetFeeAndPenaltyAccount = this.accountHelper.createAssetAccount();
final Account incomeAccount = this.accountHelper.createIncomeAccount();
final Account expenseAccount = this.accountHelper.createExpenseAccount();
final Account overpaymentAccount = this.accountHelper.createLiabilityAccount();
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true));
Integer penalty2 = ChargesHelper.createCharges(requestSpec, responseSpec,
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true, 1));
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
.withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat()
.withAccountingRulePeriodicAccrual(new Account[] { assetAccount, incomeAccount, expenseAccount, overpaymentAccount })
.withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0")
.withFeeAndPenaltyAssetAccount(assetFeeAndPenaltyAccount).build(null);
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
String loanExternalIdStr = UUID.randomUUID().toString();
final HashMap loan = applyForLoanApplication(client.getClientId().intValue(), loanProductID, loanExternalIdStr);
Integer loanId = (Integer) loan.get("resourceId");
this.loanTransactionHelper.approveLoan("02 September 2022", loanId);
String txnExternalIdStr = UUID.randomUUID().toString();
final HashMap disbursedLoanResult = this.loanTransactionHelper.disburseLoan("03 September 2022", loanId, "1000", txnExternalIdStr);
// Check whether the provided external id was retrieved
assertEquals(txnExternalIdStr, disbursedLoanResult.get("resourceExternalId"));
LocalDate targetDate = LocalDate.of(2022, 9, 7);
final String penaltyCharge1AddedDate = dateFormatter.format(targetDate);
Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10"));
Integer penalty2LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10"));
// Check whether we can fetch transaction templates with proper result http code (HTTP 200..300)
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "repayment", null, null, null));
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "payoutRefund", null, null, null));
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "waiveinterest", null, null, null));
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "close-rescheduled", null, null, null));
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "disburse", null, null, null));
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "recoverypayment", null, null, null));
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "refundbycash", null, null, null));
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "foreclosure", null, null, null));
ok(fineract().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, "creditBalanceRefund", null, null, null));
// Check whether an external id was generated
String waiveChargeExternalIdStr = UUID.randomUUID().toString();
PostLoansLoanIdChargesChargeIdResponse waiveLoanChargeResult = loanTransactionHelper.waiveLoanCharge((long) loanId,
(long) penalty1LoanChargeId, new PostLoansLoanIdChargesChargeIdRequest().externalId(waiveChargeExternalIdStr));
assertEquals(waiveChargeExternalIdStr, waiveLoanChargeResult.getSubResourceExternalId());
// Check whether an external id was generated
HashMap undoWaiveLoanChargeResult = loanTransactionHelper.undoWaiveLoanCharge((long) loanId, waiveChargeExternalIdStr);
assertEquals(waiveChargeExternalIdStr, undoWaiveLoanChargeResult.get("subResourceExternalId"));
// Check whether an external id was generated
waiveLoanChargeResult = loanTransactionHelper.waiveLoanCharge((long) loanId, (long) penalty1LoanChargeId,
new PostLoansLoanIdChargesChargeIdRequest());
assertNotNull(waiveLoanChargeResult.getSubResourceExternalId());
// Check whether an external id was generated
undoWaiveLoanChargeResult = loanTransactionHelper.undoWaiveLoanCharge(loanExternalIdStr, waiveLoanChargeResult.getSubResourceId());
assertNotNull(undoWaiveLoanChargeResult.get("subResourceExternalId"));
// Check whether an external id was generated
waiveChargeExternalIdStr = UUID.randomUUID().toString();
waiveLoanChargeResult = loanTransactionHelper.waiveLoanCharge((long) loanId, (long) penalty1LoanChargeId,
new PostLoansLoanIdChargesChargeIdRequest().externalId(waiveChargeExternalIdStr));
assertEquals(waiveChargeExternalIdStr, waiveLoanChargeResult.getSubResourceExternalId());
// Check whether an external id was generated
undoWaiveLoanChargeResult = loanTransactionHelper.undoWaiveLoanCharge(loanExternalIdStr,
waiveLoanChargeResult.getSubResourceExternalId());
assertEquals(waiveChargeExternalIdStr, undoWaiveLoanChargeResult.get("subResourceExternalId"));
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse repaymentResult = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en")
.transactionAmount(5.0));
assertNotNull(repaymentResult.getResourceExternalId());
// Check whether the provided external id was retrieved
String transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse repaymentResultWithExternalId = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en")
.transactionAmount(5.0).externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, repaymentResultWithExternalId.getResourceExternalId());
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse merchantIssuedRefundResult = loanTransactionHelper
.makeMerchantIssuedRefund(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("06 September 2022").locale("en").transactionAmount(5.0));
assertNotNull(merchantIssuedRefundResult.getResourceExternalId());
// Check whether the provided external id was retrieved
transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse merchantIssuedRefundResultWithExternalId = loanTransactionHelper
.makeMerchantIssuedRefund(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("06 September 2022").locale("en").transactionAmount(5.0).externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, merchantIssuedRefundResultWithExternalId.getResourceExternalId());
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse payoutRefundResult = loanTransactionHelper.makePayoutRefund(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en")
.transactionAmount(5.0));
assertNotNull(payoutRefundResult.getResourceExternalId());
// Check whether the provided external id was retrieved
transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse payoutRefundResultWithExternalId = loanTransactionHelper
.makePayoutRefund(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("06 September 2022").locale("en").transactionAmount(5.0).externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, payoutRefundResultWithExternalId.getResourceExternalId());
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse goodWillCreditResult = loanTransactionHelper.makeGoodwillCredit(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en")
.transactionAmount(5.0));
assertNotNull(goodWillCreditResult.getResourceExternalId());
// Check whether the provided external id was retrieved
transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse goodWillCreditResultWithExternalId = loanTransactionHelper
.makeGoodwillCredit(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("06 September 2022").locale("en").transactionAmount(5.0).externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, goodWillCreditResultWithExternalId.getResourceExternalId());
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse writeoffResult = loanTransactionHelper.makeWriteoff(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en"));
assertNotNull(writeoffResult.getResourceExternalId());
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse makeRecoveryPaymentResult = loanTransactionHelper.makeRecoveryPayment(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en")
.transactionAmount(5.0));
assertNotNull(makeRecoveryPaymentResult.getResourceExternalId());
// Check whether the provided external id was retrieved
transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse makeRecoveryPaymentResultWithExternalId = loanTransactionHelper
.makeRecoveryPayment(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("06 September 2022").locale("en").transactionAmount(5.0).externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, makeRecoveryPaymentResultWithExternalId.getResourceExternalId());
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse undoWriteoffResult = loanTransactionHelper.makeUndoWriteoff(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en")
.transactionAmount(5.0));
assertNotNull(undoWriteoffResult.getResourceExternalId());
// Check whether the provided external id was retrieved
transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse writeoffResultWithExternalId = loanTransactionHelper.makeWriteoff(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("06 September 2022").locale("en")
.externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, writeoffResultWithExternalId.getResourceExternalId());
// Check whether the provided external id was retrieved
final PostLoansLoanIdTransactionsResponse undoWriteoffResultWithExternalId = loanTransactionHelper
.makeUndoWriteoff(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest());
assertEquals(transactionExternalIdStr, undoWriteoffResultWithExternalId.getResourceExternalId());
// Overpay the account
transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse overpaymentResultWithExternalId = loanTransactionHelper
.makeLoanRepayment(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("06 September 2022").locale("en").transactionAmount(5000.0).externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, overpaymentResultWithExternalId.getResourceExternalId());
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse makeCreditBalanceRefundResult = loanTransactionHelper
.makeCreditBalanceRefund(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("06 September 2022").locale("en").transactionAmount(5.0));
assertNotNull(makeCreditBalanceRefundResult.getResourceExternalId());
// Check whether the provided external id was retrieved
transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse makeCreditBalanceRefundResultWithExternalId = loanTransactionHelper
.makeCreditBalanceRefund(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate("06 September 2022").locale("en").transactionAmount(5.0).externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, makeCreditBalanceRefundResultWithExternalId.getResourceExternalId());
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse chargeRefundResult = loanTransactionHelper.makeChargeRefund(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").locale("en").loanChargeId(penalty1LoanChargeId)
.transactionAmount(1.0));
assertNotNull(chargeRefundResult.getResourceExternalId());
// Check whether the provided external id was retrieved
transactionExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse chargeRefundResultWithExternalId = loanTransactionHelper
.makeChargeRefund(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").locale("en")
.loanChargeId(penalty1LoanChargeId).transactionAmount(1.0).externalId(transactionExternalIdStr));
assertEquals(transactionExternalIdStr, chargeRefundResultWithExternalId.getResourceExternalId());
// Create a loan with interest and test the rest of the transactions
final String loanProductWithInterestJSON = new LoanProductTestBuilder().withPrincipal("10000.0").withRepaymentTypeAsMonth()
.withRepaymentAfterEvery("2").withNumberOfRepayments("5").withRepaymentTypeAsMonth().withinterestRatePerPeriod("1")
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat()
.withAccounting("1", null).build(null);
final Integer loanProductWithInterestID = this.loanTransactionHelper.getLoanProductId(loanProductWithInterestJSON);
LocalDate aMonthBefore = Utils.getLocalDateOfTenant().minusMonths(1);
String formattedDate = dateFormatter.format(aMonthBefore);
final Integer savingsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec,
client.getClientId().intValue(), "10000.0");
loanExternalIdStr = UUID.randomUUID().toString();
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("10000.0").withLoanTermFrequency("10")
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("5").withRepaymentEveryAfter("2")
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("1").withInterestTypeAsFlatBalance()
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
.withExpectedDisbursementDate(formattedDate).withSubmittedOnDate(formattedDate).withLoanType("individual")
.withExternalId(loanExternalIdStr)
.build(client.getClientId().toString(), loanProductWithInterestID.toString(), savingsId.toString());
final HashMap loanWithInterest = this.loanTransactionHelper.getLoanId(loanApplicationJSON, "");
Integer loanWithInterestId = (Integer) loanWithInterest.get("resourceId");
this.loanTransactionHelper.approveLoan(formattedDate, loanWithInterestId);
final HashMap disbursedLoanWithInterestResult = this.loanTransactionHelper.disburseLoan(formattedDate, loanWithInterestId, "1000",
null);
// Check whether an external id was generated
assertNotNull(disbursedLoanWithInterestResult.get("resourceExternalId"));
LocalDate aMonthBeforePlus3Days = aMonthBefore.plusDays(3);
formattedDate = dateFormatter.format(aMonthBeforePlus3Days);
Integer penalty3LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanWithInterestId,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), formattedDate, "10"));
Integer penalty4LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanWithInterestId,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty2), formattedDate, "1000"));
Integer penalty5LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanWithInterestId,
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty2), formattedDate, "1000"));
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse waiveInterestResult = loanTransactionHelper.makeWaiveInterest(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate(formattedDate).locale("en")
.transactionAmount(5.0));
assertNotNull(waiveInterestResult.getResourceExternalId());
// Check whether the provided external id was retrieved
String waiveInterestTxnExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse waiveInterestResultWithExternalId = loanTransactionHelper
.makeWaiveInterest(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate(formattedDate).locale("en").transactionAmount(1.0).externalId(waiveInterestTxnExternalIdStr));
assertEquals(waiveInterestTxnExternalIdStr, waiveInterestResultWithExternalId.getResourceExternalId());
String inAdvanceRepaymentTxnExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse inAdvanceRepaymentResult = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate(formattedDate).locale("en")
.transactionAmount(500.0).externalId(inAdvanceRepaymentTxnExternalIdStr));
String inAdvanceRepayment2TxnExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse inAdvanceRepayment2Result = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate(formattedDate).locale("en")
.transactionAmount(50.0).externalId(inAdvanceRepayment2TxnExternalIdStr));
// Check whether an external id was generated
final PostLoansLoanIdTransactionsResponse makeRefundByCashResult = loanTransactionHelper.makeRefundByCash(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate(formattedDate).locale("en")
.transactionAmount(1.0));
assertNotNull(makeRefundByCashResult.getResourceExternalId());
// Check whether the provided external id was retrieved
String makeRefundTxnExternalIdStr = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse makeRefundByCashResultWithExternalId = loanTransactionHelper
.makeRefundByCash(loanExternalIdStr, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
.transactionDate(formattedDate).locale("en").transactionAmount(5.0).externalId(makeRefundTxnExternalIdStr));
assertEquals(makeRefundTxnExternalIdStr, makeRefundByCashResultWithExternalId.getResourceExternalId());
PostLoansLoanIdTransactionsResponse adjustmentResult = loanTransactionHelper.reverseLoanTransaction((long) loanWithInterestId,
inAdvanceRepayment2TxnExternalIdStr, new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate(formattedDate)
.locale("en").dateFormat("dd MMMM yyyy").transactionAmount(0.0));
assertEquals(inAdvanceRepayment2TxnExternalIdStr, adjustmentResult.getResourceExternalId());
adjustmentResult = loanTransactionHelper.reverseLoanTransaction(loanExternalIdStr, inAdvanceRepaymentResult.getResourceId(),
new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate(formattedDate).locale("en").dateFormat("dd MMMM yyyy")
.transactionAmount(0.0));
assertEquals(inAdvanceRepaymentTxnExternalIdStr, adjustmentResult.getResourceExternalId());
String adjustTransactionExternalId = UUID.randomUUID().toString();
adjustmentResult = loanTransactionHelper.adjustLoanTransaction(loanExternalIdStr, waiveInterestTxnExternalIdStr,
new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate(formattedDate).locale("en").dateFormat("dd MMMM yyyy")
.transactionAmount(2.0).externalId(adjustTransactionExternalId));
assertEquals(adjustTransactionExternalId, adjustmentResult.getResourceExternalId());
adjustmentResult = loanTransactionHelper.adjustLoanTransaction(loanExternalIdStr, adjustmentResult.getResourceExternalId(),
new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate(formattedDate).locale("en").dateFormat("dd MMMM yyyy")
.transactionAmount(1.0));
assertNotNull(adjustmentResult.getResourceExternalId());
String repaymentForChargeback = UUID.randomUUID().toString();
final PostLoansLoanIdTransactionsResponse repaymentForChargebackResult = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate(formattedDate).locale("en")
.transactionAmount(5.0).externalId(repaymentForChargeback));
String chargebackTransactionExternalId = UUID.randomUUID().toString();
PostLoansLoanIdTransactionsResponse chargebackResult = loanTransactionHelper.chargebackLoanTransaction(loanExternalIdStr,
repaymentForChargebackResult.getResourceExternalId(), new PostLoansLoanIdTransactionsTransactionIdRequest().locale("en")
.transactionAmount(2.0).externalId(chargebackTransactionExternalId).paymentTypeId(1L));
assertEquals(chargebackTransactionExternalId, chargebackResult.getResourceExternalId());
chargebackResult = loanTransactionHelper.chargebackLoanTransaction(loanExternalIdStr,
repaymentForChargebackResult.getResourceExternalId(),
new PostLoansLoanIdTransactionsTransactionIdRequest().locale("en").transactionAmount(2.0).paymentTypeId(2L));
assertNotNull(chargebackResult.getResourceExternalId());
// Check whether the provided external id was retrieved
String chargeAdjustmentExternalIdStr = UUID.randomUUID().toString();
PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResult = loanTransactionHelper.chargeAdjustment((long) loanWithInterestId,
(long) penalty3LoanChargeId,
new PostLoansLoanIdChargesChargeIdRequest().externalId(chargeAdjustmentExternalIdStr).amount(1.0).locale("en"));
assertEquals(chargeAdjustmentExternalIdStr, chargeAdjustmentResult.getSubResourceExternalId());
// Check whether an external id was generated
chargeAdjustmentResult = loanTransactionHelper.chargeAdjustment((long) loanWithInterestId, (long) penalty3LoanChargeId,
new PostLoansLoanIdChargesChargeIdRequest().amount(1.0).locale("en"));
assertNotNull(chargeAdjustmentResult.getSubResourceExternalId());
HashMap payChargeResult = this.loanTransactionHelper.payChargesForLoan(loanWithInterestId, penalty4LoanChargeId,
LoanTransactionHelper.getPayChargeJSON(formattedDate, null), "");
assertNotNull(payChargeResult.get("subResourceExternalId"));
String payChargeExternalIdStr = UUID.randomUUID().toString();
payChargeResult = this.loanTransactionHelper.payChargesForLoan(loanWithInterestId, penalty5LoanChargeId,
LoanTransactionHelper.getPayChargeJSON(formattedDate, null, payChargeExternalIdStr), "");
assertEquals(payChargeExternalIdStr, payChargeResult.get("subResourceExternalId"));
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 50, false);
}
private HashMap applyForLoanApplication(final Integer clientID, final Integer loanProductID, final String externalId) {
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
.withExpectedDisbursementDate("03 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual")
.withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null);
return (HashMap) this.loanTransactionHelper.createLoanAccount(loanApplicationJSON, "");
}
}
@@ -75,7 +75,7 @@ public class LoanAuditingIntegrationTest {
public void checkAuditDates() throws InterruptedException { public void checkAuditDates() throws InterruptedException {
final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec); final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
String username = Utils.randomNameGenerator("user", 8); String username = Utils.randomNameGenerator("user", 8);
final Integer userId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, 1, staffId, username, "password", final Integer userId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, 1, staffId, username, "P4ssw0rd",
"resourceId"); "resourceId");
LOG.info("-------------------------Creating Client---------------------------"); LOG.info("-------------------------Creating Client---------------------------");
@@ -129,7 +129,7 @@ public class LoanAuditingIntegrationTest {
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", this.requestSpec.header("Authorization",
"Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(username, "password")); "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(username, "P4ssw0rd"));
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
loanStatusHashMap = this.loanTransactionHelper.approveLoan("11 July 2022", loanID); loanStatusHashMap = this.loanTransactionHelper.approveLoan("11 July 2022", loanID);
@@ -74,7 +74,7 @@ public class LoanTransactionAuditingIntegrationTest {
public void checkAuditDates() throws InterruptedException { public void checkAuditDates() throws InterruptedException {
final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec); final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
String username = Utils.randomNameGenerator("user", 8); String username = Utils.randomNameGenerator("user", 8);
final Integer userId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, 1, staffId, username, "password", final Integer userId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, 1, staffId, username, "P4ssw0rd",
"resourceId"); "resourceId");
LOG.info("-------------------------Creating Client---------------------------"); LOG.info("-------------------------Creating Client---------------------------");
@@ -138,7 +138,7 @@ public class LoanTransactionAuditingIntegrationTest {
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", this.requestSpec.header("Authorization",
"Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(username, "password")); "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(username, "P4ssw0rd"));
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
this.loanTransactionHelper.reverseRepayment(loanID, transactionId, "11 July 2022"); this.loanTransactionHelper.reverseRepayment(loanID, transactionId, "11 July 2022");
@@ -98,9 +98,9 @@ public class GlobalConfigurationHelper {
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations(); ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec); ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec);
// There are currently 37 global configurations. // There are currently 45 global configurations.
Assertions.assertEquals(44, expectedGlobalConfigurations.size()); Assertions.assertEquals(45, expectedGlobalConfigurations.size());
Assertions.assertEquals(44, actualGlobalConfigurations.size()); Assertions.assertEquals(45, actualGlobalConfigurations.size());
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) { for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
@@ -488,6 +488,14 @@ public class GlobalConfigurationHelper {
loanRepaymentOverdueDaysDefault.put("trapDoor", false); loanRepaymentOverdueDaysDefault.put("trapDoor", false);
defaults.add(loanRepaymentOverdueDaysDefault); defaults.add(loanRepaymentOverdueDaysDefault);
HashMap<String, Object> isAutomaticExternalIdGenerationEnabled = new HashMap<>();
isAutomaticExternalIdGenerationEnabled.put("id", 50);
isAutomaticExternalIdGenerationEnabled.put("name", "enable-auto-generated-external-id");
isAutomaticExternalIdGenerationEnabled.put("value", 0);
isAutomaticExternalIdGenerationEnabled.put("enabled", false);
isAutomaticExternalIdGenerationEnabled.put("trapDoor", false);
defaults.add(isAutomaticExternalIdGenerationEnabled);
return defaults; return defaults;
} }
@@ -58,7 +58,6 @@ import org.apache.fineract.client.models.PostLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.PutLoansLoanIdResponse; import org.apache.fineract.client.models.PutLoansLoanIdResponse;
import org.apache.fineract.client.util.JSON; import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.client.IntegrationTest; import org.apache.fineract.integrationtests.client.IntegrationTest;
@@ -116,8 +115,17 @@ public class LoanTransactionHelper extends IntegrationTest {
return this.getLoanId(loanApplicationJSON, this.requestSpec, this.responseSpec); return this.getLoanId(loanApplicationJSON, this.requestSpec, this.responseSpec);
} }
public HashMap getLoanId(final String loanApplicationJSON, final String responseAttribute) {
return (HashMap) this.getLoanId(loanApplicationJSON, responseAttribute, this.requestSpec, this.responseSpec);
}
public Object getLoanId(final String loanApplicationJSON, final String responseAttribute, RequestSpecification requestSpec,
ResponseSpecification responseSpec) {
return Utils.performServerPost(requestSpec, responseSpec, APPLY_LOAN_URL, loanApplicationJSON, responseAttribute);
}
public Integer getLoanId(final String loanApplicationJSON, RequestSpecification requestSpec, ResponseSpecification responseSpec) { public Integer getLoanId(final String loanApplicationJSON, RequestSpecification requestSpec, ResponseSpecification responseSpec) {
return Utils.performServerPost(requestSpec, responseSpec, APPLY_LOAN_URL, loanApplicationJSON, "loanId"); return (Integer) getLoanId(loanApplicationJSON, "loanId", requestSpec, responseSpec);
} }
public HashMap<String, Integer> getGlimId(final String loanApplicationJSON) { public HashMap<String, Integer> getGlimId(final String loanApplicationJSON) {
@@ -258,12 +266,12 @@ public class LoanTransactionHelper extends IntegrationTest {
return Utils.performServerGet(requestSpec, responseSpec, GET_REPAYMENTS_URL, "loanRepaymentScheduleInstallments"); return Utils.performServerGet(requestSpec, responseSpec, GET_REPAYMENTS_URL, "loanRepaymentScheduleInstallments");
} }
public PostLoansLoanIdTransactionsTransactionIdResponse applyLoanTransactionCommand(final Integer loanId, final Integer transactionId, public PostLoansLoanIdTransactionsResponse applyLoanTransactionCommand(final Integer loanId, final Integer transactionId,
final String command, final String payload, final ResponseSpecification responseSpec) { final String command, final String payload, final ResponseSpecification responseSpec) {
final String LOAN_TRANSACTION_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/" + transactionId + "?command=" final String LOAN_TRANSACTION_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/" + transactionId + "?command="
+ command + "&" + Utils.TENANT_IDENTIFIER; + command + "&" + Utils.TENANT_IDENTIFIER;
final String response = Utils.performServerPost(requestSpec, responseSpec, LOAN_TRANSACTION_URL, payload, null); final String response = Utils.performServerPost(requestSpec, responseSpec, LOAN_TRANSACTION_URL, payload, null);
return GSON.fromJson(response, PostLoansLoanIdTransactionsTransactionIdResponse.class); return GSON.fromJson(response, PostLoansLoanIdTransactionsResponse.class);
} }
public HashMap approveLoan(final String approvalDate, final Integer loanID) { public HashMap approveLoan(final String approvalDate, final Integer loanID) {
@@ -305,6 +313,11 @@ public class LoanTransactionHelper extends IntegrationTest {
getDisburseLoanAsJSON(date, null, netDisbursalAmount)); getDisburseLoanAsJSON(date, null, netDisbursalAmount));
} }
public HashMap disburseLoan(final String date, final Integer loanID, final String netDisbursalAmount, final String externalId) {
return (HashMap) performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID),
getDisburseLoanAsJSON(date, null, netDisbursalAmount, externalId), "");
}
public HashMap disburseLoanWithTransactionAmount(final String date, final Integer loanID, final String transactionAmount) { public HashMap disburseLoanWithTransactionAmount(final String date, final Integer loanID, final String transactionAmount) {
return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID), return performLoanTransaction(createLoanOperationURL(DISBURSE_LOAN_COMMAND, loanID),
getDisburseLoanAsJSON(date, transactionAmount, null)); getDisburseLoanAsJSON(date, transactionAmount, null));
@@ -469,12 +482,179 @@ public class LoanTransactionHelper extends IntegrationTest {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "repayment")); return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "repayment"));
} }
public HashMap undoWaiveLoanCharge(final Long loanId, final String transactionExternalId) {
log.info("--------------------------------- UNDO WAIVE CHARGES FOR LOAN --------------------------------");
final String TRANSAC_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/external-id/" + transactionExternalId + "?"
+ Utils.TENANT_IDENTIFIER;
return Utils.performServerPut(requestSpec, responseSpec, TRANSAC_URL, "", "");
}
public HashMap undoWaiveLoanCharge(final String loanExternalId, final Long transactionId) {
log.info("--------------------------------- UNDO WAIVE CHARGES FOR LOAN --------------------------------");
final String TRANSAC_URL = "/fineract-provider/api/v1/loans/external-id/" + loanExternalId + "/transactions/" + transactionId + "?"
+ Utils.TENANT_IDENTIFIER;
return Utils.performServerPut(requestSpec, responseSpec, TRANSAC_URL, "", "");
}
public HashMap undoWaiveLoanCharge(final String loanExternalId, final String transactionExternalId) {
log.info("--------------------------------- UNDO WAIVE CHARGES FOR LOAN --------------------------------");
final String TRANSAC_URL = "/fineract-provider/api/v1/loans/external-id/" + loanExternalId + "/transactions/external-id/"
+ transactionExternalId + "?" + Utils.TENANT_IDENTIFIER;
return Utils.performServerPut(requestSpec, responseSpec, TRANSAC_URL, "", "");
}
public PostLoansLoanIdChargesChargeIdResponse waiveLoanCharge(final Long loanId, final Long chargeId,
final PostLoansLoanIdChargesChargeIdRequest request) {
return ok(fineract().loanCharges.executeLoanCharge1(loanId, chargeId, request, "waive"));
}
public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "repayment"));
}
public PostLoansLoanIdTransactionsResponse makeMerchantIssuedRefund(final Long loanId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "merchantIssuedRefund"));
}
public PostLoansLoanIdTransactionsResponse makeMerchantIssuedRefund(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "merchantIssuedRefund"));
}
public PostLoansLoanIdTransactionsResponse makePayoutRefund(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "payoutRefund"));
}
public PostLoansLoanIdTransactionsResponse makePayoutRefund(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "payoutRefund"));
}
public PostLoansLoanIdTransactionsResponse makeChargeRefund(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "chargeRefund"));
}
public PostLoansLoanIdTransactionsResponse makeChargeRefund(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "chargeRefund"));
}
public PostLoansLoanIdTransactionsResponse makeGoodwillCredit(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "goodwillCredit"));
}
public PostLoansLoanIdTransactionsResponse makeGoodwillCredit(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "goodwillCredit"));
}
public PostLoansLoanIdTransactionsResponse makeWaiveInterest(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "waiveinterest"));
}
public PostLoansLoanIdTransactionsResponse makeWaiveInterest(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "waiveinterest"));
}
public PostLoansLoanIdTransactionsResponse makeWriteoff(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "writeoff"));
}
public PostLoansLoanIdTransactionsResponse makeWriteoff(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "writeoff"));
}
public PostLoansLoanIdTransactionsResponse makeUndoWriteoff(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "undowriteoff"));
}
public PostLoansLoanIdTransactionsResponse makeUndoWriteoff(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "undowriteoff"));
}
public PostLoansLoanIdTransactionsResponse makeRecoveryPayment(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "recoverypayment"));
}
public PostLoansLoanIdTransactionsResponse makeRecoveryPayment(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "recoverypayment"));
}
public PostLoansLoanIdTransactionsResponse makeRefundByCash(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "refundByCash"));
}
public PostLoansLoanIdTransactionsResponse makeRefundByCash(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "refundByCash"));
}
public PostLoansLoanIdTransactionsResponse makeCreditBalanceRefund(final Long loanId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "creditBalanceRefund"));
}
public PostLoansLoanIdTransactionsResponse makeCreditBalanceRefund(final String loanExternalId,
final PostLoansLoanIdTransactionsRequest request) {
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "creditBalanceRefund"));
}
public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final String loanExternalId, final Long transactionId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, request, "undo"));
}
public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final String loanExternalId, final String transactionExternalId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction3(loanExternalId, transactionExternalId, request, "undo"));
}
public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Long loanId, final String transactionExternalId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, request, "undo"));
}
public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final String loanExternalId, final Long transactionId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, request, "chargeback"));
}
public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final String loanExternalId, final String transactionExternalId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction3(loanExternalId, transactionExternalId, request, "chargeback"));
}
public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final Long loanId, final String transactionExternalId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, request, "chargeback"));
}
public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final String loanExternalId, final Long transactionId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, request, "adjust"));
}
public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final String loanExternalId, final String transactionExternalId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction3(loanExternalId, transactionExternalId, request, "adjust"));
}
public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final Long loanId, final String transactionExternalId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, request, "adjust"));
}
public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Integer loanId, final Long transactionId, String date, public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Integer loanId, final Long transactionId, String date,
ResponseSpecification responseSpec) { ResponseSpecification responseSpec) {
return postLoanTransaction(createLoanTransactionURL(UNDO, loanId, transactionId.intValue()), getUndoJsonBody(date), responseSpec); return postLoanTransaction(createLoanTransactionURL(UNDO, loanId, transactionId.intValue()), getUndoJsonBody(date), responseSpec);
} }
public PostLoansLoanIdTransactionsTransactionIdResponse reverseLoanTransaction(final Long loanId, final Long transactionId, public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Long loanId, final Long transactionId,
final PostLoansLoanIdTransactionsTransactionIdRequest request) { final PostLoansLoanIdTransactionsTransactionIdRequest request) {
return ok(fineract().loanTransactions.adjustLoanTransaction(loanId, transactionId, request, "undo")); return ok(fineract().loanTransactions.adjustLoanTransaction(loanId, transactionId, request, "undo"));
} }
@@ -574,6 +754,13 @@ public class LoanTransactionHelper extends IntegrationTest {
return (Integer) response.get("resourceId"); return (Integer) response.get("resourceId");
} }
public HashMap payChargesForLoan(final Integer loanId, final Integer loanchargeId, final String json, final String responseAttribute) {
log.info("--------------------------------- WAIVE CHARGES FOR LOAN --------------------------------");
final String CHARGES_URL = "/fineract-provider/api/v1/loans/" + loanId + "/charges/" + loanchargeId + "?command=pay&"
+ Utils.TENANT_IDENTIFIER;
return Utils.performServerPost(requestSpec, responseSpec, CHARGES_URL, json, responseAttribute);
}
public ArrayList<HashMap> getLoanTransactionDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, public ArrayList<HashMap> getLoanTransactionDetails(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
final Integer loanID) { final Integer loanID) {
final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=all&exclude=guarantors,futureSchedule&" final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=all&exclude=guarantors,futureSchedule&"
@@ -616,6 +803,11 @@ public class LoanTransactionHelper extends IntegrationTest {
private String getDisburseLoanAsJSON(final String actualDisbursementDate, final String transactionAmount, private String getDisburseLoanAsJSON(final String actualDisbursementDate, final String transactionAmount,
final String netDisbursalAmount) { final String netDisbursalAmount) {
return getDisburseLoanAsJSON(actualDisbursementDate, transactionAmount, netDisbursalAmount, null);
}
private String getDisburseLoanAsJSON(final String actualDisbursementDate, final String transactionAmount,
final String netDisbursalAmount, final String externalId) {
final HashMap<String, String> map = new HashMap<>(); final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en"); map.put("locale", "en");
map.put("dateFormat", "dd MMMM yyyy"); map.put("dateFormat", "dd MMMM yyyy");
@@ -627,6 +819,9 @@ public class LoanTransactionHelper extends IntegrationTest {
if (transactionAmount != null) { if (transactionAmount != null) {
map.put("transactionAmount", transactionAmount); map.put("transactionAmount", transactionAmount);
} }
if (externalId != null) {
map.put("externalId", externalId);
}
log.info("Loan Application disburse request : {} ", map); log.info("Loan Application disburse request : {} ", map);
return new Gson().toJson(map); return new Gson().toJson(map);
} }
@@ -842,6 +1037,10 @@ public class LoanTransactionHelper extends IntegrationTest {
} }
public static String getPayChargeJSON(final String date, final String installmentNumber) { public static String getPayChargeJSON(final String date, final String installmentNumber) {
return getPayChargeJSON(date, installmentNumber, null);
}
public static String getPayChargeJSON(final String date, final String installmentNumber, final String externalId) {
final HashMap<String, String> map = new HashMap<>(); final HashMap<String, String> map = new HashMap<>();
map.put("locale", "en_GB"); map.put("locale", "en_GB");
map.put("dateFormat", "dd MMMM yyyy"); map.put("dateFormat", "dd MMMM yyyy");
@@ -849,6 +1048,9 @@ public class LoanTransactionHelper extends IntegrationTest {
if (installmentNumber != null) { if (installmentNumber != null) {
map.put("installmentNumber", installmentNumber); map.put("installmentNumber", installmentNumber);
} }
if (externalId != null) {
map.put("externalId", externalId);
}
String json = new Gson().toJson(map); String json = new Gson().toJson(map);
log.info("{}", json); log.info("{}", json);
return json; return json;
@@ -1236,7 +1438,7 @@ public class LoanTransactionHelper extends IntegrationTest {
final String payload = createChargebackPayload(amount, paymentTypeList.get(paymentTypeIdx).getId()); final String payload = createChargebackPayload(amount, paymentTypeList.get(paymentTypeIdx).getId());
log.info("Loan Chargeback: {}", payload); log.info("Loan Chargeback: {}", payload);
PostLoansLoanIdTransactionsTransactionIdResponse postLoansTransactionCommandResponse = applyLoanTransactionCommand(loanId, PostLoansLoanIdTransactionsResponse postLoansTransactionCommandResponse = applyLoanTransactionCommand(loanId,
transactionId.intValue(), "chargeback", payload, responseSpec); transactionId.intValue(), "chargeback", payload, responseSpec);
assertNotNull(postLoansTransactionCommandResponse); assertNotNull(postLoansTransactionCommandResponse);