Example usage for org.joda.time LocalDate isBefore

List of usage examples for org.joda.time LocalDate isBefore

Introduction

In this page you can find the example usage for org.joda.time LocalDate isBefore.

Prototype

public boolean isBefore(ReadablePartial partial) 

Source Link

Document

Is this partial earlier than the specified partial.

Usage

From source file:com.gst.portfolio.account.service.StandingInstructionWritePlatformServiceImpl.java

License:Apache License

@Override
@CronTarget(jobName = JobName.EXECUTE_STANDING_INSTRUCTIONS)
public void executeStandingInstructions() throws JobExecutionException {
    Collection<StandingInstructionData> instructionDatas = this.standingInstructionReadPlatformService
            .retrieveAll(StandingInstructionStatus.ACTIVE.getValue());
    final StringBuilder sb = new StringBuilder();
    for (StandingInstructionData data : instructionDatas) {
        boolean isDueForTransfer = false;
        AccountTransferRecurrenceType recurrenceType = data.recurrenceType();
        StandingInstructionType instructionType = data.instructionType();
        LocalDate transactionDate = new LocalDate();
        if (recurrenceType.isPeriodicRecurrence()) {
            final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
            PeriodFrequencyType frequencyType = data.recurrenceFrequency();
            LocalDate startDate = data.validFrom();
            if (frequencyType.isMonthly()) {
                startDate = startDate.withDayOfMonth(data.recurrenceOnDay());
                if (startDate.isBefore(data.validFrom())) {
                    startDate = startDate.plusMonths(1);
                }/*from   w  w w  .j  a v a  2s .  c  o m*/
            } else if (frequencyType.isYearly()) {
                startDate = startDate.withDayOfMonth(data.recurrenceOnDay())
                        .withMonthOfYear(data.recurrenceOnMonth());
                if (startDate.isBefore(data.validFrom())) {
                    startDate = startDate.plusYears(1);
                }
            }
            isDueForTransfer = scheduledDateGenerator.isDateFallsInSchedule(frequencyType,
                    data.recurrenceInterval(), startDate, transactionDate);

        }
        BigDecimal transactionAmount = data.amount();
        if (data.toAccountType().isLoanAccount() && (recurrenceType.isDuesRecurrence()
                || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) {
            StandingInstructionDuesData standingInstructionDuesData = this.standingInstructionReadPlatformService
                    .retriveLoanDuesData(data.toAccount().accountId());
            if (data.instructionType().isDuesAmoutTransfer()) {
                transactionAmount = standingInstructionDuesData.totalDueAmount();
            }
            if (recurrenceType.isDuesRecurrence()) {
                isDueForTransfer = new LocalDate().equals(standingInstructionDuesData.dueDate());
            }
        }

        if (isDueForTransfer && transactionAmount != null && transactionAmount.compareTo(BigDecimal.ZERO) > 0) {
            final SavingsAccount fromSavingsAccount = null;
            final boolean isRegularTransaction = true;
            final boolean isExceptionForBalanceCheck = false;
            AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, transactionAmount,
                    data.fromAccountType(), data.toAccountType(), data.fromAccount().accountId(),
                    data.toAccount().accountId(), data.name() + " Standing instruction trasfer ", null, null,
                    null, null, data.toTransferType(), null, null, data.transferType().getValue(), null, null,
                    null, null, null, fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
            final boolean transferCompleted = transferAmount(sb, accountTransferDTO, data.getId());

            if (transferCompleted) {
                final String updateQuery = "UPDATE m_account_transfer_standing_instructions SET last_run_date = ? where id = ?";
                this.jdbcTemplate.update(updateQuery, transactionDate.toDate(), data.getId());
            }

        }
    }
    if (sb.length() > 0) {
        throw new JobExecutionException(sb.toString());
    }

}

From source file:com.gst.portfolio.calendar.domain.Calendar.java

License:Apache License

public Map<String, Object> updateStartDateAndDerivedFeilds(final LocalDate newMeetingStartDate) {

    final Map<String, Object> actualChanges = new LinkedHashMap<>(9);

    final LocalDate currentDate = DateUtils.getLocalDateOfTenant();

    if (newMeetingStartDate.isBefore(currentDate)) {
        final String defaultUserMessage = "New meeting effective from date cannot be in past";
        throw new CalendarDateException("new.start.date.cannot.be.in.past", defaultUserMessage,
                newMeetingStartDate, getStartDateLocalDate());
    } else if (isStartDateAfter(newMeetingStartDate) && isStartDateBeforeOrEqual(currentDate)) {
        // new meeting date should be on or after start date or current
        // date// w  w w  .  j a v a 2s.co  m
        final String defaultUserMessage = "New meeting effective from date cannot be a date before existing meeting start date";
        throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage,
                newMeetingStartDate, getStartDateLocalDate());
    } else {

        actualChanges.put(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue(), newMeetingStartDate.toString());
        this.startDate = newMeetingStartDate.toDate();

        /*
         * If meeting start date is changed then there is possibilities of
         * recurring day may change, so derive the recurring day and update
         * it if it is changed. For weekly type is weekday and for monthly
         * type it is day of the month
         */

        CalendarFrequencyType calendarFrequencyType = CalendarUtils.getFrequency(this.recurrence);
        Integer interval = CalendarUtils.getInterval(this.recurrence);
        Integer repeatsOnDay = null;

        /*
         * Repeats on day, need to derive based on the start date
         */

        if (calendarFrequencyType.isWeekly()) {
            repeatsOnDay = newMeetingStartDate.getDayOfWeek();
        } else if (calendarFrequencyType.isMonthly()) {
            repeatsOnDay = newMeetingStartDate.getDayOfMonth();
        }

        // TODO cover other recurrence also

        this.recurrence = constructRecurrence(calendarFrequencyType, interval, repeatsOnDay, null);

    }

    return actualChanges;

}

From source file:com.gst.portfolio.calendar.service.CalendarReadPlatformServiceImpl.java

License:Apache License

private LocalDate getPeriodStartDate(final LocalDate seedDate, final LocalDate recurrenceStartDate,
        final LocalDate fromDate) {
    LocalDate periodStartDate = null;
    if (fromDate != null) {
        periodStartDate = fromDate;//w  w  w.j av  a 2s. com
    } else {
        final LocalDate currentDate = DateUtils.getLocalDateOfTenant();
        if (seedDate.isBefore(currentDate.minusYears(1))) {
            periodStartDate = currentDate.minusYears(1);
        } else {
            periodStartDate = recurrenceStartDate;
        }
    }
    return periodStartDate;
}

From source file:com.gst.portfolio.client.service.ClientChargeWritePlatformServiceJpaRepositoryImpl.java

License:Apache License

@Override
public CommandProcessingResult addCharge(Long clientId, JsonCommand command) {
    try {//from   w ww  .j a v a2s  . c  o  m
        this.clientChargeDataValidator.validateAdd(command.json());

        final Client client = clientRepository.getActiveClientInUserScope(clientId);

        final Long chargeDefinitionId = command.longValueOfParameterNamed(ClientApiConstants.chargeIdParamName);
        final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(chargeDefinitionId);

        // validate for client charge
        if (!charge.isClientCharge()) {
            final String errorMessage = "Charge with identifier " + charge.getId()
                    + " cannot be applied to a Client";
            throw new ChargeCannotBeAppliedToException("client", errorMessage, charge.getId());
        }

        final ClientCharge clientCharge = ClientCharge.createNew(client, charge, command);
        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat());
        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
                .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);
        LocalDate activationDate = client.getActivationLocalDate();
        LocalDate dueDate = clientCharge.getDueLocalDate();
        if (dueDate.isBefore(activationDate)) {
            baseDataValidator.reset().parameter(ClientApiConstants.dueAsOfDateParamName)
                    .value(dueDate.toString(fmt))
                    .failWithCodeNoParameterAddedToErrorCode("dueDate.before.activationDate");

            if (!dataValidationErrors.isEmpty()) {
                throw new PlatformApiDataValidationException(dataValidationErrors);
            }
        }

        validateDueDateOnWorkingDay(clientCharge, fmt);
        this.clientChargeRepository.saveAndFlush(clientCharge);

        return new CommandProcessingResultBuilder() //
                .withEntityId(clientCharge.getId()) //
                .withOfficeId(clientCharge.getClient().getOffice().getId()) //
                .withClientId(clientCharge.getClient().getId()) //
                .build();
    } catch (DataIntegrityViolationException dve) {
        handleDataIntegrityIssues(clientId, null, dve);
        return CommandProcessingResult.empty();
    }
}

From source file:com.gst.portfolio.client.service.ClientChargeWritePlatformServiceJpaRepositoryImpl.java

License:Apache License

/**
 * Validates transaction to ensure that <br>
 * charge is active <br>/*from  ww  w  .java  2s .com*/
 * transaction date is valid (between client activation and todays date)
 * <br>
 * charge is not already paid or waived <br>
 * amount is not more than total due
 * 
 * @param client
 * @param clientCharge
 * @param fmt
 * @param transactionDate
 * @param amountPaid
 * @param requiresTransactionDateValidation
 *            if set to false, transaction date specific validation is
 *            skipped
 * @param requiresTransactionAmountValidation
 *            if set to false transaction amount validation is skipped
 * @return
 */
private void validatePaymentDateAndAmount(final Client client, final ClientCharge clientCharge,
        final DateTimeFormatter fmt, final LocalDate transactionDate, final BigDecimal amountPaid,
        final boolean requiresTransactionDateValidation, final boolean requiresTransactionAmountValidation) {
    final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
    final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
            .resource(ClientApiConstants.CLIENT_CHARGES_RESOURCE_NAME);

    if (clientCharge.isNotActive()) {
        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("charge.is.not.active");
        if (!dataValidationErrors.isEmpty()) {
            throw new PlatformApiDataValidationException(dataValidationErrors);
        }
    }

    if (requiresTransactionDateValidation) {
        validateTransactionDateOnWorkingDay(transactionDate, clientCharge, fmt);

        if (client.getActivationLocalDate() != null
                && transactionDate.isBefore(client.getActivationLocalDate())) {
            baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName)
                    .value(transactionDate.toString(fmt))
                    .failWithCodeNoParameterAddedToErrorCode("transaction.before.activationDate");
            throw new PlatformApiDataValidationException(dataValidationErrors);
        }

        if (DateUtils.isDateInTheFuture(transactionDate)) {
            baseDataValidator.reset().parameter(ClientApiConstants.transactionDateParamName)
                    .value(transactionDate.toString(fmt))
                    .failWithCodeNoParameterAddedToErrorCode("transaction.is.futureDate");
            throw new PlatformApiDataValidationException(dataValidationErrors);
        }
    }

    // validate charge is not already paid or waived
    if (clientCharge.isWaived()) {
        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
                "transaction.invalid.account.charge.is.already.waived");
        if (!dataValidationErrors.isEmpty()) {
            throw new PlatformApiDataValidationException(dataValidationErrors);
        }
    } else if (clientCharge.isPaid()) {
        baseDataValidator.reset()
                .failWithCodeNoParameterAddedToErrorCode("transaction.invalid.account.charge.is.paid");
        if (!dataValidationErrors.isEmpty()) {
            throw new PlatformApiDataValidationException(dataValidationErrors);
        }
    }

    if (requiresTransactionAmountValidation) {
        final Money chargePaid = Money.of(clientCharge.getCurrency(), amountPaid);
        if (!clientCharge.getAmountOutstanding().isGreaterThanOrEqualTo(chargePaid)) {
            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
                    "transaction.invalid.charge.amount.paid.in.access");
            if (!dataValidationErrors.isEmpty()) {
                throw new PlatformApiDataValidationException(dataValidationErrors);
            }
        }
    }
}

From source file:com.gst.portfolio.floatingrates.data.FloatingRateDTO.java

License:Apache License

public BigDecimal fetchBaseRate(LocalDate date) {
    BigDecimal rate = null;//from w  ww  .  jav a2  s.c o m
    for (FloatingRatePeriodData periodData : this.baseLendingRatePeriods) {
        final LocalDate periodFromDate = new LocalDate(periodData.getFromDate());
        if (periodFromDate.isBefore(date) || periodFromDate.isEqual(date)) {
            rate = periodData.getInterestRate();
            break;
        }
    }
    return rate;
}

From source file:com.gst.portfolio.loanaccount.domain.Loan.java

License:Apache License

/**
 * Creates a loanTransaction for "Apply Charge Event" with transaction date
 * set to "suppliedTransactionDate". The newly created transaction is also
 * added to the Loan on which this method is called.
 * /*ww w .j  ava2s . co m*/
 * If "suppliedTransactionDate" is not passed Id, the transaction date is
 * set to the loans due date if the due date is lesser than todays date. If
 * not, the transaction date is set to todays date
 * 
 * @param loanCharge
 * @param suppliedTransactionDate
 * @return
 */
public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge,
        final LocalDate suppliedTransactionDate, final AppUser currentUser) {
    final Money chargeAmount = loanCharge.getAmount(getCurrency());
    Money feeCharges = chargeAmount;
    Money penaltyCharges = Money.zero(loanCurrency());
    if (loanCharge.isPenaltyCharge()) {
        penaltyCharges = chargeAmount;
        feeCharges = Money.zero(loanCurrency());
    }

    LocalDate transactionDate = null;

    if (suppliedTransactionDate != null) {
        transactionDate = suppliedTransactionDate;
    } else {
        transactionDate = loanCharge.getDueLocalDate();
        final LocalDate currentDate = DateUtils.getLocalDateOfTenant();

        // if loan charge is to be applied on a future date, the loan
        // transaction would show todays date as applied date
        if (transactionDate == null || currentDate.isBefore(transactionDate)) {
            transactionDate = currentDate;
        }
    }

    final LoanTransaction applyLoanChargeTransaction = LoanTransaction.accrueLoanCharge(this, getOffice(),
            chargeAmount, transactionDate, feeCharges, penaltyCharges, DateUtils.getLocalDateTimeOfTenant(),
            currentUser);
    Integer installmentNumber = null;
    final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(applyLoanChargeTransaction, loanCharge,
            loanCharge.getAmount(getCurrency()).getAmount(), installmentNumber);
    applyLoanChargeTransaction.getLoanChargesPaid().add(loanChargePaidBy);
    addLoanTransaction(applyLoanChargeTransaction);
    return applyLoanChargeTransaction;
}

From source file:com.gst.portfolio.loanaccount.domain.Loan.java

License:Apache License

public Map<String, Object> loanApplicationRejection(final AppUser currentUser, final JsonCommand command,
        final LoanLifecycleStateMachine loanLifecycleStateMachine) {

    validateAccountStatus(LoanEvent.LOAN_REJECTED);

    final Map<String, Object> actualChanges = new LinkedHashMap<>();

    final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED,
            LoanStatus.fromInt(this.loanStatus));
    if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
        this.loanStatus = statusEnum.getValue();
        actualChanges.put("status", LoanEnumerations.status(this.loanStatus));

        final LocalDate rejectedOn = command.localDateValueOfParameterNamed("rejectedOnDate");

        final Locale locale = new Locale(command.locale());
        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);

        this.rejectedOnDate = rejectedOn.toDate();
        this.rejectedBy = currentUser;
        this.closedOnDate = rejectedOn.toDate();
        this.closedBy = currentUser;

        actualChanges.put("locale", command.locale());
        actualChanges.put("dateFormat", command.dateFormat());
        actualChanges.put("rejectedOnDate", rejectedOn.toString(fmt));
        actualChanges.put("closedOnDate", rejectedOn.toString(fmt));

        if (rejectedOn.isBefore(getSubmittedOnDate())) {
            final String errorMessage = "The date on which a loan is rejected cannot be before its submittal date: "
                    + getSubmittedOnDate().toString();
            throw new InvalidLoanStateTransitionException("reject", "cannot.be.before.submittal.date",
                    errorMessage, rejectedOn, getSubmittedOnDate());
        }/* w  ww . j  av  a 2 s .c  o  m*/

        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REJECTED, rejectedOn);

        if (rejectedOn.isAfter(DateUtils.getLocalDateOfTenant())) {
            final String errorMessage = "The date on which a loan is rejected cannot be in the future.";
            throw new InvalidLoanStateTransitionException("reject", "cannot.be.a.future.date", errorMessage,
                    rejectedOn);
        }
    } else {
        final String errorMessage = "Only the loan applications with status 'Submitted and pending approval' are allowed to be rejected.";
        throw new InvalidLoanStateTransitionException("reject", "cannot.reject", errorMessage);
    }

    return actualChanges;
}

From source file:com.gst.portfolio.loanaccount.domain.Loan.java

License:Apache License

public Map<String, Object> loanApplicationWithdrawnByApplicant(final AppUser currentUser,
        final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine) {

    final Map<String, Object> actualChanges = new LinkedHashMap<>();

    final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_WITHDRAWN,
            LoanStatus.fromInt(this.loanStatus));
    if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
        this.loanStatus = statusEnum.getValue();
        actualChanges.put("status", LoanEnumerations.status(this.loanStatus));

        LocalDate withdrawnOn = command.localDateValueOfParameterNamed("withdrawnOnDate");
        if (withdrawnOn == null) {
            withdrawnOn = command.localDateValueOfParameterNamed("eventDate");
        }/* w  w  w  . ja va2s .c  o m*/

        final Locale locale = new Locale(command.locale());
        final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);

        this.withdrawnOnDate = withdrawnOn.toDate();
        this.withdrawnBy = currentUser;
        this.closedOnDate = withdrawnOn.toDate();
        this.closedBy = currentUser;

        actualChanges.put("locale", command.locale());
        actualChanges.put("dateFormat", command.dateFormat());
        actualChanges.put("withdrawnOnDate", withdrawnOn.toString(fmt));
        actualChanges.put("closedOnDate", withdrawnOn.toString(fmt));

        if (withdrawnOn.isBefore(getSubmittedOnDate())) {
            final String errorMessage = "The date on which a loan is withdrawn cannot be before its submittal date: "
                    + getSubmittedOnDate().toString();
            throw new InvalidLoanStateTransitionException("withdraw", "cannot.be.before.submittal.date",
                    errorMessage, command, getSubmittedOnDate());
        }

        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_WITHDRAWN, withdrawnOn);

        if (withdrawnOn.isAfter(DateUtils.getLocalDateOfTenant())) {
            final String errorMessage = "The date on which a loan is withdrawn cannot be in the future.";
            throw new InvalidLoanStateTransitionException("withdraw", "cannot.be.a.future.date", errorMessage,
                    command);
        }
    } else {
        final String errorMessage = "Only the loan applications with status 'Submitted and pending approval' are allowed to be withdrawn by applicant.";
        throw new InvalidLoanStateTransitionException("withdraw", "cannot.withdraw", errorMessage);
    }

    return actualChanges;
}

From source file:com.gst.portfolio.loanaccount.domain.Loan.java

License:Apache License

public Map<String, Object> loanApplicationApproval(final AppUser currentUser, final JsonCommand command,
        final JsonArray disbursementDataArray, final LoanLifecycleStateMachine loanLifecycleStateMachine) {

    validateAccountStatus(LoanEvent.LOAN_APPROVED);

    final Map<String, Object> actualChanges = new LinkedHashMap<>();

    /*/* w  w  w.j a v a  2 s  .  c  o  m*/
     * statusEnum is holding the possible new status derived from
     * loanLifecycleStateMachine.transition.
     */

    final LoanStatus newStatusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_APPROVED,
            LoanStatus.fromInt(this.loanStatus));

    /*
     * FIXME: There is no need to check below condition, if
     * loanLifecycleStateMachine.transition is doing it's responsibility
     * properly. Better implementation approach is, if code passes invalid
     * combination of states (fromState and toState), state machine should
     * return invalidate state and below if condition should check for not
     * equal to invalidateState, instead of check new value is same as
     * present value.
     */

    if (!newStatusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
        this.loanStatus = newStatusEnum.getValue();
        actualChanges.put("status", LoanEnumerations.status(this.loanStatus));

        // only do below if status has changed in the 'approval' case
        LocalDate approvedOn = command.localDateValueOfParameterNamed("approvedOnDate");
        String approvedOnDateChange = command.stringValueOfParameterNamed("approvedOnDate");
        if (approvedOn == null) {
            approvedOn = command.localDateValueOfParameterNamed("eventDate");
            approvedOnDateChange = command.stringValueOfParameterNamed("eventDate");
        }

        LocalDate expecteddisbursementDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");

        BigDecimal approvedLoanAmount = command
                .bigDecimalValueOfParameterNamed(LoanApiConstants.approvedLoanAmountParameterName);

        if (approvedLoanAmount != null) {

            // Approved amount has to be less than or equal to principal
            // amount demanded

            if (approvedLoanAmount.compareTo(this.proposedPrincipal) == -1) {

                this.approvedPrincipal = approvedLoanAmount;

                /*
                 * All the calculations are done based on the principal
                 * amount, so it is necessary to set principal amount to
                 * approved amount
                 */

                this.loanRepaymentScheduleDetail.setPrincipal(approvedLoanAmount);

                actualChanges.put(LoanApiConstants.approvedLoanAmountParameterName, approvedLoanAmount);
                actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, approvedLoanAmount);
            } else if (approvedLoanAmount.compareTo(this.proposedPrincipal) == 1) {
                final String errorMessage = "Loan approved amount can't be greater than loan amount demanded.";
                throw new InvalidLoanStateTransitionException("approval",
                        "amount.can't.be.greater.than.loan.amount.demanded", errorMessage,
                        this.proposedPrincipal, approvedLoanAmount);
            }

            /* Update disbursement details */
            if (disbursementDataArray != null) {
                updateDisbursementDetails(command, actualChanges);
            }
        }
        if (loanProduct.isMultiDisburseLoan()) {
            recalculateAllCharges();

            if (this.disbursementDetails.isEmpty()) {
                final String errorMessage = "For this loan product, disbursement details must be provided";
                throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName,
                        errorMessage);
            }

            if (this.disbursementDetails.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());
            }
        }
        this.approvedOnDate = approvedOn.toDate();
        this.approvedBy = currentUser;
        actualChanges.put("locale", command.locale());
        actualChanges.put("dateFormat", command.dateFormat());
        actualChanges.put("approvedOnDate", approvedOnDateChange);

        final LocalDate submittalDate = new LocalDate(this.submittedOnDate);
        if (approvedOn.isBefore(submittalDate)) {
            final String errorMessage = "The date on which a loan is approved cannot be before its submittal date: "
                    + submittalDate.toString();
            throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date",
                    errorMessage, getApprovedOnDate(), submittalDate);
        }

        if (expecteddisbursementDate != null) {
            this.expectedDisbursementDate = expecteddisbursementDate.toDate();
            actualChanges.put("expectedDisbursementDate", expectedDisbursementDate);

            if (expecteddisbursementDate.isBefore(approvedOn)) {
                final String errorMessage = "The expected disbursement date should be either on or after the approval date: "
                        + approvedOn.toString();
                throw new InvalidLoanStateTransitionException("expecteddisbursal",
                        "should.be.on.or.after.approval.date", errorMessage, getApprovedOnDate(),
                        expecteddisbursementDate);
            }
        }

        validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_APPROVED, approvedOn);

        if (approvedOn.isAfter(DateUtils.getLocalDateOfTenant())) {
            final String errorMessage = "The date on which a loan is approved cannot be in the future.";
            throw new InvalidLoanStateTransitionException("approval", "cannot.be.a.future.date", errorMessage,
                    getApprovedOnDate());
        }

        if (this.loanOfficer != null) {
            final LoanOfficerAssignmentHistory loanOfficerAssignmentHistory = LoanOfficerAssignmentHistory
                    .createNew(this, this.loanOfficer, approvedOn);
            this.loanOfficerHistory.add(loanOfficerAssignmentHistory);
        }
    }

    return actualChanges;
}