diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 398218dec..faf8cb184 100644
--- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -207,6 +207,14 @@ public class CommandWrapperBuilder {
return this;
}
+ public CommandWrapperBuilder returnChequeBook(final Long chequeBookId) {
+ this.actionName = "RETURN";
+ this.entityName = "CHEQUEBOOK";
+ this.entityId = chequeBookId;
+ this.href = "/chequebooks/" + chequeBookId;
+ return this;
+ }
+
public CommandWrapperBuilder approveChequeBookPendingRequest(final Long pendingRequestId) {
this.actionName = "APPROVE";
this.entityName = "CHEQUEBOOKPENDINGREQUEST";
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/api/ChequeBookApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/api/ChequeBookApiResource.java
index bffbafa9e..74936ae88 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/api/ChequeBookApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/api/ChequeBookApiResource.java
@@ -99,10 +99,10 @@ public class ChequeBookApiResource {
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Run a command on a cheque book", description = "Supported commands: issue, reject, withdraw, "
- + "stoppayment, revokestoppayment, reportlost, cancelleaf, markused.")
+ + "stoppayment, revokestoppayment, reportlost, cancelleaf, markused, return.")
public CommandProcessingResult handleCommand(@PathParam("id") @Parameter(description = "id") final Long id,
@QueryParam("command") @Parameter(description = "issue | reject | withdraw | stoppayment | revokestoppayment | "
- + "reportlost | cancelleaf | markused") final String commandParam,
+ + "reportlost | cancelleaf | markused | return") final String commandParam,
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
final CommandWrapper request;
@@ -122,6 +122,8 @@ public class ChequeBookApiResource {
request = builder.cancelLeafChequeBook(id).build();
} else if ("markused".equalsIgnoreCase(commandParam)) {
request = builder.markUsedChequeBook(id).build();
+ } else if ("return".equalsIgnoreCase(commandParam)) {
+ request = builder.returnChequeBook(id).build();
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/data/ChequeLeafData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/data/ChequeLeafData.java
index bd3e23b7d..510f8cd4d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/data/ChequeLeafData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/data/ChequeLeafData.java
@@ -52,6 +52,9 @@ public class ChequeLeafData {
private final String usedReason;
private final Long usedBy;
private final OffsetDateTime usedOn;
+ private final String returnReason;
+ private final Long returnedBy;
+ private final OffsetDateTime returnedOn;
private final String accountType;
private final Long accountId;
private final String accountNo;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeLeaf.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeLeaf.java
index 16d8d6f7d..83f220980 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeLeaf.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeLeaf.java
@@ -40,10 +40,11 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
*
*
* Legal transitions: (created)->ACTIVE (issued) or (created, no book)->LOST (unissued theft report);
- * ACTIVE<->STOP_PAYMENT (the one round-trip); ACTIVE->CANCELLED / LOST / WITHDRAWN / USED (all terminal).
- * A recovered "lost" cheque conventionally stays hotlisted rather than silently reactivating, since the
- * customer has typically already been issued a replacement book. USED is manually recorded by a branch,
- * never automatic — see {@link #markUsed(String, Long)}.
+ * ACTIVE<->STOP_PAYMENT (the one round-trip); ACTIVE->CANCELLED / LOST / WITHDRAWN / USED / RETURNED (all
+ * terminal). A recovered "lost" cheque conventionally stays hotlisted rather than silently reactivating,
+ * since the customer has typically already been issued a replacement book; a RETURNED (dishonoured) leaf
+ * likewise stays flagged rather than reverting to ACTIVE. USED is manually recorded by a branch, never
+ * automatic — see {@link #markUsed(String, Long)}.
*/
@Entity
@Table(name = "m_cheque_leaf")
@@ -105,6 +106,16 @@ public class ChequeLeaf extends AbstractPersistableCustom {
@Column(name = "used_on")
private OffsetDateTime usedOn;
+ /** Set when a presented leaf is dishonoured/bounced (insufficient funds, signature mismatch, etc.). */
+ @Column(name = "return_reason", length = 500)
+ private String returnReason;
+
+ @Column(name = "returned_by")
+ private Long returnedBy;
+
+ @Column(name = "returned_on")
+ private OffsetDateTime returnedOn;
+
@Column(name = "created_on")
private OffsetDateTime createdOn;
@@ -191,4 +202,13 @@ public class ChequeLeaf extends AbstractPersistableCustom {
this.usedBy = markedBy;
this.usedOn = DateUtils.getOffsetDateTimeOfTenant();
}
+
+ /** The leaf was presented for payment and dishonoured/bounced. Terminal — no revoke, matching LOST/CANCELLED. */
+ public void returned(final String reason, final Long actorId) {
+ assertActive();
+ this.status = ChequeLeafStatus.RETURNED.name();
+ this.returnReason = reason;
+ this.returnedBy = actorId;
+ this.returnedOn = DateUtils.getOffsetDateTimeOfTenant();
+ }
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeLeafStatus.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeLeafStatus.java
index 4229dec45..2bac5b42f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeLeafStatus.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/domain/ChequeLeafStatus.java
@@ -23,9 +23,11 @@ package org.apache.fineract.portfolio.chequebook.domain;
* stolen); ACTIVE<->STOP_PAYMENT (the one round-trip, via stop-payment/revoke); ACTIVE->CANCELLED
* (spoiled leaf voided); ACTIVE->LOST (reported lost/stolen post-issuance); ACTIVE->WITHDRAWN (account
* closed); ACTIVE->USED (manually recorded once the branch sees the leaf clear on the bank/clearing
- * statement — this CBS has no clearing/presentment integration, so USED can never be set automatically).
- * LOST, CANCELLED, WITHDRAWN and USED are all terminal — a recovered "lost" cheque conventionally stays
- * hotlisted since the customer has typically already been issued a replacement book.
+ * statement — this CBS has no clearing/presentment integration, so USED can never be set automatically);
+ * ACTIVE->RETURNED (presented and dishonoured/bounced — insufficient funds, signature mismatch, etc.).
+ * LOST, CANCELLED, WITHDRAWN, USED and RETURNED are all terminal — a recovered "lost" cheque conventionally
+ * stays hotlisted since the customer has typically already been issued a replacement book, and a returned
+ * leaf stays flagged rather than silently reverting to ACTIVE.
*/
public enum ChequeLeafStatus {
@@ -34,5 +36,6 @@ public enum ChequeLeafStatus {
CANCELLED, //
LOST, //
WITHDRAWN, //
- USED; //
+ USED, //
+ RETURNED; //
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/handler/ReturnChequeBookCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/handler/ReturnChequeBookCommandHandler.java
new file mode 100644
index 000000000..0f22e87f0
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/handler/ReturnChequeBookCommandHandler.java
@@ -0,0 +1,42 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.chequebook.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.chequebook.service.ChequeBookWritePlatformService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "CHEQUEBOOK", action = "RETURN")
+@RequiredArgsConstructor
+public class ReturnChequeBookCommandHandler implements NewCommandSourceHandler {
+
+ private final ChequeBookWritePlatformService writePlatformService;
+
+ @Transactional
+ @Override
+ public CommandProcessingResult processCommand(final JsonCommand command) {
+ return this.writePlatformService.returnChequeBook(command.entityId(), command);
+ }
+}
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 630a7694b..1827f9a00 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
@@ -296,6 +296,7 @@ public class ChequeBookReadPlatformServiceImpl implements ChequeBookReadPlatform
.lostReason(leaf.getLostReason()).lostReportedBy(leaf.getLostReportedBy()).lostReportedOn(leaf.getLostReportedOn())
.cancelledReason(leaf.getCancelledReason()).cancelledBy(leaf.getCancelledBy()).cancelledOn(leaf.getCancelledOn())
.usedReason(leaf.getUsedReason()).usedBy(leaf.getUsedBy()).usedOn(leaf.getUsedOn())
+ .returnReason(leaf.getReturnReason()).returnedBy(leaf.getReturnedBy()).returnedOn(leaf.getReturnedOn())
.accountType(leaf.getAccountType()).accountId(leaf.getAccountId()).accountNo(display.accountNo)
.clientName(display.clientName).officeName(leaf.getOfficeName()).build();
}
@@ -448,6 +449,7 @@ public class ChequeBookReadPlatformServiceImpl implements ChequeBookReadPlatform
+ "f.lost_reported_on as lost_reported_on, f.cancelled_reason as cancelled_reason, "
+ "f.cancelled_by as cancelled_by, f.cancelled_on as cancelled_on, "
+ "f.used_reason as used_reason, f.used_by as used_by, f.used_on as used_on, "
+ + "f.return_reason as return_reason, f.returned_by as returned_by, f.returned_on as returned_on, "
+ "b.account_type as account_type, b.account_id as account_id, bo.name as book_office_name, "
+ "lo.name as lot_office_name "
+ "from m_cheque_leaf f left join m_cheque_book b on b.id = f.cheque_book_id left join m_office bo on bo.id = b.office_id "
@@ -469,6 +471,8 @@ public class ChequeBookReadPlatformServiceImpl implements ChequeBookReadPlatform
.cancelledReason(rs.getString("cancelled_reason")).cancelledBy((Long) rs.getObject("cancelled_by"))
.cancelledOn(rs.getObject("cancelled_on", OffsetDateTime.class)).usedReason(rs.getString("used_reason"))
.usedBy((Long) rs.getObject("used_by")).usedOn(rs.getObject("used_on", OffsetDateTime.class))
+ .returnReason(rs.getString("return_reason")).returnedBy((Long) rs.getObject("returned_by"))
+ .returnedOn(rs.getObject("returned_on", OffsetDateTime.class))
.accountType(rs.getString("account_type")).accountId((Long) rs.getObject("account_id")).accountNo(null)
.clientName(null).officeName(officeName).build();
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookWritePlatformService.java
index 510c3e73b..627ad0a3c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookWritePlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/chequebook/service/ChequeBookWritePlatformService.java
@@ -61,6 +61,9 @@ public interface ChequeBookWritePlatformService {
CommandProcessingResult cancelLeaf(Long chequeBookId, JsonCommand command);
+ /** Flags leaves in range as RETURNED — presented and dishonoured/bounced. Terminal, like reportlost. */
+ CommandProcessingResult returnChequeBook(Long chequeBookId, JsonCommand command);
+
/**
* Manually records that leaves cleared, once a branch sees them on the bank/clearing statement — never
* automatic, since this CBS has no clearing/presentment integration to detect it on its own.
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 17edfd73a..7bbdca631 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
@@ -541,6 +541,21 @@ public class ChequeBookWritePlatformServiceImpl implements ChequeBookWritePlatfo
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(chequeBookId).build();
}
+ @Transactional
+ @Override
+ public CommandProcessingResult returnChequeBook(final Long chequeBookId, final JsonCommand command) {
+ this.validator.validateForBookLeafAction(command.json(), true);
+ final ChequeBook book = this.chequeBookRepositoryWrapper.findOneWithNotFoundDetection(chequeBookId);
+ final String reason = command.stringValueOfParameterNamed(ChequeBookApiJsonInputParams.REASON.getValue());
+ final Long actorId = this.context.authenticatedUser().getId();
+ final List leaves = resolveLeavesInRange(book, command);
+ for (final ChequeLeaf leaf : leaves) {
+ leaf.returned(reason, actorId);
+ }
+ this.chequeLeafRepository.saveAll(leaves);
+ return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(chequeBookId).build();
+ }
+
@Transactional
@Override
public CommandProcessingResult markLeafUsed(final Long chequeBookId, final JsonCommand command) {
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 5ee01a0e0..83cb412f8 100644
--- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -408,4 +408,5 @@
+
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0388_chequeleaf_returned_status.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0388_chequeleaf_returned_status.xml
new file mode 100644
index 000000000..641dc4294
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0388_chequeleaf_returned_status.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
+ SELECT 'portfolio', 'RETURN_CHEQUEBOOK', 'CHEQUEBOOK', 'RETURN', true
+ WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'RETURN_CHEQUEBOOK');
+
+
+
+