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'); + + + +