diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeStockLotRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeStockLotRepository.java index 0c6676ed6..2f57d49e5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeStockLotRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeStockLotRepository.java @@ -28,12 +28,14 @@ public interface ChequeStockLotRepository extends JpaRepository findBySeriesIdAndOfficeIdAndStatusOrderByIdAsc(Long seriesId, Long officeId, String status); /** - * Anti-duplication control: a newly received range must not overlap any existing lot's range within the SAME - * series. Each series is its own stationery batch from the press (different leaf count/paper stock) and is - * numbered independently — two different series legitimately reusing the same numeric range is normal in - * real-world cheque stock, matching how the press assigns a fresh serial block per print order. + * Anti-duplication control: a newly received range must not overlap any existing lot's range, REGARDLESS of + * series. A real bank's leaf-serial numbers are allocated from one bank-wide numbering authority — two + * different cheque designs (series) never legitimately reuse the same numeric range, because whichever + * account eventually receives books from both would end up holding two leaves with the identical serial + * (a collision NpciInwardPushService's drawer-account-number disambiguation can't resolve when both leaves + * belong to the same account). This used to be scoped to "within the same series only", which is exactly + * what let that collision happen in the first place. */ - @Query("SELECT COUNT(l) FROM ChequeStockLot l WHERE l.series.id = :seriesId AND l.rangeStart <= :rangeEnd AND l.rangeEnd >= :rangeStart") - long countOverlappingInSeries(@Param("seriesId") long seriesId, @Param("rangeStart") long rangeStart, - @Param("rangeEnd") long rangeEnd); + @Query("SELECT COUNT(l) FROM ChequeStockLot l WHERE l.rangeStart <= :rangeEnd AND l.rangeEnd >= :rangeStart") + long countOverlappingAnySeries(@Param("rangeStart") long rangeStart, @Param("rangeEnd") long rangeEnd); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookReadPlatformServiceImpl.java index 1827f9a00..9bebce03b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookReadPlatformServiceImpl.java @@ -223,8 +223,10 @@ public class ChequeBookReadPlatformServiceImpl implements ChequeBookReadPlatform @Override public List retrieveLeafBySerialNo(final Long leafSerialNo) { - // a bare serial number is only unique WITHIN a series (see ChequeStockLotRepository.countOverlappingInSeries) — - // two different series can legitimately share a number, so this can return more than one match + // new stock lots are now checked for overlap across every series (see + // ChequeStockLotRepository.countOverlappingAnySeries), so a bare serial number stays globally unique going + // forward. Older lots received before that check existed can still collide, so this stays defensive and + // may return more than one match for pre-existing data. final String sql = "select " + this.leafMapper.schema() + " where f.leaf_serial_no = ? order by s.series_code asc"; final List leaves = this.jdbcTemplate.query(sql, this.leafMapper, leafSerialNo); if (leaves.isEmpty()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookWritePlatformServiceImpl.java index e7e9e0b6a..2e0deae3e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookWritePlatformServiceImpl.java @@ -185,13 +185,14 @@ public class ChequeBookWritePlatformServiceImpl implements ChequeBookWritePlatfo @Override public CommandProcessingResult receiveStock(final JsonCommand command) { this.validator.validateForStockLotReceive(command.json()); - final Long seriesId = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.SERIES_ID.getValue()); final Long rangeStart = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_START.getValue()); final Long rangeEnd = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_END.getValue()); // fail fast for the maker; re-checked authoritatively at approval time in case stock changed meanwhile - if (this.chequeStockLotRepository.countOverlappingInSeries(seriesId, rangeStart, rangeEnd) > 0) { + if (this.chequeStockLotRepository.countOverlappingAnySeries(rangeStart, rangeEnd) > 0) { throw new GeneralPlatformDomainRuleException("error.msg.chequestocklot.range.overlap", - "The given leaf-serial range overlaps with an existing stock lot for this series.", rangeStart); + "The given leaf-serial range overlaps with an existing stock lot (checked across every series, " + + "not just this one) — pick a different range.", + rangeStart); } final Long pendingId = submitPendingRequest(ChequeBookPendingRequestType.RECEIVE_STOCK, command); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(pendingId).build(); @@ -208,9 +209,11 @@ public class ChequeBookWritePlatformServiceImpl implements ChequeBookWritePlatfo final Long rangeEnd = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_END.getValue()); final String batchRef = command.stringValueOfParameterNamedAllowingNull(ChequeBookApiJsonInputParams.BATCH_REF.getValue()); - if (this.chequeStockLotRepository.countOverlappingInSeries(seriesId, rangeStart, rangeEnd) > 0) { + if (this.chequeStockLotRepository.countOverlappingAnySeries(rangeStart, rangeEnd) > 0) { throw new GeneralPlatformDomainRuleException("error.msg.chequestocklot.range.overlap", - "The given leaf-serial range overlaps with an existing stock lot for this series.", rangeStart); + "The given leaf-serial range overlaps with an existing stock lot (checked across every series, " + + "not just this one) — pick a different range.", + rangeStart); } final ChequeSeries series = this.chequeSeriesRepositoryWrapper.findOneWithNotFoundDetection(seriesId);