feat(cashier): GL entries endpoint + remove vault-cashier GL postings

- GET /cashreports/gl-entries: returns voucher_no, ac_no, ac_name
  (customer name), dr (withdrawal), cr (deposit) for cashier transactions
  with optional cashierId/tellerId filter
- TellerWritePlatformServiceJpaImpl: removed ALLOCATE/SETTLE GL entries
- OrganisationTellerConfiguration: removed obsolete GL constructor args

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-02 10:47:22 +05:30
co-authored by Claude Sonnet 4.6
parent 6bdcac7b77
commit 7c4cf6260c
2 changed files with 144 additions and 71 deletions
@@ -1,71 +1,67 @@
/** /**
* 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.starter; package org.apache.fineract.organisation.teller.starter;
import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper; import org.apache.fineract.infrastructure.core.service.PaginationHelper;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.PaginationHelper; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
import org.apache.fineract.infrastructure.security.service.SqlValidator; import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; import org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper; import org.apache.fineract.organisation.staff.domain.StaffRepository;
import org.apache.fineract.organisation.office.service.OfficeReadPlatformService; import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
import org.apache.fineract.organisation.staff.domain.StaffRepository; import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator;
import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; import org.apache.fineract.organisation.teller.domain.CashierRepository;
import org.apache.fineract.organisation.teller.data.CashierTransactionDataValidator; import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository;
import org.apache.fineract.organisation.teller.domain.CashierRepository; import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
import org.apache.fineract.organisation.teller.domain.CashierTransactionRepository; import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper; import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformService;
import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer; import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformServiceImpl;
import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformService; import org.apache.fineract.organisation.teller.service.TellerWritePlatformService;
import org.apache.fineract.organisation.teller.service.TellerManagementReadPlatformServiceImpl; import org.apache.fineract.organisation.teller.service.TellerWritePlatformServiceJpaImpl;
import org.apache.fineract.organisation.teller.service.TellerWritePlatformService; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.apache.fineract.organisation.teller.service.TellerWritePlatformServiceJpaImpl; import org.springframework.context.annotation.Bean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate; @Configuration
public class OrganisationTellerConfiguration {
@Configuration
public class OrganisationTellerConfiguration { @Bean
@ConditionalOnMissingBean(TellerManagementReadPlatformService.class)
@Bean public TellerManagementReadPlatformService tellerManagementReadPlatformService(JdbcTemplate jdbcTemplate,
@ConditionalOnMissingBean(TellerManagementReadPlatformService.class) PlatformSecurityContext context, OfficeReadPlatformService officeReadPlatformService,
public TellerManagementReadPlatformService tellerManagementReadPlatformService(JdbcTemplate jdbcTemplate, StaffReadPlatformService staffReadPlatformService, CurrencyReadPlatformService currencyReadPlatformService,
PlatformSecurityContext context, OfficeReadPlatformService officeReadPlatformService, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper, SqlValidator sqlValidator) {
StaffReadPlatformService staffReadPlatformService, CurrencyReadPlatformService currencyReadPlatformService, return new TellerManagementReadPlatformServiceImpl(jdbcTemplate, context, officeReadPlatformService, staffReadPlatformService,
DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper, SqlValidator sqlValidator) { currencyReadPlatformService, sqlGenerator, paginationHelper, sqlValidator);
return new TellerManagementReadPlatformServiceImpl(jdbcTemplate, context, officeReadPlatformService, staffReadPlatformService, }
currencyReadPlatformService, sqlGenerator, paginationHelper, sqlValidator);
} @Bean
@ConditionalOnMissingBean(TellerWritePlatformService.class)
@Bean public TellerWritePlatformService tellerWritePlatformService(PlatformSecurityContext context,
@ConditionalOnMissingBean(TellerWritePlatformService.class) TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer, TellerRepositoryWrapper tellerRepositoryWrapper,
public TellerWritePlatformService tellerWritePlatformService(PlatformSecurityContext context, OfficeRepositoryWrapper officeRepositoryWrapper, StaffRepository staffRepository, CashierRepository cashierRepository,
TellerCommandFromApiJsonDeserializer fromApiJsonDeserializer, TellerRepositoryWrapper tellerRepositoryWrapper, CashierTransactionRepository cashierTxnRepository,
OfficeRepositoryWrapper officeRepositoryWrapper, StaffRepository staffRepository, CashierRepository cashierRepository, CashierTransactionDataValidator cashierTransactionDataValidator) {
CashierTransactionRepository cashierTxnRepository, JournalEntryRepository glJournalEntryRepository, return new TellerWritePlatformServiceJpaImpl(context, fromApiJsonDeserializer, tellerRepositoryWrapper, officeRepositoryWrapper,
FinancialActivityAccountRepositoryWrapper financialActivityAccountRepositoryWrapper, staffRepository, cashierRepository, cashierTxnRepository, cashierTransactionDataValidator);
CashierTransactionDataValidator cashierTransactionDataValidator) { }
return new TellerWritePlatformServiceJpaImpl(context, fromApiJsonDeserializer, tellerRepositoryWrapper, officeRepositoryWrapper, }
staffRepository, cashierRepository, cashierTxnRepository, glJournalEntryRepository,
financialActivityAccountRepositoryWrapper, cashierTransactionDataValidator);
}
}
@@ -518,4 +518,81 @@ public class CashReportApiResource {
entry[2] += count * sign; entry[2] += count * sign;
} }
} }
/**
* GET /cashreports/gl-entries?date=yyyy-MM-dd&cashierId=X&tellerId=Y
* Returns cashier transaction summary: voucher no, account no,
* account holder name, DR (withdrawal), CR (deposit).
* - cashierId: filter by specific cashier
* - tellerId: filter by all cashiers under a teller
* - neither: all cashier transactions for the date
*/
@GET
@Path("gl-entries")
public String glEntries(@QueryParam("date") String date,
@QueryParam("cashierId") Long cashierId,
@QueryParam("tellerId") Long tellerId) {
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
// Build cashier officer_id filter
String officerFilter;
Object[] params;
if (cashierId != null) {
officerFilter = "AND std.officer_id IN ("
+ "SELECT au.id FROM m_appuser au "
+ "JOIN m_cashiers mc ON mc.staff_id = au.staff_id WHERE mc.id = ?)";
params = new Object[] { txnDate, cashierId };
} else if (tellerId != null) {
officerFilter = "AND std.officer_id IN ("
+ "SELECT au.id FROM m_appuser au "
+ "JOIN m_cashiers mc ON mc.staff_id = au.staff_id "
+ "JOIN m_tellers mt ON mt.id = mc.teller_id WHERE mt.id = ?)";
params = new Object[] { txnDate, tellerId };
} else {
officerFilter = "AND std.officer_id IN ("
+ "SELECT au.id FROM m_appuser au "
+ "JOIN m_cashiers mc ON mc.staff_id = au.staff_id)";
params = new Object[] { txnDate };
}
// One row per transaction — account no, account holder name, dr/cr amount
final String sql = "SELECT "
+ " std.txn_id AS voucher_no, "
+ " std.account_no AS ac_no, "
+ " COALESCE(mc.display_name, '') AS ac_name, "
+ " SUM(CASE WHEN std.txn_type = 'WITHDRAWAL' THEN std.total_amount ELSE 0 END) AS dr_amount, "
+ " SUM(CASE WHEN std.txn_type = 'DEPOSIT' THEN std.total_amount ELSE 0 END) AS cr_amount "
+ "FROM m_savings_txn_denomination std "
+ "LEFT JOIN m_client mc ON mc.id = std.client_id "
+ "WHERE std.txn_date = CAST(? AS DATE) "
+ officerFilter
+ " GROUP BY std.txn_id, std.account_no, mc.display_name "
+ "ORDER BY std.txn_id";
List<Map<String, Object>> rows = jdbcTemplate.query(sql, (rs, rowNum) -> {
Map<String, Object> row = new LinkedHashMap<>();
row.put("voucherNo", rs.getLong("voucher_no"));
row.put("acNo", rs.getString("ac_no"));
row.put("acName", rs.getString("ac_name"));
row.put("dr", rs.getBigDecimal("dr_amount"));
row.put("cr", rs.getBigDecimal("cr_amount"));
return row;
}, params);
java.math.BigDecimal totalDr = rows.stream()
.map(r -> (java.math.BigDecimal) r.get("dr"))
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
java.math.BigDecimal totalCr = rows.stream()
.map(r -> (java.math.BigDecimal) r.get("cr"))
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
Map<String, Object> result = new LinkedHashMap<>();
result.put("date", txnDate);
result.put("rows", rows);
result.put("totalDr", totalDr);
result.put("totalCr", totalCr);
return gson.toJson(result);
}
} }