@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 writeOff(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());
    }

    final LoanTransaction writeoff =
        loan.closeAsWrittenOff(command.getTransactionDate(), defaultLoanLifecycleStateMachine());

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

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

    return new EntityIdentifier(loan.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 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 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());
  }