@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 createRole(final JsonCommand command) {

    context.authenticatedUser();

    final RoleCommand roleCommand =
        this.roleCommandFromApiJsonDeserializer.commandFromApiJson(command.json());
    roleCommand.validateForCreate();

    final Role entity = Role.fromJson(command);
    this.roleRepository.save(entity);

    return EntityIdentifier.resourceResult(entity.getId(), null);
  }
  @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 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);
  }