in fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssemblerImpl.java [441:864]
public Map<String, Object> updateFrom(JsonCommand command, Loan loan) {
final Map<String, Object> changes = new HashMap<>();
LoanProduct loanProduct;
final String productIdParamName = "productId";
final Long productId = command.longValueOfParameterNamed(productIdParamName);
if (productId == null || productId.equals(loan.getLoanProduct().getId())) {
loanProduct = loan.getLoanProduct();
} else {
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
}
final Set<LoanCharge> existingCharges = loan.getActiveCharges();
Map<Long, LoanChargeData> chargesMap = new HashMap<>();
for (LoanCharge charge : existingCharges) {
LoanChargeData chargeData = new LoanChargeData(charge.getId(), charge.getDueLocalDate(), charge.amountOrPercentage());
chargesMap.put(charge.getId(), chargeData);
}
List<LoanDisbursementDetails> disbursementDetails = this.loanDisbursementDetailsAssembler
.fetchDisbursementData(command.parsedJson().getAsJsonObject());
/**
* Stores all charges which are passed in during modify loan application
**/
final Set<LoanCharge> possiblyModifiedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),
disbursementDetails);
/** Boolean determines if any charge has been modified **/
boolean isChargeModified = false;
Set<Charge> newTrancheCharges = this.loanChargeAssembler.getNewLoanTrancheCharges(command.parsedJson());
for (Charge charge : newTrancheCharges) {
loan.addTrancheLoanCharge(charge);
}
/**
* If there are any charges already present, which are now not passed in as a part of the request, deem the
* charges as modified
**/
if (!possiblyModifiedLoanCharges.isEmpty()) {
if (!possiblyModifiedLoanCharges.containsAll(existingCharges)) {
isChargeModified = true;
}
}
/**
* If any new charges are added or values of existing charges are modified
**/
for (LoanCharge loanCharge : possiblyModifiedLoanCharges) {
if (loanCharge.getId() == null) {
isChargeModified = true;
} else {
LoanChargeData chargeData = chargesMap.get(loanCharge.getId());
if (loanCharge.amountOrPercentage().compareTo(chargeData.getAmountOrPercentage()) != 0
|| (loanCharge.isSpecifiedDueDate() && !loanCharge.getDueLocalDate().equals(chargeData.getDueDate()))) {
isChargeModified = true;
}
}
}
Set<LoanCollateralManagement> possiblyModifedLoanCollateralItems = null;
if (command.parameterExists("loanType")) {
final String loanTypeStr = command.stringValueOfParameterNamed("loanType");
final AccountType loanType = AccountType.fromName(loanTypeStr);
if (!StringUtils.isBlank(loanTypeStr) && loanType.isIndividualAccount()) {
possiblyModifedLoanCollateralItems = this.loanCollateralAssembler.fromParsedJson(command.parsedJson());
}
}
this.loanScheduleAssembler.updateLoanApplicationAttributes(command, loan, changes);
if (!changes.isEmpty()) {
final boolean recalculateLoanSchedule = !(changes.size() == 1
&& changes.containsKey(LoanApiConstants.inArrearsToleranceParameterName));
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, recalculateLoanSchedule);
isChargeModified = true;
}
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
if (command.isChangeInStringParameterNamed(LoanApiConstants.accountNoParameterName, loan.getAccountNumber())) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.accountNoParameterName);
changes.put(LoanApiConstants.accountNoParameterName, newValue);
loan.setAccountNumber(StringUtils.defaultIfEmpty(newValue, null));
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.createStandingInstructionAtDisbursementParameterName,
loan.shouldCreateStandingInstructionAtDisbursement())) {
final Boolean valueAsInput = command
.booleanObjectValueOfParameterNamed(LoanApiConstants.createStandingInstructionAtDisbursementParameterName);
changes.put(LoanApiConstants.createStandingInstructionAtDisbursementParameterName, valueAsInput);
loan.setCreateStandingInstructionAtDisbursement(valueAsInput);
}
if (command.isChangeInStringParameterNamed(LoanApiConstants.externalIdParameterName, loan.getExternalId().getValue())) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.externalIdParameterName);
ExternalId externalId = ExternalIdFactory.produce(newValue);
if (externalId.isEmpty() && TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
changes.put(LoanApiConstants.externalIdParameterName, externalId);
loan.setExternalId(externalId);
}
// add clientId, groupId and loanType changes to actual changes
final Long clientId = loan.getClient() == null ? null : loan.getClient().getId();
if (command.isChangeInLongParameterNamed(LoanApiConstants.clientIdParameterName, clientId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.clientIdParameterName);
changes.put(LoanApiConstants.clientIdParameterName, newValue);
final Client client = this.clientRepository.findOneWithNotFoundDetection(newValue);
loan.updateClient(client);
}
// FIXME: AA - We may require separate api command to move loan from one
// group to another
final Long groupId = loan.getGroup() == null ? null : loan.getGroup().getId();
if (command.isChangeInLongParameterNamed(LoanApiConstants.groupIdParameterName, groupId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.groupIdParameterName);
changes.put(LoanApiConstants.groupIdParameterName, newValue);
final Group group = this.groupRepository.findOneWithNotFoundDetection(newValue);
loan.updateGroup(group);
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.productIdParameterName, loan.getLoanProduct().getId())) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.productIdParameterName);
changes.put(LoanApiConstants.productIdParameterName, newValue);
loan.updateLoanProduct(loanProduct);
final MonetaryCurrency currency = new MonetaryCurrency(loanProduct.getCurrency().getCode(),
loanProduct.getCurrency().getDigitsAfterDecimal(), loanProduct.getCurrency().getCurrencyInMultiplesOf());
loan.getLoanRepaymentScheduleDetail().setCurrency(currency);
if (!changes.containsKey(LoanApiConstants.interestRateFrequencyTypeParameterName)) {
loan.updateInterestRateFrequencyType();
}
if (loanProduct.isLinkedToFloatingInterestRate()) {
loan.getLoanProductRelatedDetail().updateForFloatingInterestRates();
} else {
loan.setInterestRateDifferential(null);
loan.setIsFloatingInterestRate(null);
}
loan.updateIsInterestRecalculationEnabled();
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.isFloatingInterestRateParameterName,
loan.getIsFloatingInterestRate())) {
final Boolean newValue = command.booleanObjectValueOfParameterNamed(LoanApiConstants.isFloatingInterestRateParameterName);
changes.put(LoanApiConstants.isFloatingInterestRateParameterName, newValue);
loan.setIsFloatingInterestRate(newValue);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.interestRateDifferentialParameterName,
loan.getInterestRateDifferential())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanApiConstants.interestRateDifferentialParameterName);
changes.put(LoanApiConstants.interestRateDifferentialParameterName, newValue);
loan.setInterestRateDifferential(newValue);
}
Long existingFundId = null;
if (loan.getFund() != null) {
existingFundId = loan.getFund().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.fundIdParameterName, existingFundId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.fundIdParameterName);
changes.put(LoanApiConstants.fundIdParameterName, newValue);
final Fund fund = findFundByIdIfProvided(newValue);
loan.updateFund(fund);
}
Long existingLoanOfficerId = null;
if (loan.getLoanOfficer() != null) {
existingLoanOfficerId = loan.getLoanOfficer().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.loanOfficerIdParameterName, existingLoanOfficerId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.loanOfficerIdParameterName);
changes.put(LoanApiConstants.loanOfficerIdParameterName, newValue);
final Staff newOfficer = findLoanOfficerByIdIfProvided(newValue);
loanOfficerService.updateLoanOfficerOnLoanApplication(loan, newOfficer);
}
Long existingLoanPurposeId = null;
if (loan.getLoanPurpose() != null) {
existingLoanPurposeId = loan.getLoanPurpose().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.loanPurposeIdParameterName, existingLoanPurposeId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.loanPurposeIdParameterName);
changes.put(LoanApiConstants.loanPurposeIdParameterName, newValue);
final CodeValue loanPurpose = findCodeValueByIdIfProvided(newValue);
loan.updateLoanPurpose(loanPurpose);
}
if (command.isChangeInStringParameterNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName,
loan.getTransactionProcessingStrategyCode())
&& loanProduct.getLoanConfigurableAttributes().getTransactionProcessingStrategyBoolean()) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName);
final String transactionProcessingStrategyCode = command.stringValueOfParameterNamed("transactionProcessingStrategyCode");
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode);
changes.put(LoanApiConstants.transactionProcessingStrategyCodeParameterName, newValue);
loan.updateTransactionProcessingStrategy(transactionProcessingStrategyCode,
loanRepaymentScheduleTransactionProcessor.getName());
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.submittedOnDateParameterName, loan.getSubmittedOnDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.submittedOnDateParameterName);
changes.put(LoanApiConstants.submittedOnDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
loan.setSubmittedOnDate(command.localDateValueOfParameterNamed(LoanApiConstants.submittedOnDateParameterName));
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName,
loan.getExpectedDisbursedOnLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName);
changes.put(LoanApiConstants.expectedDisbursementDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setExpectedDisbursementDate(
command.localDateValueOfParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName));
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName,
loan.getExpectedFirstRepaymentOnDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName);
changes.put(LoanApiConstants.repaymentsStartingFromDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setExpectedFirstRepaymentOnDate(
command.localDateValueOfParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName));
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName,
loan.isSyncDisbursementWithMeeting())) {
final Boolean valueAsInput = command
.booleanObjectValueOfParameterNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName);
changes.put(LoanApiConstants.syncDisbursementWithMeetingParameterName, valueAsInput);
loan.setSyncDisbursementWithMeeting(valueAsInput);
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.interestChargedFromDateParameterName,
loan.getInterestChargedFromDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.interestChargedFromDateParameterName);
changes.put(LoanApiConstants.interestChargedFromDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setInterestChargedFromDate(command.localDateValueOfParameterNamed(LoanApiConstants.interestChargedFromDateParameterName));
}
if (isChargeModified) {
changes.put(LoanApiConstants.chargesParameterName, loanChargeMapper.map(possiblyModifiedLoanCharges, loan.getCurrency()));
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
if (command.parameterExists(LoanApiConstants.collateralParameterName) && possiblyModifedLoanCollateralItems != null
&& possiblyModifedLoanCollateralItems.equals(loan.getLoanCollateralManagements())) {
changes.put(LoanApiConstants.collateralParameterName, loanCollateralManagementMapper.map(possiblyModifedLoanCollateralItems));
}
if (command.isChangeInIntegerParameterNamed(LoanApiConstants.loanTermFrequencyParameterName, loan.getTermFrequency())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanApiConstants.loanTermFrequencyParameterName);
changes.put(LoanApiConstants.loanTermFrequencyParameterName, newValue);
loan.setTermFrequency(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanApiConstants.loanTermFrequencyTypeParameterName,
loan.getTermPeriodFrequencyType().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanApiConstants.loanTermFrequencyTypeParameterName);
changes.put(LoanApiConstants.loanTermFrequencyTypeParameterName, newValue);
loan.setTermPeriodFrequencyType(PeriodFrequencyType.fromInt(newValue));
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.principalParameterName, loan.getApprovedPrincipal())) {
loan.setApprovedPrincipal(command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalParameterName));
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.principalParameterName, loan.getProposedPrincipal())) {
BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalParameterName);
changes.put(LoanApiConstants.principalParameterName, newValue);
loan.setProposedPrincipal(newValue);
}
if (loanProduct.isMultiDisburseLoan()) {
loanDisbursementService.updateDisbursementDetails(loan, command, changes);
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
loan.getMaxOutstandingLoanBalance())) {
loan.setMaxOutstandingLoanBalance(
command.bigDecimalValueOfParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName));
}
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if (loanProduct.isDisallowExpectedDisbursements()) {
if (disbursementDataArray != null && !disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details are not allowed";
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
} else {
if (disbursementDataArray == null || disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size());
}
}
} else {
loan.clearDisbursementDetails();
}
if (loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) {
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.fixedEmiAmountParameterName, loan.getFixedEmiAmount())) {
loan.setFixedEmiAmount(command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedEmiAmountParameterName));
changes.put(LoanApiConstants.fixedEmiAmountParameterName, loan.getFixedEmiAmount());
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
} else {
loan.setFixedEmiAmount(null);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
loan.getFixedPrincipalPercentagePerInstallment())) {
loan.setFixedPrincipalPercentagePerInstallment(
command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName));
changes.put(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, loan.getFixedPrincipalPercentagePerInstallment());
}
final LoanProductRelatedDetail productRelatedDetail = loan.repaymentScheduleDetail();
if (loan.loanProduct().getLoanConfigurableAttributes() != null) {
loanScheduleAssembler.updateProductRelatedDetails(productRelatedDetail, loan);
}
if (loan.getLoanProduct().isCanUseForTopup() && loan.getClientId() != null) {
final Boolean isTopup = command.booleanObjectValueOfParameterNamed(LoanApiConstants.isTopup);
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.isTopup, loan.isTopup())) {
loan.setIsTopup(isTopup);
changes.put(LoanApiConstants.isTopup, isTopup);
}
if (loan.isTopup()) {
final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose);
LoanTopupDetails existingLoanTopupDetails = loan.getTopupLoanDetails();
if (existingLoanTopupDetails == null || !existingLoanTopupDetails.getLoanIdToClose().equals(loanIdToClose)
|| changes.containsKey("submittedOnDate") || changes.containsKey("expectedDisbursementDate")
|| changes.containsKey("principal") || changes.containsKey(LoanApiConstants.disbursementDataParameterName)) {
Long existingLoanIdToClose = null;
if (existingLoanTopupDetails != null) {
existingLoanIdToClose = existingLoanTopupDetails.getLoanIdToClose();
}
if (!loanIdToClose.equals(existingLoanIdToClose)) {
final LoanTopupDetails topupDetails = new LoanTopupDetails(loan, loanIdToClose);
loan.setTopupLoanDetails(topupDetails);
changes.put(LoanApiConstants.loanIdToClose, loanIdToClose);
}
}
} else {
loan.setTopupLoanDetails(null);
}
} else {
if (loan.isTopup()) {
loan.setIsTopup(false);
loan.setTopupLoanDetails(null);
changes.put(LoanApiConstants.isTopup, false);
}
}
/**
* TODO: Allow other loan types if needed.
*/
if (command.parameterExists("loanType")) {
final String loanTypeStr = command.stringValueOfParameterNamed("loanType");
final AccountType loanType = AccountType.fromName(loanTypeStr);
if (!StringUtils.isBlank(loanTypeStr) && loanType.isIndividualAccount()) {
final String collateralParamName = "collateral";
if (changes.containsKey(collateralParamName)) {
loan.updateLoanCollateral(possiblyModifedLoanCollateralItems);
}
}
}
final String chargesParamName = "charges";
if (changes.containsKey(chargesParamName)) {
loan.updateLoanCharges(possiblyModifiedLoanCharges);
}
// update installment level delinquency
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY,
loan.isEnableInstallmentLevelDelinquency())) {
final Boolean enableInstallmentLevelDelinquency = command
.booleanObjectValueOfParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY);
loan.updateEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency);
}
if (changes.containsKey("recalculateLoanSchedule")) {
changes.remove("recalculateLoanSchedule");
final JsonElement parsedQuery = this.fromApiJsonHelper.parse(command.json());
final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromApiJsonHelper);
final LoanScheduleModel loanScheduleModel = this.calculationPlatformService.calculateLoanSchedule(query, false);
loanSchedule.updateLoanSchedule(loan, loanScheduleModel);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
loanChargeService.recalculateAllCharges(loan);
}
// Changes to modify loan rates.
if (command.hasParameter(LoanProductConstants.RATES_PARAM_NAME)) {
loan.updateLoanRates(rateAssembler.fromParsedJson(command.parsedJson()));
}
return changes;
}