@Transactional
  @Override
  public EntityIdentifier waiveLoanCharge(final LoanChargeCommand command) {

    this.context.authenticatedUser();

    // LoanChargeCommandValidator validator = new
    // LoanChargeCommandValidator(command);
    // validator.validateForUpdate();

    final Long loanId = command.getLoanId();
    final Loan loan = retrieveLoanBy(loanId);

    final Long loanChargeId = command.getId();
    final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, loanChargeId);

    final LoanTransaction waiveTransaction =
        loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine());

    this.loanTransactionRepository.save(waiveTransaction);
    this.loanRepository.save(loan);

    final String noteText = ""; // command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      final Note note = Note.loanTransactionNote(loan, waiveTransaction, noteText);
      this.noteRepository.save(note);
    }

    return new EntityIdentifier(loanCharge.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier waiveInterestOnLoan(final LoanTransactionCommand command) {

    context.authenticatedUser();

    final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command);
    validator.validate();

    final Loan loan = this.loanRepository.findOne(command.getLoanId());
    if (loan == null) {
      throw new LoanNotFoundException(command.getLoanId());
    }

    final LoanTransaction waiveTransaction =
        loan.waiveInterest(
            command.getTransactionAmount(),
            command.getTransactionDate(),
            defaultLoanLifecycleStateMachine());

    this.loanTransactionRepository.save(waiveTransaction);
    this.loanRepository.save(loan);

    final String noteText = command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      final Note note = Note.loanTransactionNote(loan, waiveTransaction, noteText);
      this.noteRepository.save(note);
    }

    return new EntityIdentifier(loan.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier closeAsRescheduled(final LoanTransactionCommand command) {
    context.authenticatedUser();

    final LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command);
    validator.validateNonMonetaryTransaction();

    final Loan loan = this.loanRepository.findOne(command.getLoanId());
    if (loan == null) {
      throw new LoanNotFoundException(command.getLoanId());
    }

    loan.closeAsMarkedForReschedule(
        command.getTransactionDate(), defaultLoanLifecycleStateMachine());

    this.loanRepository.save(loan);

    final String noteText = command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      final Note note = Note.loanNote(loan, noteText);
      this.noteRepository.save(note);
    }

    return new EntityIdentifier(loan.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier disburseLoan(final Long loanId, final JsonCommand command) {

    final AppUser currentUser = context.authenticatedUser();

    final LoanStateTransitionCommand disburseLoan =
        this.loanStateTransitionCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
    disburseLoan.validate();

    final Loan loan = retrieveLoanBy(loanId);

    final String noteText = command.stringValueOfParameterNamed("note");
    final LocalDate actualDisbursementDate = disburseLoan.getDisbursedOnDate();
    if (this.isBeforeToday(actualDisbursementDate) && currentUser.canNotDisburseLoanInPast()) {
      throw new NoAuthorizationException(
          "User has no authority to disburse loan with a date in the past.");
    }

    final ApplicationCurrency currency =
        this.applicationCurrencyRepository.findOneByCode(loan.getPrincpal().getCurrencyCode());

    final Map<String, Object> changes =
        loan.disburse(command, defaultLoanLifecycleStateMachine(), currency);
    this.loanRepository.save(loan);

    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.loanNote(loan, noteText);
      this.noteRepository.save(note);
    }

    return EntityIdentifier.resourceResult(loanId, command.commandId(), changes);
  }
  @Transactional
  @Override
  public EntityIdentifier applicantWithdrawsFromLoanApplication(
      final Long loanId, final JsonCommand command) {

    final AppUser currentUser = context.authenticatedUser();

    final LoanStateTransitionCommand applicantWithdrawsFromLoanApplication =
        this.loanStateTransitionCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
    applicantWithdrawsFromLoanApplication.validate();

    final Loan loan = retrieveLoanBy(loanId);

    final LocalDate eventDate = applicantWithdrawsFromLoanApplication.getWithdrawnOnDate();
    if (this.isBeforeToday(eventDate) && currentUser.canNotWithdrawByClientLoanInPast()) {
      throw new NoAuthorizationException(
          "User has no authority to mark loan as withdrawn by applicant with a date in the past.");
    }

    final Map<String, Object> changes =
        loan.loanApplicationWithdrawnByApplicant(command, defaultLoanLifecycleStateMachine());
    this.loanRepository.save(loan);

    String noteText = command.stringValueOfParameterNamed("note");
    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.loanNote(loan, noteText);
      this.noteRepository.save(note);
    }

    return EntityIdentifier.resourceResult(loanId, command.commandId(), changes);
  }
  @Transactional
  @Override
  public EntityIdentifier submitLoanApplication(final JsonCommand command) {

    context.authenticatedUser();

    LoanApplicationCommand loanApplicationCommand =
        this.fromApiJsonDeserializer.commandFromApiJson(command.json());
    loanApplicationCommand.validate();

    CalculateLoanScheduleQuery calculateLoanScheduleQuery =
        this.calculateLoanScheduleQueryFromApiJsonDeserializer.commandFromApiJson(command.json());
    calculateLoanScheduleQuery.validate();

    final Loan newLoanApplication = loanAssembler.assembleFrom(command);

    this.loanRepository.save(newLoanApplication);

    final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
    if (StringUtils.isNotBlank(submittedOnNote)) {
      Note note = Note.loanNote(newLoanApplication, submittedOnNote);
      this.noteRepository.save(note);
    }

    return EntityIdentifier.resourceResult(newLoanApplication.getId(), command.commandId());
  }
  @Transactional
  @Override
  public EntityIdentifier adjustLoanTransaction(final AdjustLoanTransactionCommand command) {

    context.authenticatedUser();

    AdjustLoanTransactionCommandValidator validator =
        new AdjustLoanTransactionCommandValidator(command);
    validator.validate();

    Loan loan = this.loanRepository.findOne(command.getLoanId());
    if (loan == null) {
      throw new LoanNotFoundException(command.getLoanId());
    }

    LoanTransaction transactionToAdjust =
        this.loanTransactionRepository.findOne(command.getTransactionId());
    if (transactionToAdjust == null) {
      throw new LoanTransactionNotFoundException(command.getTransactionId());
    }

    final MonetaryCurrency currency = loan.repaymentScheduleDetail().getPrincipal().getCurrency();
    final Money transactionAmount = Money.of(currency, command.getTransactionAmount());

    // adjustment is only supported for repayments and waivers at present
    LocalDate transactionDate = command.getTransactionDate();
    LoanTransaction newTransactionDetail =
        LoanTransaction.repayment(transactionAmount, transactionDate);
    if (transactionToAdjust.isInterestWaiver()) {
      newTransactionDetail = LoanTransaction.waiver(loan, transactionAmount, transactionDate);
    }

    loan.adjustExistingTransaction(
        transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine());

    if (newTransactionDetail.isGreaterThanZero(currency)) {
      this.loanTransactionRepository.save(newTransactionDetail);
    }

    this.loanRepository.save(loan);

    String noteText = command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.loanTransactionNote(loan, newTransactionDetail, noteText);
      this.noteRepository.save(note);
    }

    return new EntityIdentifier(loan.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier approveDepositApplication(
      final DepositStateTransitionApprovalCommand command) {

    // AppUser currentUser = context.authenticatedUser();

    DepositStateTransitionApprovalCommandValidator validator =
        new DepositStateTransitionApprovalCommandValidator(command);
    validator.validate();

    DepositAccount account = this.depositAccountRepository.findOne(command.getAccountId());
    if (account == null || account.isDeleted()) {
      throw new DepositAccountNotFoundException(command.getAccountId());
    }

    // FIXME - madhukar - you are checking for loan permission here instead
    // of some specific deposit account permission.
    // removing check for now until rules dealing with creating and
    // maintaining deposit accounts in the past is clarified
    LocalDate eventDate = command.getEventDate();
    // if (this.isBeforeToday(eventDate) &&
    // currentUser.canNotApproveLoanInPast()) {
    // throw new
    // NoAuthorizationException("User has no authority to approve deposit with a date in the
    // past.");
    // }

    account.approve(
        eventDate,
        defaultDepositLifecycleStateMachine(),
        command,
        this.fixedTermDepositInterestCalculator);

    this.depositAccountRepository.save(account);

    String noteText = command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.depositNote(account, noteText);
      this.noteRepository.save(note);
    }

    return new EntityIdentifier(account.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier withdrawDepositAccountMoney(
      final DepositAccountWithdrawalCommand command) {

    context.authenticatedUser();

    DepositAccountWithdrawalCommandValidator validator =
        new DepositAccountWithdrawalCommandValidator(command);
    validator.validate();

    DepositAccount account = this.depositAccountRepository.findOne(command.getAccountId());
    if (account == null || account.isDeleted()) {
      throw new DepositAccountNotFoundException(command.getAccountId());
    }

    LocalDate eventDate = command.getMaturesOnDate();

    Integer lockinPeriod = account.getLockinPeriod();
    LocalDate lockinPeriodExpDate =
        account.getActualCommencementDate().plusMonths(Integer.valueOf(lockinPeriod));
    if (account.isLockinPeriodAllowed()) {
      if (eventDate.isBefore(lockinPeriodExpDate)) {
        throw new DepositAccountTransactionsException(
            "deposit.transaction.canot.withdraw.before.lockinperiod.reached",
            "You can not withdraw your application before maturity date reached");
      }
    }
    // if (eventDate.isBefore(account.maturesOnDate())) {
    // this.depositAccountAssembler.adjustTotalAmountForPreclosureInterest(account,
    // eventDate);
    // }
    account.withdrawDepositAccountMoney(
        defaultDepositLifecycleStateMachine(), fixedTermDepositInterestCalculator, eventDate);
    this.depositAccountRepository.save(account);
    String noteText = command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.depositNote(account, noteText);
      this.noteRepository.save(note);
    }

    return new EntityIdentifier(account.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier undoLoanDisbursal(final Long loanId, final JsonCommand command) {

    context.authenticatedUser();

    final Loan loan = retrieveLoanBy(loanId);

    final Map<String, Object> changes = loan.undoDisbursal(defaultLoanLifecycleStateMachine());
    this.loanRepository.save(loan);

    final String noteText = command.stringValueOfParameterNamed("note");
    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.loanNote(loan, noteText);
      this.noteRepository.save(note);
    }

    return EntityIdentifier.resourceResult(loanId, command.commandId(), changes);
  }
  @Transactional
  @Override
  public EntityIdentifier undoDepositApproval(UndoStateTransitionCommand command) {

    context.authenticatedUser();

    DepositAccount account = this.depositAccountRepository.findOne(command.getLoanId());
    if (account == null || account.isDeleted()) {
      throw new DepositAccountNotFoundException(command.getLoanId());
    }

    account.undoDepositApproval(defaultDepositLifecycleStateMachine());
    this.depositAccountRepository.save(account);

    String noteText = command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.depositNote(account, noteText);
      this.noteRepository.save(note);
    }
    return new EntityIdentifier(account.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier makeLoanRepayment(final LoanTransactionCommand command) {

    AppUser currentUser = context.authenticatedUser();

    LoanTransactionCommandValidator validator = new LoanTransactionCommandValidator(command);
    validator.validate();

    Loan loan = this.loanRepository.findOne(command.getLoanId());
    if (loan == null) {
      throw new LoanNotFoundException(command.getLoanId());
    }

    LocalDate transactionDate = command.getTransactionDate();
    if (this.isBeforeToday(transactionDate) && currentUser.canNotMakeRepaymentOnLoanInPast()) {
      throw new NoAuthorizationException(
          "error.msg.no.permission.to.make.repayment.on.loan.in.past");
    }

    Money repayment =
        Money.of(
            loan.repaymentScheduleDetail().getPrincipal().getCurrency(),
            command.getTransactionAmount());

    LoanTransaction loanRepayment = LoanTransaction.repayment(repayment, transactionDate);
    loan.makeRepayment(loanRepayment, defaultLoanLifecycleStateMachine());
    this.loanTransactionRepository.save(loanRepayment);
    this.loanRepository.save(loan);

    String noteText = command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.loanTransactionNote(loan, loanRepayment, noteText);
      this.noteRepository.save(note);
    }

    return new EntityIdentifier(loan.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier withdrawDepositApplication(DepositStateTransitionCommand command) {

    // AppUser currentUser = context.authenticatedUser();

    DepositStateTransitionCommandValidator validator =
        new DepositStateTransitionCommandValidator(command);
    validator.validate();

    DepositAccount account = this.depositAccountRepository.findOne(command.getAccountId());
    if (account == null || account.isDeleted()) {
      throw new DepositAccountNotFoundException(command.getAccountId());
    }

    // removing check for now until rules dealing with creating and
    // maintaining deposit accounts in the past is clarified
    LocalDate eventDate = command.getEventDate();
    // if (this.isBeforeToday(eventDate) &&
    // currentUser.canNotApproveLoanInPast()) {
    // throw new
    // NoAuthorizationException("User has no authority to approve deposit with a date in the
    // past.");
    // }

    account.withdrawnByApplicant(eventDate, defaultDepositLifecycleStateMachine());
    this.depositAccountRepository.save(account);

    String noteText = command.getNote();
    if (StringUtils.isNotBlank(noteText)) {
      Note note = Note.depositNote(account, noteText);
      this.noteRepository.save(note);
    }

    return new EntityIdentifier(account.getId());
  }
  @Transactional
  @Override
  public EntityIdentifier modifyLoanApplication(final Long loanId, final JsonCommand command) {

    context.authenticatedUser();

    LoanApplicationCommand loanApplicationCommand =
        this.fromApiJsonDeserializer.commandFromApiJson(command.json());
    loanApplicationCommand.validate();

    CalculateLoanScheduleQuery calculateLoanScheduleQuery =
        this.calculateLoanScheduleQueryFromApiJsonDeserializer.commandFromApiJson(command.json());
    calculateLoanScheduleQuery.validate();

    final Loan existingLoanApplication = retrieveLoanBy(loanId);

    final Map<String, Object> changes =
        existingLoanApplication.modifyLoanApplication(
            command, loanApplicationCommand.getCharges(), this.aprCalculator);

    final String clientIdParamName = "clientId";
    if (changes.containsKey(clientIdParamName)) {
      final Long clientId = command.longValueOfParameterNamed(clientIdParamName);
      final Client client = this.clientRepository.findOne(clientId);
      if (client == null || client.isDeleted()) {
        throw new ClientNotFoundException(clientId);
      }

      existingLoanApplication.updateClient(client);
    }

    final String productIdParamName = "productId";
    if (changes.containsKey(productIdParamName)) {
      final Long productId = command.longValueOfParameterNamed(productIdParamName);
      final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
      if (loanProduct == null) {
        throw new LoanProductNotFoundException(productId);
      }

      existingLoanApplication.updateLoanProduct(loanProduct);
    }

    final String fundIdParamName = "fundId";
    if (changes.containsKey(fundIdParamName)) {
      final Long fundId = command.longValueOfParameterNamed(fundIdParamName);
      final Fund fund = this.loanAssembler.findFundByIdIfProvided(fundId);

      existingLoanApplication.updateFund(fund);
    }

    final String strategyIdParamName = "transactionProcessingStrategyId";
    if (changes.containsKey(strategyIdParamName)) {
      final Long strategyId = command.longValueOfParameterNamed(strategyIdParamName);
      final LoanTransactionProcessingStrategy strategy =
          this.loanAssembler.findStrategyByIdIfProvided(strategyId);

      existingLoanApplication.updateTransactionProcessingStrategy(strategy);
    }

    final String chargesParamName = "charges";
    if (changes.containsKey(chargesParamName)) {
      final Set<LoanCharge> loanCharges =
          this.loanChargeAssembler.fromParsedJson(command.parsedJson());
      existingLoanApplication.updateLoanCharges(loanCharges);
    }

    if (changes.containsKey("recalculateLoanSchedule")) {
      changes.remove("recalculateLoanSchedule");

      final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json());
      final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper);

      final LoanScheduleData loanSchedule =
          this.calculationPlatformService.calculateLoanSchedule(query);
      existingLoanApplication.updateLoanSchedule(loanSchedule);
      existingLoanApplication.updateLoanScheduleDependentDerivedFields();
    }

    this.loanRepository.save(existingLoanApplication);

    final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote");
    if (StringUtils.isNotBlank(submittedOnNote)) {
      Note note = Note.loanNote(existingLoanApplication, submittedOnNote);
      this.noteRepository.save(note);
    }

    return EntityIdentifier.resourceResult(loanId, command.commandId(), changes);
  }