public Map updateFrom()

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;
    }