feat(cashier): remove GL entries for vault-to-cashier transfers (Option A)

ALLOCATE (vault→cashier) and SETTLE (cashier→vault) no longer post
DR CASH_AT_TELLER / CR CASH_AT_MAINVAULT journal entries.

Rationale: Cash transfer between vault and teller drawer is an internal
physical movement — the denomination/amount is already tracked operationally
via m_cash_transfer and m_cash_settlement tables. A single Cash in Hand GL
(CASH_AT_MAINVAULT) covers all bank cash. GL entries now only occur on
actual customer deposit/withdrawal transactions (SAVINGS_REFERENCE ↔
SAVINGS_CONTROL), keeping the GL clean and RBI-compliant.

Removed unused imports and field injections:
  JournalEntryRepository, FinancialActivityAccountRepositoryWrapper,
  FinancialActivity, FinancialActivityAccount, GLAccount,
  JournalEntry, JournalEntryType

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-02 07:13:48 +05:30
co-authored by Claude Sonnet 4.6
parent 8bf28a8033
commit 6bdcac7b77
@@ -1,451 +1,399 @@
/** /**
* Licensed to the Apache Software Foundation (ASF) under one * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file * or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information * distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file * regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the * to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance * "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the * KIND, either express or implied. See the License for the
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.fineract.organisation.teller.service; package org.apache.fineract.organisation.teller.service;
import jakarta.persistence.PersistenceException; import jakarta.persistence.PersistenceException;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.accounting.glaccount.domain.GLAccount; import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler; import org.apache.fineract.organisation.staff.domain.StaffRepository;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException; import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.teller.domain.Cashier;
import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.teller.domain.CashierRepository;
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; import org.apache.fineract.organisation.teller.domain.CashierTransaction;
import org.apache.fineract.organisation.staff.domain.Staff; import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository;
import org.apache.fineract.organisation.staff.domain.StaffRepository; import org.apache.fineract.organisation.teller.domain.CashierTxnType;
import org.apache.fineract.organisation.staff.exception.StaffNotFoundException; import org.apache.fineract.organisation.teller.domain.Teller;
import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator; import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
import org.apache.fineract.organisation.teller.domain.Cashier; import org.apache.fineract.organisation.teller.exception.CashierExistForTellerException;
import org.apache.fineract.organisation.teller.domain.CashierRepository; import org.apache.fineract.organisation.teller.exception.CashierNotFoundException;
import org.apache.fineract.organisation.teller.domain.CashierTransaction; import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository; import org.apache.fineract.useradministration.domain.AppUser;
import org.apache.fineract.organisation.teller.domain.CashierTxnType; import org.springframework.dao.DataIntegrityViolationException;
import org.apache.fineract.organisation.teller.domain.Teller; import org.springframework.orm.jpa.JpaSystemException;
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper; import org.springframework.transaction.annotation.Transactional;
import org.apache.fineract.organisation.teller.exception.CashierExistForTellerException;
import org.apache.fineract.organisation.teller.exception.CashierNotFoundException; @AllArgsConstructor
import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer; @Slf4j
import org.apache.fineract.useradministration.domain.AppUser; public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformService {
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException; private final PlatformSecurityContext context;
import org.springframework.transaction.annotation.Transactional; private final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final TellerRepositoryWrapper tellerRepositoryWrapper;
@AllArgsConstructor private final OfficeRepositoryWrapper officeRepositoryWrapper;
@Slf4j private final StaffRepository staffRepository;
public class TellerWritePlatformServiceJpaImpl implements TellerWritePlatformService { private final CashierRepository cashierRepository;
private final CashierTransactionRepository cashierTxnRepository;
private final PlatformSecurityContext context; private final CashierTransactionDataValidator cashierTransactionDataValidator;
private final TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final TellerRepositoryWrapper tellerRepositoryWrapper; @Override
private final OfficeRepositoryWrapper officeRepositoryWrapper; @Transactional
private final StaffRepository staffRepository; public CommandProcessingResult createTeller(JsonCommand command) {
private final CashierRepository cashierRepository; try {
private final CashierTransactionRepository cashierTxnRepository; this.context.authenticatedUser();
private final JournalEntryRepository glJournalEntryRepository;
private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper; final Long officeId = command.longValueOfParameterNamed("officeId");
private final CashierTransactionDataValidator cashierTransactionDataValidator;
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
@Override
@Transactional // final Office parent =
public CommandProcessingResult createTeller(JsonCommand command) { // validateUserPriviledgeOnOfficeAndRetrieve(currentUser, officeId);
try { final Office tellerOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId);
this.context.authenticatedUser(); final Teller teller = Teller.fromJson(tellerOffice, command);
final Long officeId = command.longValueOfParameterNamed("officeId"); // pre save to generate id for use in office hierarchy
this.tellerRepositoryWrapper.saveAndFlush(teller);
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
return new CommandProcessingResultBuilder() //
// final Office parent = .withCommandId(command.commandId()) //
// validateUserPriviledgeOnOfficeAndRetrieve(currentUser, officeId); .withEntityId(teller.getId()) //
final Office tellerOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId); .withOfficeId(teller.getOffice().getId()) //
final Teller teller = Teller.fromJson(tellerOffice, command); .build();
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
// pre save to generate id for use in office hierarchy handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
this.tellerRepositoryWrapper.saveAndFlush(teller); return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
return new CommandProcessingResultBuilder() // Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
.withCommandId(command.commandId()) // handleTellerDataIntegrityIssues(command, throwable, dve);
.withEntityId(teller.getId()) // return CommandProcessingResult.empty();
.withOfficeId(teller.getOffice().getId()) // }
.build(); }
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); @Override
return CommandProcessingResult.empty(); @Transactional
} catch (final PersistenceException dve) { public CommandProcessingResult modifyTeller(Long tellerId, JsonCommand command) {
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); try {
handleTellerDataIntegrityIssues(command, throwable, dve);
return CommandProcessingResult.empty(); final Long officeId = command.longValueOfParameterNamed("officeId");
} final Office tellerOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId);
} final AppUser currentUser = this.context.authenticatedUser();
@Override this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
@Transactional
public CommandProcessingResult modifyTeller(Long tellerId, JsonCommand command) { final Teller teller = validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);
try {
final Map<String, Object> changes = teller.update(tellerOffice, command);
final Long officeId = command.longValueOfParameterNamed("officeId");
final Office tellerOffice = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId); if (!changes.isEmpty()) {
final AppUser currentUser = this.context.authenticatedUser(); this.tellerRepositoryWrapper.saveAndFlush(teller);
}
this.fromApiJsonDeserializer.validateForCreateAndUpdateTeller(command.json());
return new CommandProcessingResultBuilder() //
final Teller teller = validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId); .withCommandId(command.commandId()) //
.withEntityId(teller.getId()) //
final Map<String, Object> changes = teller.update(tellerOffice, command); .withOfficeId(teller.officeId()) //
.with(changes) //
if (!changes.isEmpty()) { .build();
this.tellerRepositoryWrapper.saveAndFlush(teller); } catch (final JpaSystemException | DataIntegrityViolationException dve) {
} handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
return CommandProcessingResult.empty();
return new CommandProcessingResultBuilder() // } catch (final PersistenceException dve) {
.withCommandId(command.commandId()) // Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
.withEntityId(teller.getId()) // handleTellerDataIntegrityIssues(command, throwable, dve);
.withOfficeId(teller.officeId()) // return CommandProcessingResult.empty();
.with(changes) // }
.build(); }
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); /*
return CommandProcessingResult.empty(); * used to restrict modifying operations to office that are either the users office or lower (child) in the office
} catch (final PersistenceException dve) { * hierarchy
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); */
handleTellerDataIntegrityIssues(command, throwable, dve); private Teller validateUserPriviledgeOnTellerAndRetrieve(final AppUser currentUser, final Long tellerId) {
return CommandProcessingResult.empty();
} final Long userOfficeId = currentUser.getOffice().getId();
} final Office userOffice = this.officeRepositoryWrapper.findOfficeHierarchy(userOfficeId);
final Teller tellerToReturn = this.tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
/* final Long tellerOfficeId = tellerToReturn.officeId();
* used to restrict modifying operations to office that are either the users office or lower (child) in the office if (userOffice.doesNotHaveAnOfficeInHierarchyWithId(tellerOfficeId)) {
* hierarchy throw new NoAuthorizationException("User does not have sufficient priviledges to act on the provided office.");
*/ }
private Teller validateUserPriviledgeOnTellerAndRetrieve(final AppUser currentUser, final Long tellerId) { return tellerToReturn;
}
final Long userOfficeId = currentUser.getOffice().getId();
final Office userOffice = this.officeRepositoryWrapper.findOfficeHierarchy(userOfficeId); @Override
final Teller tellerToReturn = this.tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId); @Transactional
final Long tellerOfficeId = tellerToReturn.officeId(); public CommandProcessingResult deleteTeller(Long tellerId) {
if (userOffice.doesNotHaveAnOfficeInHierarchyWithId(tellerOfficeId)) { // TODO Auto-generated method stub
throw new NoAuthorizationException("User does not have sufficient priviledges to act on the provided office.");
} Teller teller = tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
return tellerToReturn; Set<Cashier> isTellerIdPresentInCashier = teller.getCashiers();
}
for (final Cashier tellerIdInCashier : isTellerIdPresentInCashier) {
@Override if (tellerIdInCashier.getTeller().getId().toString().equalsIgnoreCase(tellerId.toString())) {
@Transactional throw new CashierExistForTellerException(tellerId);
public CommandProcessingResult deleteTeller(Long tellerId) { }
// TODO Auto-generated method stub
}
Teller teller = tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId); tellerRepositoryWrapper.delete(teller);
Set<Cashier> isTellerIdPresentInCashier = teller.getCashiers(); return new CommandProcessingResultBuilder() //
.withEntityId(teller.getId()) //
for (final Cashier tellerIdInCashier : isTellerIdPresentInCashier) { .build();
if (tellerIdInCashier.getTeller().getId().toString().equalsIgnoreCase(tellerId.toString())) {
throw new CashierExistForTellerException(tellerId); }
}
/*
} * Guaranteed to throw an exception no matter what the data integrity issue is.
tellerRepositoryWrapper.delete(teller); */
return new CommandProcessingResultBuilder() // private void handleTellerDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve) {
.withEntityId(teller.getId()) // if (realCause.getMessage().contains("m_tellers_name_unq")) {
.build(); final String name = command.stringValueOfParameterNamed("name");
throw new PlatformDataIntegrityException("error.msg.teller.duplicate.name", "Teller with name `" + name + "` already exists",
} "name", name);
}
/*
* Guaranteed to throw an exception no matter what the data integrity issue is. log.error("Error occured.", dve);
*/ throw ErrorHandler.getMappable(dve, "error.msg.teller.unknown.data.integrity.issue",
private void handleTellerDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve) { "Unknown data integrity issue with resource: " + realCause.getMessage());
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", @Override
"name", name); public CommandProcessingResult allocateCashierToTeller(final Long tellerId, JsonCommand command) {
} try {
this.context.authenticatedUser();
log.error("Error occured.", dve); Long hourStartTime;
throw ErrorHandler.getMappable(dve, "error.msg.teller.unknown.data.integrity.issue", Long minStartTime;
"Unknown data integrity issue with resource: " + realCause.getMessage()); Long hourEndTime;
} Long minEndTime;
String startTime = " ";
@Override String endTime = " ";
public CommandProcessingResult allocateCashierToTeller(final Long tellerId, JsonCommand command) { final Teller teller = this.tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId);
try { final Office tellerOffice = teller.getOffice();
this.context.authenticatedUser();
Long hourStartTime; final Long staffId = command.longValueOfParameterNamed("staffId");
Long minStartTime;
Long hourEndTime; this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());
Long minEndTime;
String startTime = " "; final Staff staff = this.staffRepository.findById(staffId).orElseThrow(() -> new StaffNotFoundException(staffId));
String endTime = " "; final Boolean isFullDay = command.booleanObjectValueOfParameterNamed("isFullDay");
final Teller teller = this.tellerRepositoryWrapper.findOneWithNotFoundDetection(tellerId); if (!isFullDay) {
final Office tellerOffice = teller.getOffice(); hourStartTime = command.longValueOfParameterNamed("hourStartTime");
minStartTime = command.longValueOfParameterNamed("minStartTime");
final Long staffId = command.longValueOfParameterNamed("staffId");
if (minStartTime == 0) {
this.fromApiJsonDeserializer.validateForAllocateCashier(command.json()); startTime = hourStartTime.toString() + ":" + minStartTime.toString() + "0";
} else {
final Staff staff = this.staffRepository.findById(staffId).orElseThrow(() -> new StaffNotFoundException(staffId)); startTime = hourStartTime.toString() + ":" + minStartTime.toString();
final Boolean isFullDay = command.booleanObjectValueOfParameterNamed("isFullDay"); }
if (!isFullDay) {
hourStartTime = command.longValueOfParameterNamed("hourStartTime"); hourEndTime = command.longValueOfParameterNamed("hourEndTime");
minStartTime = command.longValueOfParameterNamed("minStartTime"); minEndTime = command.longValueOfParameterNamed("minEndTime");
if (minEndTime == 0) {
if (minStartTime == 0) { endTime = hourEndTime.toString() + ":" + minEndTime.toString() + "0";
startTime = hourStartTime.toString() + ":" + minStartTime.toString() + "0"; } else {
} else { endTime = hourEndTime.toString() + ":" + minEndTime.toString();
startTime = hourStartTime.toString() + ":" + minStartTime.toString(); }
}
}
hourEndTime = command.longValueOfParameterNamed("hourEndTime"); final Cashier cashier = Cashier.fromJson(tellerOffice, teller, staff, startTime, endTime, command);
minEndTime = command.longValueOfParameterNamed("minEndTime"); this.cashierTransactionDataValidator.validateCashierAllowedDateAndTime(cashier, teller);
if (minEndTime == 0) {
endTime = hourEndTime.toString() + ":" + minEndTime.toString() + "0"; this.cashierRepository.save(cashier);
} else {
endTime = hourEndTime.toString() + ":" + minEndTime.toString(); return new CommandProcessingResultBuilder() //
} .withCommandId(command.commandId()) //
.withEntityId(teller.getId()) //
} .withSubEntityId(cashier.getId()) //
final Cashier cashier = Cashier.fromJson(tellerOffice, teller, staff, startTime, endTime, command); .build();
this.cashierTransactionDataValidator.validateCashierAllowedDateAndTime(cashier, teller); } catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
this.cashierRepository.save(cashier); return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
return new CommandProcessingResultBuilder() // Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
.withCommandId(command.commandId()) // handleTellerDataIntegrityIssues(command, throwable, dve);
.withEntityId(teller.getId()) // return CommandProcessingResult.empty();
.withSubEntityId(cashier.getId()) // }
.build(); }
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); @Override
return CommandProcessingResult.empty(); @Transactional
} catch (final PersistenceException dve) { public CommandProcessingResult updateCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); try {
handleTellerDataIntegrityIssues(command, throwable, dve); final AppUser currentUser = this.context.authenticatedUser();
return CommandProcessingResult.empty();
} this.fromApiJsonDeserializer.validateForAllocateCashier(command.json());
}
final Long staffId = command.longValueOfParameterNamed("staffId");
@Override final Staff staff = this.staffRepository.findById(staffId).orElseThrow(() -> new StaffNotFoundException(staffId));
@Transactional
public CommandProcessingResult updateCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) { final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);
try {
final AppUser currentUser = this.context.authenticatedUser(); cashier.setStaff(staff);
this.fromApiJsonDeserializer.validateForAllocateCashier(command.json()); // TODO - check if staff office and teller office match
final Long staffId = command.longValueOfParameterNamed("staffId"); final Map<String, Object> changes = cashier.update(command);
final Staff staff = this.staffRepository.findById(staffId).orElseThrow(() -> new StaffNotFoundException(staffId));
if (!changes.isEmpty()) {
final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId); this.cashierRepository.saveAndFlush(cashier);
}
cashier.setStaff(staff);
return new CommandProcessingResultBuilder() //
// TODO - check if staff office and teller office match .withCommandId(command.commandId()) //
.withEntityId(cashier.getTeller().getId()) //
final Map<String, Object> changes = cashier.update(command); .withSubEntityId(cashier.getId()) //
.with(changes) //
if (!changes.isEmpty()) { .build();
this.cashierRepository.saveAndFlush(cashier); } catch (final JpaSystemException | DataIntegrityViolationException dve) {
} handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
return CommandProcessingResult.empty();
return new CommandProcessingResultBuilder() // } catch (final PersistenceException dve) {
.withCommandId(command.commandId()) // Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
.withEntityId(cashier.getTeller().getId()) // handleTellerDataIntegrityIssues(command, throwable, dve);
.withSubEntityId(cashier.getId()) // return CommandProcessingResult.empty();
.with(changes) // }
.build(); }
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); private Cashier validateUserPriviledgeOnCashierAndRetrieve(final AppUser currentUser, final Long tellerId, final Long cashierId) {
return CommandProcessingResult.empty();
} catch (final PersistenceException dve) { validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId);
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
handleTellerDataIntegrityIssues(command, throwable, dve); return this.cashierRepository.findById(cashierId).orElse(null);
return CommandProcessingResult.empty(); }
}
} @Override
@Transactional
private Cashier validateUserPriviledgeOnCashierAndRetrieve(final AppUser currentUser, final Long tellerId, final Long cashierId) { public CommandProcessingResult deleteCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) {
try {
validateUserPriviledgeOnTellerAndRetrieve(currentUser, tellerId); final AppUser currentUser = this.context.authenticatedUser();
final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId);
return this.cashierRepository.findById(cashierId).orElse(null); this.cashierRepository.delete(cashier);
}
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
@Override handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
@Transactional return CommandProcessingResult.empty();
public CommandProcessingResult deleteCashierAllocation(Long tellerId, Long cashierId, JsonCommand command) { } catch (final PersistenceException dve) {
try { Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
final AppUser currentUser = this.context.authenticatedUser(); handleTellerDataIntegrityIssues(command, throwable, dve);
final Cashier cashier = validateUserPriviledgeOnCashierAndRetrieve(currentUser, tellerId, cashierId); return CommandProcessingResult.empty();
this.cashierRepository.delete(cashier); }
} catch (final JpaSystemException | DataIntegrityViolationException dve) { return new CommandProcessingResultBuilder() //
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); .withEntityId(cashierId) //
return CommandProcessingResult.empty(); .build();
} catch (final PersistenceException dve) { }
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
handleTellerDataIntegrityIssues(command, throwable, dve); /*
return CommandProcessingResult.empty(); * @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); }
return new CommandProcessingResultBuilder() // */
.withEntityId(cashierId) //
.build(); @Override
} public CommandProcessingResult allocateCashToCashier(final Long cashierId, JsonCommand command) {
return doTransactionForCashier(cashierId, CashierTxnType.ALLOCATE, command); // For
/* // fund
* @Override public CommandProcessingResult inwardCashToCashier (final Long cashierId, final CashierTransaction // allocation
* cashierTxn) { CashierTxnType txnType = CashierTxnType.INWARD_CASH_TXN; // pre save to generate id for use in // to
* office hierarchy this.cashierTxnRepository.save(cashierTxn); } // cashier
*/ }
@Override @Override
public CommandProcessingResult allocateCashToCashier(final Long cashierId, JsonCommand command) { public CommandProcessingResult settleCashFromCashier(final Long cashierId, JsonCommand command) {
return doTransactionForCashier(cashierId, CashierTxnType.ALLOCATE, command); // For
// fund this.cashierTransactionDataValidator.validateSettleCashAndCashOutTransactions(cashierId, command);
// allocation
// to return doTransactionForCashier(cashierId, CashierTxnType.SETTLE, command); // For
// cashier // fund
} // settlement
// from
@Override // cashier
public CommandProcessingResult settleCashFromCashier(final Long cashierId, JsonCommand command) { }
this.cashierTransactionDataValidator.validateSettleCashAndCashOutTransactions(cashierId, command); private CommandProcessingResult doTransactionForCashier(final Long cashierId, final CashierTxnType txnType, JsonCommand command) {
try {
return doTransactionForCashier(cashierId, CashierTxnType.SETTLE, command); // For final AppUser currentUser = this.context.authenticatedUser();
// fund
// settlement final Cashier cashier = this.cashierRepository.findById(cashierId).orElseThrow(() -> new CashierNotFoundException(cashierId));
// from
// cashier this.fromApiJsonDeserializer.validateForCashTxnForCashier(command.json());
}
// TODO: can we please remove this whole block?!? this is 20 lines of dead code!!!
private CommandProcessingResult doTransactionForCashier(final Long cashierId, final CashierTxnType txnType, JsonCommand command) { final String entityType = command.stringValueOfParameterNamed("entityType");
try { if (entityType != null) {
final AppUser currentUser = this.context.authenticatedUser(); if (entityType.equals("loan account")) {
// TODO : Check if loan account exists
final Cashier cashier = this.cashierRepository.findById(cashierId).orElseThrow(() -> new CashierNotFoundException(cashierId)); // LoanAccount loan = null;
// if (loan == null) { throw new
this.fromApiJsonDeserializer.validateForCashTxnForCashier(command.json()); // LoanAccountFoundException(entityId); }
} else if (entityType.equals("savings account")) {
// TODO: can we please remove this whole block?!? this is 20 lines of dead code!!! // TODO : Check if loan account exists
final String entityType = command.stringValueOfParameterNamed("entityType"); // SavingsAccount savingsaccount = null;
if (entityType != null) { // if (savingsaccount == null) { throw new
if (entityType.equals("loan account")) { // SavingsAccountNotFoundException(entityId); }
// TODO : Check if loan account exists
// LoanAccount loan = null; }
// if (loan == null) { throw new if (entityType.equals("client")) {
// LoanAccountFoundException(entityId); } // TODO: Check if client exists
} else if (entityType.equals("savings account")) { // Client client = null;
// TODO : Check if loan account exists // if (client == null) { throw new
// SavingsAccount savingsaccount = null; // ClientNotFoundException(entityId); }
// if (savingsaccount == null) { throw new } else {
// SavingsAccountNotFoundException(entityId); } // TODO : Invalid type handling
}
} }
if (entityType.equals("client")) {
// TODO: Check if client exists final CashierTransaction cashierTxn = CashierTransaction.fromJson(cashier, command);
// Client client = null; cashierTxn.setTxnType(txnType.getId());
// if (client == null) { throw new
// ClientNotFoundException(entityId); } this.cashierTxnRepository.save(cashierTxn);
} else {
// TODO : Invalid type handling // GL entries skipped for vault-to-cashier (ALLOCATE) and cashier-to-vault (SETTLE)
} // transfers. Cash is internal bank physical movement — denomination tracking is
} // handled operationally via m_cash_transfer / m_cash_settlement tables.
// GL impact happens only on customer deposit/withdrawal transactions.
final CashierTransaction cashierTxn = CashierTransaction.fromJson(cashier, command);
cashierTxn.setTxnType(txnType.getId()); return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
this.cashierTxnRepository.save(cashierTxn); .withEntityId(cashier.getId()) //
.withSubEntityId(cashierTxn.getId()) //
// Pass the journal entries .build();
FinancialActivityAccount mainVaultFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper } catch (final JpaSystemException | DataIntegrityViolationException dve) {
.findByFinancialActivityTypeWithNotFoundDetection(FinancialActivity.CASH_AT_MAINVAULT.getValue()); handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
FinancialActivityAccount tellerCashFinancialActivityAccount = this.financialActivityAccountRepositoryWrapper return CommandProcessingResult.empty();
.findByFinancialActivityTypeWithNotFoundDetection(FinancialActivity.CASH_AT_TELLER.getValue()); } catch (final PersistenceException dve) {
GLAccount creditAccount = null; Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
GLAccount debitAccount = null; handleTellerDataIntegrityIssues(command, throwable, dve);
if (txnType.equals(CashierTxnType.ALLOCATE)) { return CommandProcessingResult.empty();
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 = Long.toHexString(Long.parseLong(uniqueVal));
final JournalEntry debitJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment
// detail
debitAccount, cashierTxn.getCurrencyCode(),
transactionId, false, // manual entry
cashierTxn.getTxnDate(), JournalEntryType.DEBIT, cashierTxn.getTxnAmount(), cashierTxn.getTxnNote(), // Description
null, null, null, // entity Type, entityId, reference number
null, null, null, null); // Loan
// and
// Savings
// Txn
final JournalEntry creditJournalEntry = JournalEntry.createNew(cashierOffice, null, // payment
// detail
creditAccount, cashierTxn.getCurrencyCode(),
transactionId, false, // manual entry
cashierTxn.getTxnDate(), JournalEntryType.CREDIT, cashierTxn.getTxnAmount(), cashierTxn.getTxnNote(), // Description
null, null, null, // entity Type, entityId, reference number
null, null, null, null); // 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 JpaSystemException | DataIntegrityViolationException dve) {
handleTellerDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
handleTellerDataIntegrityIssues(command, throwable, dve);
return CommandProcessingResult.empty();
}
}
}