feat(cashier): implement expected balance for logged-in cashier, settlement checks, and reconciliation rules
This commit is contained in:
+55
@@ -157,6 +157,61 @@ public class CashReconciliationApiResource {
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /cashreconciliation/expected/me
|
||||
* Same as /expected but auto-detects cashierId from the authenticated user.
|
||||
*/
|
||||
@GET
|
||||
@Path("expected/me")
|
||||
public String getExpectedForMe(@QueryParam("asOfDate") String asOfDate) {
|
||||
context.authenticatedUser().validateHasPermissionTo("READ_CASHRECONCILIATION");
|
||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
||||
Long appUserId;
|
||||
try {
|
||||
appUserId = context.authenticatedUser().getId();
|
||||
} catch (Exception e) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.not.authenticated", "Not authenticated.");
|
||||
}
|
||||
Long cashierId;
|
||||
try {
|
||||
cashierId = jdbcTemplate.queryForObject(
|
||||
"SELECT mc.id FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id JOIN m_appuser au ON au.staff_id = ms.id WHERE au.id = ?",
|
||||
Long.class, appUserId);
|
||||
} catch (Exception e) {
|
||||
Map<String, Object> empty = new LinkedHashMap<>();
|
||||
empty.put("cashierId", null);
|
||||
empty.put("asOfDate", dateFilter);
|
||||
empty.put("expectedTotal", java.math.BigDecimal.ZERO);
|
||||
empty.put("denominations", new ArrayList<>());
|
||||
return gson.toJson(empty);
|
||||
}
|
||||
// Delegate to the existing /expected logic
|
||||
return getExpected(cashierId, dateFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /cashreconciliation/check?cashierId=X&date=yyyy-MM-dd
|
||||
* Returns whether reconciliation has been done today for a cashier.
|
||||
*/
|
||||
@GET
|
||||
@Path("check")
|
||||
public String check(@QueryParam("cashierId") Long cashierId, @QueryParam("date") String date) {
|
||||
context.authenticatedUser().validateHasPermissionTo("READ_CASHRECONCILIATION");
|
||||
if (cashierId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.cashier.required",
|
||||
"cashierId query param is required.");
|
||||
}
|
||||
final String reconDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
||||
Integer count = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_cash_reconciliation WHERE cashier_id = ? AND recon_date = CAST(? AS DATE)",
|
||||
Integer.class, cashierId, reconDate);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("cashierId", cashierId);
|
||||
result.put("date", reconDate);
|
||||
result.put("reconciled", count != null && count > 0);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Transactional
|
||||
public String create(String body) {
|
||||
|
||||
+40
@@ -11,6 +11,7 @@ import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
@@ -22,6 +23,7 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlement;
|
||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlementDetail;
|
||||
@@ -46,6 +48,7 @@ public class CashSettlementApiResource {
|
||||
private final PlatformSecurityContext context;
|
||||
private final GlDispatchService glDispatchService;
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@POST
|
||||
@@ -75,6 +78,24 @@ public class CashSettlementApiResource {
|
||||
final String currencyCode = json.has("currencyCode") && !json.get("currencyCode").isJsonNull()
|
||||
? json.get("currencyCode").getAsString() : "INR";
|
||||
|
||||
// Require reconciliation before settlement
|
||||
Integer reconCount = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_cash_reconciliation WHERE cashier_id = ? AND recon_date = CAST(? AS DATE)",
|
||||
Integer.class, fromCashierId, settlementDate.toString());
|
||||
if (reconCount == null || reconCount == 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.reconciliation.required",
|
||||
"Reconciliation must be completed before settlement for cashier " + fromCashierId + " on " + settlementDate + ". Please complete today's reconciliation first.");
|
||||
}
|
||||
|
||||
// Prevent duplicate settlement for same cashier+date
|
||||
Integer existingCount = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_cash_settlement WHERE from_cashier_id = ? AND settlement_date = CAST(? AS DATE) AND (is_reversed IS NULL OR is_reversed = false)",
|
||||
Integer.class, fromCashierId, settlementDate.toString());
|
||||
if (existingCount != null && existingCount > 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.duplicate",
|
||||
"A settlement for cashier " + fromCashierId + " on " + settlementDate + " already exists. Reverse the existing settlement first.");
|
||||
}
|
||||
|
||||
final JsonArray denominations = json.has("denominations") && json.get("denominations").isJsonArray()
|
||||
? json.getAsJsonArray("denominations") : new JsonArray();
|
||||
|
||||
@@ -168,6 +189,25 @@ public class CashSettlementApiResource {
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("check")
|
||||
public String check(@QueryParam("cashierId") Long cashierId, @QueryParam("date") String date) {
|
||||
context.authenticatedUser().validateHasPermissionTo("READ_CASHSETTLEMENT");
|
||||
if (cashierId == null) {
|
||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.cashier.required",
|
||||
"cashierId query param is required.");
|
||||
}
|
||||
final String settlementDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
||||
Integer count = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM m_cash_settlement WHERE from_cashier_id = ? AND settlement_date = CAST(? AS DATE) AND (is_reversed IS NULL OR is_reversed = false)",
|
||||
Integer.class, cashierId, settlementDate);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("cashierId", cashierId);
|
||||
result.put("date", settlementDate);
|
||||
result.put("alreadySettled", count != null && count > 0);
|
||||
return gson.toJson(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
public String getAll() {
|
||||
List<CashSettlement> list = repository.findAllByOrderByCreatedAtDesc();
|
||||
|
||||
Reference in New Issue
Block a user