org.mifosplatform.organisation.teller.service.TellerWritePlatformServiceJpaImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.mifosplatform.organisation.teller.service.TellerWritePlatformServiceJpaImpl.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.mifosplatform.organisation.teller.service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.mifosplatform.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY;
import org.mifosplatform.accounting.financialactivityaccount.domain.FinancialActivityAccount;
import org.mifosplatform.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
import org.mifosplatform.accounting.glaccount.data.GLAccountData;
import org.mifosplatform.accounting.glaccount.domain.GLAccount;
import org.mifosplatform.accounting.glaccount.service.GLAccountReadPlatformService;
import org.mifosplatform.accounting.journalentry.data.JournalEntryAssociationParametersData;
import org.mifosplatform.accounting.journalentry.domain.JournalEntry;
import org.mifosplatform.accounting.journalentry.domain.JournalEntryRepository;
import org.mifosplatform.accounting.journalentry.domain.JournalEntryType;
import org.mifosplatform.accounting.journalentry.exception.JournalEntryInvalidException;
import org.mifosplatform.accounting.journalentry.exception.JournalEntryInvalidException.GL_JOURNAL_ENTRY_INVALID_REASON;
import org.mifosplatform.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.security.exception.NoAuthorizationException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.staff.domain.StaffRepository;
import org.mifosplatform.organisation.staff.exception.StaffNotFoundException;
import org.mifosplatform.organisation.teller.data.TellerData;
import org.mifosplatform.organisation.teller.domain.Cashier;
import org.mifosplatform.organisation.teller.domain.CashierRepository;
import org.mifosplatform.organisation.teller.domain.CashierTransaction;
import org.mifosplatform.organisation.teller.domain.CashierTransactionRepository;
import org.mifosplatform.organisation.teller.domain.CashierTxnType;
import org.mifosplatform.organisation.teller.domain.Teller;
import org.mifosplatform.organisation.teller.domain.TellerRepository;
import org.mifosplatform.organisation.teller.domain.TellerRepositoryWrapper;
import org.mifosplatform.organisation.teller.exception.*;
import org.mifosplatform.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.client.domain.ClientTransaction;
import org.mifosplatform.useradministration.data.AppUserData;
import org.mifosplatform.useradministration.domain.AppUser;
import org.mifosplatform.useradministration.domain.AppUserRepository;
import org.mifosplatform.useradministration.service.AppUserReadPlatformService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformService {

    private final static Logger logger = LoggerFactory.getLogger(TellerWritePlatformServiceJpaImpl.class);

    private final PlatformSecurityContext context;
    private final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer;
    private final TellerRepository tellerRepository;
    private final TellerRepositoryWrapper tellerRepositoryWrapper;
    private final OfficeRepository officeRepository;
    private final StaffRepository staffRepository;
    private final CashierRepository cashierRepository;
    private final CashierTransactionRepository cashierTxnRepository;
    private final JournalEntryRepository glJournalEntryRepository;
    private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper;
    private final TellerManagementReadPlatformService tellerManagementReadPlatformService;
    private final GLAccountReadPlatformService glAccountReadPlatformService;
    private final AppUserRepository appUserRepository;

    @Autowired
    public TellerWritePlatformServiceJpaImpl(final PlatformSecurityContext context,
            final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer,
            final TellerRepository tellerRepository, final TellerRepositoryWrapper tellerRepositoryWrapper,
            final OfficeRepository officeRepository, final StaffRepository staffRepository,
            CashierRepository cashierRepository, CashierTransactionRepository cashierTxnRepository,
            JournalEntryRepository glJournalEntryRepository,
            FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper,
            final TellerManagementReadPlatformService tellerManagementReadPlatformService,
            final GLAccountReadPlatformService glAccountReadPlatformService,
            final AppUserRepository appUserRepository) {
        this.context = context;
        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
        this.tellerRepository = tellerRepository;
        this.tellerRepositoryWrapper = tellerRepositoryWrapper;
        this.officeRepository = officeRepository;
        this.staffRepository = staffRepository;
        this.cashierRepository = cashierRepository;
        this.cashierTxnRepository = cashierTxnRepository;
        this.glJournalEntryRepository = glJournalEntryRepository;
        this.financialActivityAccountRepositoryWrapper = financialActivityAccountRepositoryWrapper;
        this.tellerManagementReadPlatformService = tellerManagementReadPlatformService;
        this.glAccountReadPlatformService = glAccountReadPlatformService;
        this.appUserRepository = appUserRepository;
    }

    @Override
    @Transactional
    public CommandProcessingResult createTeller(JsonCommand command) {
        try {
            this.context.authenticatedUser();

            final Long officeId = command.longValueOfParameterNamed("officeId");

            this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());

            // final Office parent =
            // validateUserPriviledgeOnOfficeAndRetrieve(currentUser, officeId);
            final Office tellerOffice = this.officeRepository.findOne(officeId);
            if (tellerOffice == null) {
                throw new OfficeNotFoundException(officeId);
            }

            final Teller teller = Teller.fromJson(tellerOffice, command);

            // pre save to generate id for use in office hierarchy
            this.tellerRepository.save(teller);

            return new CommandProcessingResultBuilder() //
                    .withCommandId(command.commandId()) //
                    .withEntityId(teller.getId()) //
                    .withOfficeId(teller.getOffice().getId()) //
                    .build();
        } catch (final DataIntegrityViolationException dve) {
            handleTellerDataIntegrityIssues(command, dve);
            return CommandProcessingResult.empty();
        }
    }

    @Override
    @Transactional
    public CommandProcessingResult modifyTeller(Long tellerId, JsonCommand command) {
        try {

            final Long officeId = command.longValueOfParameterNamed("officeId");
            final Office tellerOffice = this.officeRepository.findOne(officeId);
            if (tellerOffice == null) {
                throw new OfficeNotFoundException(officeId);
            }

            final AppUser currentUser = this.context.authenticatedUser();

            this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());

            final Teller teller = validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);

            final Map<String, Object> changes = teller.update(tellerOffice, command);

            if (!changes.isEmpty()) {
                this.tellerRepository.saveAndFlush(teller);
            }

            return new CommandProcessingResultBuilder() //
                    .withCommandId(command.commandId()) //
                    .withEntityId(teller.getId()) //
                    .withOfficeId(teller.officeId()) //
                    .with(changes) //
                    .build();
        } catch (final DataIntegrityViolationException dve) {
            handleTellerDataIntegrityIssues(command, dve);
            return CommandProcessingResult.empty();
        }
    }

    /*
     * used to restrict modifying operations to office that are either the users
     * office or lower (child) in the office hierarchy
     */
    private Teller validateUserPriviledgeOnTellerAndRetrieve(final AppUser currentUser, final Long tellerId) {

        final Long userOfficeId = currentUser.getOffice().getId();
        final Office userOffice = this.officeRepository.findOne(userOfficeId);
        if (userOffice == null) {
            throw new OfficeNotFoundException(userOfficeId);
        }

        final Teller tellerToReturn = this.tellerRepository.findOne(tellerId);
        if (tellerToReturn != null) {
            final Long tellerOfficeId = tellerToReturn.officeId();
            if (userOffice.doesNotHaveAnOfficeInHierarchyWithId(tellerOfficeId)) {
                throw new NoAuthorizationException(
                        "User does not have sufficient priviledges to act on the provided office.");
            }
        } else {
            throw new TellerNotFoundException(tellerId);
        }

        return tellerToReturn;
    }

    @Override
    @Transactional
    public CommandProcessingResult deleteTeller(Long tellerId) {
        // TODO Auto-generated method stub

        Teller teller = tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
        Set<Cashier> isTellerIdPresentInCashier = teller.getCashiers();

        for (final Cashier tellerIdInCashier : isTellerIdPresentInCashier) {
            if (tellerIdInCashier.getTeller().getId().toString().equalsIgnoreCase(tellerId.toString())) {
                throw new CashierExistForTellerException(tellerId);
            }

        }
        tellerRepository.delete(teller);
        return new CommandProcessingResultBuilder() //
                .withEntityId(teller.getId()) //
                .build();

    }

    /*
     * Guaranteed to throw an exception no matter what the data integrity issue
     * is.
     */
    private void handleTellerDataIntegrityIssues(final JsonCommand command,
            final DataIntegrityViolationException dve) {

        final Throwable realCause = dve.getMostSpecificCause();
        if (realCause.getMessage().contains("m_tellers_name_unq")) {
            final String name = command.stringValueOfParameterNamed("name");
            throw new PlatformDataIntegrityException("error.msg.teller.duplicate.name",
                    "Teller with name `" + name + "` already exists", "name", name);
        }

        logger.error(dve.getMessage(), dve);
        throw new PlatformDataIntegrityException("error.msg.teller.unknown.data.integrity.issue",
                "Unknown data integrity issue with resource.");
    }

    @Override
    public CommandProcessingResult allocateCashierToTeller(final Long tellerId, JsonCommand command) {
        try {
            this.context.authenticatedUser();
            Long hourStartTime;
            Long minStartTime;
            String startTime = " ";
            final String endTime = " ";

            final Teller teller = this.tellerRepository.findOne(tellerId);
            if (teller == null) {
                throw new TellerNotFoundException(tellerId);
            }
            final Office tellerOffice = teller.getOffice();

            final Long staffId = command.longValueOfParameterNamed("staffId");

            this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());

            final Staff staff = this.staffRepository.findOne(staffId);
            if (staff == null) {
                throw new StaffNotFoundException(staffId);
            }

            // make sure this teller does not yet have an active cashier

            List<Cashier> activeTellerCashier = this.cashierRepository.getActiveTellerCashier(tellerId);

            if (!activeTellerCashier.isEmpty()) {

                throw new NoMoreThanOneActiveCashierPerTellerException(tellerId);
            }

            // make sure this cashier is not yet active in another teller

            List<Cashier> activeCashiers = this.cashierRepository.getActiveCashier(staffId, tellerId);

            if (!activeCashiers.isEmpty()) {

                throw new CashierAlreadyActiveInAnotherTellerException(staffId);
            }

            final Boolean isFullDay = command.booleanObjectValueOfParameterNamed("isFullDay");

            if (!isFullDay) {
                hourStartTime = command.longValueOfParameterNamed("hourStartTime");
                minStartTime = command.longValueOfParameterNamed("minStartTime");

                if (minStartTime == 0)
                    startTime = hourStartTime.toString() + ":" + minStartTime.toString() + "0";
                else
                    startTime = hourStartTime.toString() + ":" + minStartTime.toString();

            }

            final AppUser user = this.appUserRepository.findAppUserByStaffId(staffId);

            if (user == null) {
                throw new StaffIsNotLinkedToUserAccountException(staffId);
            }
            final Cashier cashier = Cashier.fromJson(tellerOffice, teller, staff, startTime, endTime, command,
                    user);

            cashier.assign();

            this.cashierRepository.save(cashier);

            return new CommandProcessingResultBuilder() //
                    .withCommandId(command.commandId()) //
                    .withEntityId(teller.getId()) //
                    .withSubEntityId(cashier.getId()) //
                    .build();
        } catch (final DataIntegrityViolationException dve) {
            handleTellerDataIntegrityIssues(command, dve);
            return CommandProcessingResult.empty();
        }
    }

    @Override
    @Transactional
    public CommandProcessingResult updateCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
        try {
            final AppUser currentUser = this.context.authenticatedUser();

            this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());

            final Long staffId = command.longValueOfParameterNamed("staffId");
            final Staff staff = this.staffRepository.findOne(staffId);
            if (staff == null) {
                throw new StaffNotFoundException(staffId);
            }

            final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);

            cashier.setStaff(staff);

            // TODO - check if staff office and teller office match

            final Map<String, Object> changes = cashier.update(command);

            if (!changes.isEmpty()) {
                this.cashierRepository.saveAndFlush(cashier);
            }

            return new CommandProcessingResultBuilder() //
                    .withCommandId(command.commandId()) //
                    .withEntityId(cashier.getTeller().getId()) //
                    .withSubEntityId(cashier.getId()) //
                    .with(changes) //
                    .build();
        } catch (final DataIntegrityViolationException dve) {
            handleTellerDataIntegrityIssues(command, dve);
            return CommandProcessingResult.empty();
        }
    }

    private Cashier validateUserPriviledgeOnCashierAndRetrieve(final AppUser currentUser, final Long tellerId,
            final Long cashierId) {

        validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);

        final Cashier cashierToReturn = this.cashierRepository.findOne(cashierId);

        return cashierToReturn;
    }

    @Override
    @Transactional
    public CommandProcessingResult assignCashierToTeller(Long tellerId, Long cashierId, JsonCommand command) {

        try {

            final AppUser currentUser = this.context.authenticatedUser();

            // make sure this teller does not yet have an active cashier

            List<Cashier> activeTellerCashier = this.cashierRepository.getActiveTellerCashier(tellerId);

            if (!activeTellerCashier.isEmpty()) {

                throw new NoMoreThanOneActiveCashierPerTellerException(tellerId);
            }

            // make sure this cashier is not yet active in another teller

            List<Cashier> activeCashiers = this.cashierRepository.getActiveCashier(cashierId, tellerId);

            if (!activeCashiers.isEmpty()) {

                throw new CashierAlreadyActiveInAnotherTellerException(cashierId);
            }

            final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);

            cashier.assign();

            final Map<String, Object> changes = new LinkedHashMap<>(7);
            changes.put("active", true);

            this.cashierRepository.saveAndFlush(cashier);

            return new CommandProcessingResultBuilder() //
                    .withCommandId(command.commandId()) //
                    .withEntityId(cashier.getTeller().getId()) //
                    .withSubEntityId(cashier.getId()) //
                    .with(changes) //
                    .build();
        } catch (final DataIntegrityViolationException dve) {
            handleTellerDataIntegrityIssues(command, dve);
            return CommandProcessingResult.empty();
        }

    }

    @Override
    @Transactional
    public CommandProcessingResult unassignCashierToTeller(Long tellerId, Long cashierId, JsonCommand command) {

        try {

            final AppUser currentUser = this.context.authenticatedUser();

            this.fromApiJsonDeserializer.validateForUnassignCashier(command.json());

            final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);

            cashier.unassign();

            final Map<String, Object> changes = new LinkedHashMap<>(7);
            changes.put("active", false);

            this.cashierRepository.saveAndFlush(cashier);

            return new CommandProcessingResultBuilder() //
                    .withCommandId(command.commandId()) //
                    .withEntityId(cashier.getTeller().getId()) //
                    .withSubEntityId(cashier.getId()) //
                    .with(changes) //
                    .build();
        } catch (final DataIntegrityViolationException dve) {
            handleTellerDataIntegrityIssues(command, dve);
            return CommandProcessingResult.empty();
        }

    }

    @Override
    @Transactional
    public CommandProcessingResult deleteCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
        try {
            final AppUser currentUser = this.context.authenticatedUser();
            final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);

            if (this.tellerManagementReadPlatformService.hasTransaction(cashierId)) {

                throw new CashierHasTransactionTellerException(cashierId);
            }

            this.cashierRepository.delete(cashier);

        } catch (final DataIntegrityViolationException dve) {
            handleTellerDataIntegrityIssues(command, dve);
            return CommandProcessingResult.empty();
        }

        return new CommandProcessingResultBuilder() //
                .withEntityId(cashierId) //
                .build();
    }

    /*
     * @Override public CommandProcessingResult inwardCashToCashier (final Long
     * cashierId, final CashierTransaction cashierTxn) { CashierTxnType txnType
     * = CashierTxnType.INWARD_CASH_TXN; // pre save to generate id for use in
     * office hierarchy this.cashierTxnRepository.save(cashierTxn); }
     */

    @Override
    public CommandProcessingResult allocateCashToCashier(final Long cashierId, JsonCommand command) {
        return doTransactionForCashier(cashierId, CashierTxnType.ALLOCATE, command); // For
                                                                                     // fund
                                                                                     // allocation
                                                                                     // to
                                                                                     // cashier
    }

    @Override
    public CommandProcessingResult settleCashFromCashier(final Long cashierId, JsonCommand command) {
        return doTransactionForCashier(cashierId, CashierTxnType.SETTLE, command); // For
                                                                                   // fund
                                                                                   // settlement
                                                                                   // from
                                                                                   // cashier
    }

    private CommandProcessingResult doTransactionForCashier(final Long cashierId, final CashierTxnType txnType,
            JsonCommand command) {
        try {
            final AppUser currentUser = this.context.authenticatedUser();

            final Cashier cashier = this.cashierRepository.findOne(cashierId);
            if (cashier == null) {
                throw new CashierNotFoundException(cashierId);
            }

            if (txnType.equals(CashierTxnType.SETTLE)) {

                final TellerData tellerData = this.tellerManagementReadPlatformService
                        .findTeller(cashier.getTeller().getId());

                this.fromApiJsonDeserializer.validateForCashSettleTxnForCashier(command.json(),
                        tellerData.getBalance(), cashier.getStartLocalDate());

            } else if (txnType.equals(CashierTxnType.ALLOCATE)) {

                this.fromApiJsonDeserializer.validateForCashTxnForCashier(command.json(),
                        cashier.getStartLocalDate());

                FinancialActivityAccount mainVaultFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
                        .findByFinancialActivityTypeWithNotFoundDetection(
                                FINANCIAL_ACTIVITY.CASH_AT_MAINVAULT.getValue());

                JournalEntryAssociationParametersData associationParametersData = new JournalEntryAssociationParametersData(
                        false, true, false, false, false, false);

                GLAccountData glAccountData = this.glAccountReadPlatformService.retrieveGLAccountById(
                        mainVaultFinancialActivityAccount.getGlAccount().getId(), associationParametersData);

                final BigDecimal txnAmount = command.bigDecimalValueOfParameterNamed("txnAmount");

                if (txnAmount.compareTo(new BigDecimal(glAccountData.getOrganizationRunningBalance())) > 0) {

                    throw new NotEnoughCashInTheMainVaultTellerException(glAccountData.getId());
                }
            }

            final LocalDate transactionDate = command.localDateValueOfParameterNamed("txnDate");
            final LocalDate todaysDate = new LocalDate();

            // make sure that a future date cannot be used as transaction date
            if (transactionDate.isAfter(todaysDate)) {
                throw new JournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.FUTURE_DATE,
                        transactionDate.toDate(), null, null);
            }

            final String entityType = command.stringValueOfParameterNamed("entityType");
            final Long entityId = command.longValueOfParameterNamed("entityId");
            if (entityType != null) {
                if (entityType.equals("loan account")) {
                    // TODO : Check if loan account exists
                    // LoanAccount loan = null;
                    // if (loan == null) { throw new
                    // LoanAccountFoundException(entityId); }
                } else if (entityType.equals("savings account")) {
                    // TODO : Check if loan account exists
                    // SavingsAccount savingsaccount = null;
                    // if (savingsaccount == null) { throw new
                    // SavingsAccountNotFoundException(entityId); }

                }
                if (entityType.equals("client")) {
                    // TODO: Check if client exists
                    // Client client = null;
                    // if (client == null) { throw new
                    // ClientNotFoundException(entityId); }
                } else {
                    // TODO : Invalid type handling
                }
            }

            final CashierTransaction cashierTxn = CashierTransaction.fromJson(cashier, command);
            cashierTxn.setTxnType(txnType.getId());

            this.cashierTxnRepository.save(cashierTxn);

            // Pass the journal entries
            FinancialActivityAccount mainVaultFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
                    .findByFinancialActivityTypeWithNotFoundDetection(
                            FINANCIAL_ACTIVITY.CASH_AT_MAINVAULT.getValue());
            FinancialActivityAccount tellerCashFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper
                    .findByFinancialActivityTypeWithNotFoundDetection(FINANCIAL_ACTIVITY.CASH_AT_TELLER.getValue());
            GLAccount creditAccount = null;
            GLAccount debitAccount = null;
            if (txnType.equals(CashierTxnType.ALLOCATE)) {
                debitAccount = tellerCashFinancialActivityAccount.getGlAccount();
                creditAccount = mainVaultFinancialActivityAccount.getGlAccount();
            } else if (txnType.equals(CashierTxnType.SETTLE)) {
                debitAccount = mainVaultFinancialActivityAccount.getGlAccount();
                creditAccount = tellerCashFinancialActivityAccount.getGlAccount();
            }

            final Office cashierOffice = cashier.getTeller().getOffice();

            // final Long time = System.currentTimeMillis();
            //final String uniqueVal = String.valueOf(time) + currentUser.getId() + cashierOffice.getId();

            final String transactionId = "C" + cashierTxn.getId(); // Long.toHexString(Long.parseLong(uniqueVal));

            ClientTransaction clientTransaction = null;

            final JournalEntry debitJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment
                    // detail
                    debitAccount, cashierTxn.getCurrencyCode(),
                    // transaction
                    transactionId, false, // manual entry
                    cashierTxn.getTxnDate(), JournalEntryType.DEBIT, cashierTxn.getTxnAmount(),
                    cashierTxn.getTxnNote(), // Description
                    PortfolioProductType.CASHIERTRANSACTION.getValue(), cashierTxn.getId(), null, // entity Type, entityId, reference number
                    null, null, clientTransaction); // Loan and Savings Txn

            final JournalEntry creditJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment
                    // detail
                    creditAccount, cashierTxn.getCurrencyCode(),
                    // transaction
                    transactionId, false, // manual entry
                    cashierTxn.getTxnDate(), JournalEntryType.CREDIT, cashierTxn.getTxnAmount(),
                    cashierTxn.getTxnNote(), // Description
                    PortfolioProductType.CASHIERTRANSACTION.getValue(), cashierTxn.getId(), null, // entity Type, entityId, reference number
                    null, null, clientTransaction); // Loan and Savings Txn

            this.glJournalEntryRepository.saveAndFlush(debitJournalEntry);
            this.glJournalEntryRepository.saveAndFlush(creditJournalEntry);

            return new CommandProcessingResultBuilder() //
                    .withCommandId(command.commandId()) //
                    .withEntityId(cashier.getId()) //
                    .withSubEntityId(cashierTxn.getId()) //
                    .build();
        } catch (final DataIntegrityViolationException dve) {
            handleTellerDataIntegrityIssues(command, dve);
            return CommandProcessingResult.empty();
        }
    }

}