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);
|
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
|
@POST
|
||||||
@Transactional
|
@Transactional
|
||||||
public String create(String body) {
|
public String create(String body) {
|
||||||
|
|||||||
+40
@@ -11,6 +11,7 @@ import jakarta.ws.rs.POST;
|
|||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.PathParam;
|
import jakarta.ws.rs.PathParam;
|
||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -22,6 +23,7 @@ import java.util.Map;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
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.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlement;
|
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlement;
|
||||||
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlementDetail;
|
import org.apache.fineract.portfolio.cashsettlement.domain.CashSettlementDetail;
|
||||||
@@ -46,6 +48,7 @@ public class CashSettlementApiResource {
|
|||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final GlDispatchService glDispatchService;
|
private final GlDispatchService glDispatchService;
|
||||||
private final PlatformTransactionManager transactionManager;
|
private final PlatformTransactionManager transactionManager;
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@@ -75,6 +78,24 @@ public class CashSettlementApiResource {
|
|||||||
final String currencyCode = json.has("currencyCode") && !json.get("currencyCode").isJsonNull()
|
final String currencyCode = json.has("currencyCode") && !json.get("currencyCode").isJsonNull()
|
||||||
? json.get("currencyCode").getAsString() : "INR";
|
? 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()
|
final JsonArray denominations = json.has("denominations") && json.get("denominations").isJsonArray()
|
||||||
? json.getAsJsonArray("denominations") : new JsonArray();
|
? json.getAsJsonArray("denominations") : new JsonArray();
|
||||||
|
|
||||||
@@ -168,6 +189,25 @@ public class CashSettlementApiResource {
|
|||||||
return gson.toJson(result);
|
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
|
@GET
|
||||||
public String getAll() {
|
public String getAll() {
|
||||||
List<CashSettlement> list = repository.findAllByOrderByCreatedAtDesc();
|
List<CashSettlement> list = repository.findAllByOrderByCreatedAtDesc();
|
||||||
|
|||||||
Reference in New Issue
Block a user