Merge remote-tracking branch 'origin/uate' into dev
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
This commit is contained in:
+7
-4
@@ -60,6 +60,8 @@ public interface ShareAccountApiConstants {
|
||||
|
||||
String externalid_paramname = "externalId";
|
||||
|
||||
String resolutionnumber_paramname = "resolutionNumber";
|
||||
|
||||
String currency_paramname = "currencyCode";
|
||||
|
||||
String digitsafterdecimal_paramname = "digitsAfterDecimal";
|
||||
@@ -102,9 +104,10 @@ public interface ShareAccountApiConstants {
|
||||
|
||||
Set<String> supportedParameters = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(locale_paramname, dateformat_paramname,
|
||||
id_paramname, clientid_paramname, productid_paramname, submitteddate_paramname, approveddate_paramname, externalid_paramname,
|
||||
currency_paramname, digitsafterdecimal_paramname, inmultiplesof_paramname, requestedshares_paramname,
|
||||
savingsaccountid_paramname, lockinperiod_paramname, lockperiodfrequencytype_paramname, minimumactiveperiod_paramname,
|
||||
minimumactiveperiodfrequencytype_paramname, allowdividendcalculationforinactiveclients_paramname, charges_paramname,
|
||||
applicationdate_param, purchaseddate_paramname, numberofshares_paramname, purchasedprice_paramname)));
|
||||
resolutionnumber_paramname, currency_paramname, digitsafterdecimal_paramname, inmultiplesof_paramname,
|
||||
requestedshares_paramname, savingsaccountid_paramname, lockinperiod_paramname, lockperiodfrequencytype_paramname,
|
||||
minimumactiveperiod_paramname, minimumactiveperiodfrequencytype_paramname,
|
||||
allowdividendcalculationforinactiveclients_paramname, charges_paramname, applicationdate_param, purchaseddate_paramname,
|
||||
numberofshares_paramname, purchasedprice_paramname)));
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ public class AccountRequest implements Serializable {
|
||||
private Long numberOfShares;
|
||||
private String allowDividendCalculationForInactiveClients;
|
||||
private String externalId;
|
||||
private String resolutionNumber;
|
||||
private String minimumActivePeriodFrequencyType;
|
||||
private Long savingsAccountId;
|
||||
private String locale;
|
||||
|
||||
+86
-6
@@ -92,10 +92,12 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimel
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountSubStatusEnum;
|
||||
import org.apache.fineract.portfolio.savings.exception.DepositAccountNotFoundException;
|
||||
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
@@ -847,6 +849,13 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
selectFieldsSqlBuilder.append("sa.senior_citizen_interest_rate as seniorCitizenInterestRate, ");
|
||||
selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, ");
|
||||
selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,");
|
||||
selectFieldsSqlBuilder.append("sa.sub_status_enum as subStatusEnum, ");
|
||||
selectFieldsSqlBuilder.append("sa.reason_for_block as reasonForBlock, ");
|
||||
selectFieldsSqlBuilder.append("(select COALESCE(max(sat.transaction_date),sa.activatedon_date) ");
|
||||
selectFieldsSqlBuilder.append("from m_savings_account_transaction as sat ");
|
||||
selectFieldsSqlBuilder.append("where sat.is_reversed = false and sat.is_reversal = false ");
|
||||
selectFieldsSqlBuilder.append("and sat.transaction_type_enum in (1,2) ");
|
||||
selectFieldsSqlBuilder.append("and sat.savings_account_id = sa.id) as lastActiveTransactionDate, ");
|
||||
selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName ");
|
||||
|
||||
this.selectFieldsSql = selectFieldsSqlBuilder.toString();
|
||||
@@ -1129,12 +1138,48 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
|
||||
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
|
||||
depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, transferToSavingsId);
|
||||
|
||||
// Live per-account dormancy state: subStatus + the actual countdown, mirroring
|
||||
// SavingsAccountReadPlatformServiceImpl.SavingAccountMapper. The isDormancyTrackingActive/
|
||||
// daysToInactive/daysToDormancy/daysToEscheat columns read below are the product's
|
||||
// configured thresholds; they're used here only as inputs to compute the live countdown.
|
||||
final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum");
|
||||
final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum);
|
||||
final String reasonForBlock = rs.getString("reasonForBlock");
|
||||
final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate");
|
||||
final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive");
|
||||
final Integer numDaysToInactive = JdbcSupport.getInteger(rs, "daysToInactive");
|
||||
final Integer numDaysToDormancy = JdbcSupport.getInteger(rs, "daysToDormancy");
|
||||
final Integer numDaysToEscheat = JdbcSupport.getInteger(rs, "daysToEscheat");
|
||||
Integer computedDaysToInactive = null;
|
||||
Integer computedDaysToDormancy = null;
|
||||
Integer computedDaysToEscheat = null;
|
||||
final Integer accountStatusEnum = JdbcSupport.getInteger(rs, "statusEnum");
|
||||
if (isDormancyTrackingActive && lastActiveTransactionDate != null && accountStatusEnum != null
|
||||
&& accountStatusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) {
|
||||
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) {
|
||||
computedDaysToEscheat = DateUtils.getExactDifferenceInDays(currentDate,
|
||||
lastActiveTransactionDate.plusDays(numDaysToEscheat));
|
||||
}
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) {
|
||||
computedDaysToDormancy = DateUtils.getExactDifferenceInDays(currentDate,
|
||||
lastActiveTransactionDate.plusDays(numDaysToDormancy));
|
||||
}
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) {
|
||||
computedDaysToInactive = DateUtils.getExactDifferenceInDays(currentDate,
|
||||
lastActiveTransactionDate.plusDays(numDaysToInactive));
|
||||
}
|
||||
}
|
||||
|
||||
result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"),
|
||||
JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"),
|
||||
JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"),
|
||||
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"),
|
||||
JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"),
|
||||
JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"),
|
||||
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), isDormancyTrackingActive,
|
||||
computedDaysToInactive == null ? null : computedDaysToInactive.longValue(),
|
||||
computedDaysToDormancy == null ? null : computedDaysToDormancy.longValue(),
|
||||
computedDaysToEscheat == null ? null : computedDaysToEscheat.longValue(),
|
||||
JdbcSupport.getInteger(rs, "interestCalculateSelector"),
|
||||
rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"),
|
||||
JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"),
|
||||
rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"),
|
||||
@@ -1144,6 +1189,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"),
|
||||
rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"),
|
||||
rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate"));
|
||||
result.setDormancyStatus(subStatus, reasonForBlock, lastActiveTransactionDate);
|
||||
result.setCbsAccountFields(rs.getString("accountStatus"), rs.getString("modeOfOperation"),
|
||||
JdbcSupport.getLong(rs, "introducerClientId"), rs.getString("introducerName"),
|
||||
rs.getString("introducerCifNumber"), rs.getString("specialCategory"), rs.getString("weakerSection"),
|
||||
@@ -1293,12 +1339,45 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
onAccountClosureType, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit,
|
||||
allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited);
|
||||
|
||||
// Live per-account dormancy state: subStatus + the actual countdown, mirroring
|
||||
// SavingsAccountReadPlatformServiceImpl.SavingAccountMapper. The isDormancyTrackingActive/
|
||||
// daysToInactive/daysToDormancy/daysToEscheat columns read below are the product's
|
||||
// configured thresholds; they're used here only as inputs to compute the live countdown.
|
||||
final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum");
|
||||
final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum);
|
||||
final String reasonForBlock = rs.getString("reasonForBlock");
|
||||
final LocalDate lastActiveTransactionDate = JdbcSupport.getLocalDate(rs, "lastActiveTransactionDate");
|
||||
final boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive");
|
||||
final Integer numDaysToInactive = JdbcSupport.getInteger(rs, "daysToInactive");
|
||||
final Integer numDaysToDormancy = JdbcSupport.getInteger(rs, "daysToDormancy");
|
||||
final Integer numDaysToEscheat = JdbcSupport.getInteger(rs, "daysToEscheat");
|
||||
Integer computedDaysToInactive = null;
|
||||
Integer computedDaysToDormancy = null;
|
||||
Integer computedDaysToEscheat = null;
|
||||
final Integer accountStatusEnum = JdbcSupport.getInteger(rs, "statusEnum");
|
||||
if (isDormancyTrackingActive && lastActiveTransactionDate != null && accountStatusEnum != null
|
||||
&& accountStatusEnum.equals(SavingsAccountStatusType.ACTIVE.getValue())) {
|
||||
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.ESCHEAT.getValue()) {
|
||||
computedDaysToEscheat = DateUtils.getExactDifferenceInDays(currentDate,
|
||||
lastActiveTransactionDate.plusDays(numDaysToEscheat));
|
||||
}
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.DORMANT.getValue()) {
|
||||
computedDaysToDormancy = DateUtils.getExactDifferenceInDays(currentDate,
|
||||
lastActiveTransactionDate.plusDays(numDaysToDormancy));
|
||||
}
|
||||
if (subStatusEnum < SavingsAccountSubStatusEnum.INACTIVE.getValue()) {
|
||||
computedDaysToInactive = DateUtils.getExactDifferenceInDays(currentDate,
|
||||
lastActiveTransactionDate.plusDays(numDaysToInactive));
|
||||
}
|
||||
}
|
||||
|
||||
result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("productCode"),
|
||||
JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"),
|
||||
JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"),
|
||||
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"),
|
||||
JdbcSupport.getInteger(rs, "daysToInactive"), JdbcSupport.getInteger(rs, "daysToDormancy"),
|
||||
JdbcSupport.getInteger(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"),
|
||||
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), isDormancyTrackingActive,
|
||||
computedDaysToInactive, computedDaysToDormancy,
|
||||
computedDaysToEscheat, JdbcSupport.getInteger(rs, "interestCalculateSelector"),
|
||||
rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"),
|
||||
JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"),
|
||||
rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"),
|
||||
@@ -1308,6 +1387,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"),
|
||||
rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"),
|
||||
rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate"));
|
||||
result.setDormancyStatus(subStatus, reasonForBlock, lastActiveTransactionDate);
|
||||
result.setCbsAccountFields(rs.getString("accountStatus"), rs.getString("modeOfOperation"),
|
||||
JdbcSupport.getLong(rs, "introducerClientId"), rs.getString("introducerName"),
|
||||
rs.getString("introducerCifNumber"), rs.getString("specialCategory"), rs.getString("weakerSection"),
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* 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.shareaccounts.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountPendingEditData;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountEditWritePlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/shareaccounts")
|
||||
@Component
|
||||
@Tag(name = "Share Account Edit Approval", description = "Maker-checker workflow for editing a Share Account's details (linked savings account, dividend calculation for inactive clients, lockin period, minimum active period). Edits are held in m_share_account_pending_edit (a pending row there is itself the lock) and only applied to the real account once a checker approves.")
|
||||
@RequiredArgsConstructor
|
||||
public class ShareAccountEditApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final ShareAccountEditWritePlatformService shareAccountEditWritePlatformService;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
private final DefaultToApiJsonSerializer<CommandProcessingResult> toApiJsonSerializer;
|
||||
private final DefaultToApiJsonSerializer<ShareAccountPendingEditData> pendingEditApiJsonSerializer;
|
||||
|
||||
@POST
|
||||
@Path("{accountId}/edit")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Submit a details edit for a Share Account",
|
||||
description = "Edits are held in m_share_account_pending_edit (a pending row there is itself the lock) and only "
|
||||
+ "applied to the real account once a checker approves - see /shareaccounts/{accountId}/edit/approve and /reject below.")
|
||||
public String submitEdit(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId,
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
|
||||
this.context.authenticatedUser().validateHasPermissionTo("SUBMITEDIT_SHAREACCOUNT");
|
||||
|
||||
final JsonCommand command = JsonCommand.fromJsonElement(accountId, this.fromApiJsonHelper.parse(apiRequestBodyAsJson),
|
||||
this.fromApiJsonHelper);
|
||||
|
||||
final CommandProcessingResult result = this.shareAccountEditWritePlatformService.submitEditForApproval(accountId, command);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("edit/pending")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "List Share Account details edits pending approval")
|
||||
public String retrieveAllPendingEdits(@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission("SHAREACCOUNT");
|
||||
|
||||
final Collection<ShareAccountPendingEditData> pendingEdits = this.shareAccountEditWritePlatformService
|
||||
.retrieveAllPendingEdits();
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.pendingEditApiJsonSerializer.serialize(settings, pendingEdits);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{accountId}/edit/pending")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve the pending details edit for a Share Account, if any")
|
||||
public String retrievePendingEdit(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId,
|
||||
@Context final UriInfo uriInfo) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission("SHAREACCOUNT");
|
||||
|
||||
final ShareAccountPendingEditData pendingEdit = this.shareAccountEditWritePlatformService.retrievePendingEdit(accountId);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.pendingEditApiJsonSerializer.serialize(settings, pendingEdit);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{accountId}/edit/approve")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Approve a pending Share Account details edit",
|
||||
description = "Applies the proposed details changes to the account. The maker of the edit cannot also approve it.")
|
||||
public String approvePendingEdit(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) {
|
||||
|
||||
if (this.context.authenticatedUser().hasNotPermissionForAnyOf("APPROVEEDIT_SHAREACCOUNT")) {
|
||||
throw new NoAuthorizationException("User has no authority to approve share account edits");
|
||||
}
|
||||
|
||||
final CommandProcessingResult result = this.shareAccountEditWritePlatformService.approveEdit(accountId);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{accountId}/edit/reject")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Reject a pending Share Account details edit",
|
||||
description = "Discards the proposed changes without applying them. The maker of the edit cannot also reject it.")
|
||||
public String rejectPendingEdit(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId,
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
|
||||
if (this.context.authenticatedUser().hasNotPermissionForAnyOf("REJECTEDIT_SHAREACCOUNT")) {
|
||||
throw new NoAuthorizationException("User has no authority to reject share account edits");
|
||||
}
|
||||
|
||||
String rejectReason = null;
|
||||
if (apiRequestBodyAsJson != null && !apiRequestBodyAsJson.isBlank()) {
|
||||
final var parsed = this.fromApiJsonHelper.parse(apiRequestBodyAsJson);
|
||||
if (parsed != null && parsed.isJsonObject() && parsed.getAsJsonObject().has("rejectReason")) {
|
||||
rejectReason = parsed.getAsJsonObject().get("rejectReason").getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
final CommandProcessingResult result = this.shareAccountEditWritePlatformService.rejectEdit(accountId, rejectReason);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
}
|
||||
+24
-15
@@ -41,6 +41,8 @@ public class ShareAccountData implements Serializable, AccountData {
|
||||
|
||||
private String externalId;
|
||||
|
||||
private String resolutionNumber;
|
||||
|
||||
private String savingsAccountNumber;
|
||||
|
||||
private Long clientId;
|
||||
@@ -154,9 +156,10 @@ public class ShareAccountData implements Serializable, AccountData {
|
||||
private Collection<EnumOptionData> minimumActivePeriodFrequencyTypeOptions;
|
||||
private Collection<SavingsAccountData> clientSavingsAccounts;
|
||||
|
||||
public ShareAccountData(final Long id, final String accountNo, final String externalId, final Long savingsAccountId,
|
||||
final String savingsAccountNumber, final Long clientId, final String clientName, final Long productId, final String productName,
|
||||
final ShareAccountStatusEnumData status, final ShareAccountApplicationTimelineData timeline, final CurrencyData currency,
|
||||
public ShareAccountData(final Long id, final String accountNo, final String externalId, final String resolutionNumber,
|
||||
final Long savingsAccountId, final String savingsAccountNumber, final Long clientId, final String clientName,
|
||||
final Long productId, final String productName, final ShareAccountStatusEnumData status,
|
||||
final ShareAccountApplicationTimelineData timeline, final CurrencyData currency,
|
||||
final ShareAccountSummaryData summaryData, final Collection<ShareAccountChargeData> charges,
|
||||
final Collection<ShareAccountTransactionData> purchasedSharesData, final Integer lockinPeriod,
|
||||
final EnumOptionData lockPeriodTypeEnum, final Integer minimumActivePeriod, final EnumOptionData minimumActivePeriodTypeEnum,
|
||||
@@ -164,6 +167,7 @@ public class ShareAccountData implements Serializable, AccountData {
|
||||
this.id = id;
|
||||
this.accountNo = accountNo;
|
||||
this.externalId = externalId;
|
||||
this.resolutionNumber = resolutionNumber;
|
||||
this.savingsAccountId = savingsAccountId;
|
||||
this.savingsAccountNumber = savingsAccountNumber;
|
||||
this.clientId = clientId;
|
||||
@@ -208,9 +212,10 @@ public class ShareAccountData implements Serializable, AccountData {
|
||||
this.defaultShares = defaultShares;
|
||||
}
|
||||
|
||||
private ShareAccountData(final Long id, final String accountNo, final String externalId, final Long savingsAccountId,
|
||||
final String savingsAccountNumber, final Long clientId, final String clientName, final Long productId, final String productName,
|
||||
final ShareAccountStatusEnumData status, final ShareAccountApplicationTimelineData timeline, final CurrencyData currency,
|
||||
private ShareAccountData(final Long id, final String accountNo, final String externalId, final String resolutionNumber,
|
||||
final Long savingsAccountId, final String savingsAccountNumber, final Long clientId, final String clientName,
|
||||
final Long productId, final String productName, final ShareAccountStatusEnumData status,
|
||||
final ShareAccountApplicationTimelineData timeline, final CurrencyData currency,
|
||||
final ShareAccountSummaryData summaryData, final Collection<ShareAccountChargeData> charges,
|
||||
final Collection<ShareAccountTransactionData> purchasedSharesData, final Integer lockinPeriod,
|
||||
final EnumOptionData lockPeriodTypeEnum, final Integer minimumActivePeriod, final EnumOptionData minimumActivePeriodTypeEnum,
|
||||
@@ -221,6 +226,7 @@ public class ShareAccountData implements Serializable, AccountData {
|
||||
this.id = id;
|
||||
this.accountNo = accountNo;
|
||||
this.externalId = externalId;
|
||||
this.resolutionNumber = resolutionNumber;
|
||||
this.savingsAccountId = savingsAccountId;
|
||||
this.savingsAccountNumber = savingsAccountNumber;
|
||||
this.clientId = clientId;
|
||||
@@ -250,15 +256,17 @@ public class ShareAccountData implements Serializable, AccountData {
|
||||
Collection<ChargeData> chargeOptions, Collection<SavingsAccountData> clientSavingsAccounts,
|
||||
Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions,
|
||||
Collection<EnumOptionData> minimumActivePeriodFrequencyTypeOptions) {
|
||||
return new ShareAccountData(data.id, data.accountNo, data.externalId, data.savingsAccountId, data.savingsAccountNumber,
|
||||
data.clientId, data.clientName, data.productId, data.productName, data.status, data.timeline, data.currency, data.summary,
|
||||
data.charges, data.purchasedShares, data.lockinPeriod, data.lockPeriodTypeEnum, data.minimumActivePeriod,
|
||||
data.minimumActivePeriodTypeEnum, data.allowDividendCalculationForInactiveClients, productOptions, chargeOptions,
|
||||
clientSavingsAccounts, lockinPeriodFrequencyTypeOptions, minimumActivePeriodFrequencyTypeOptions, data.currentMarketPrice);
|
||||
return new ShareAccountData(data.id, data.accountNo, data.externalId, data.resolutionNumber, data.savingsAccountId,
|
||||
data.savingsAccountNumber, data.clientId, data.clientName, data.productId, data.productName, data.status, data.timeline,
|
||||
data.currency, data.summary, data.charges, data.purchasedShares, data.lockinPeriod, data.lockPeriodTypeEnum,
|
||||
data.minimumActivePeriod, data.minimumActivePeriodTypeEnum, data.allowDividendCalculationForInactiveClients,
|
||||
productOptions, chargeOptions, clientSavingsAccounts, lockinPeriodFrequencyTypeOptions,
|
||||
minimumActivePeriodFrequencyTypeOptions, data.currentMarketPrice);
|
||||
}
|
||||
|
||||
public static ShareAccountData lookup(final Long id, final String accountNo, final Long clientId, final String clientName) {
|
||||
String externalId = null;
|
||||
String resolutionNumber = null;
|
||||
ShareAccountSummaryData summaryData = null;
|
||||
Collection<ShareAccountTransactionData> purchasedSharesData = null;
|
||||
GLAccountData suspenseAccount = null;
|
||||
@@ -282,10 +290,11 @@ public class ShareAccountData implements Serializable, AccountData {
|
||||
final Long productId = null;
|
||||
final Collection<ProductData> productOptions = null;
|
||||
|
||||
return new ShareAccountData(id, accountNo, externalId, savingsAccountId, savingsAccountNumber, clientId, clientName, productId,
|
||||
productName, status, timeline, currency, summaryData, charges, purchasedSharesData, lockinPeriod, lockPeriodTypeEnum,
|
||||
minimumActivePeriod, minimumActivePeriodTypeEnum, allowDividendCalculationForInactiveClients, productOptions, chargeOptions,
|
||||
clientSavingsAccounts, lockinPeriodFrequencyTypeOptions, minimumActivePeriodFrequencyTypeOption, currenMarketPrice);
|
||||
return new ShareAccountData(id, accountNo, externalId, resolutionNumber, savingsAccountId, savingsAccountNumber, clientId,
|
||||
clientName, productId, productName, status, timeline, currency, summaryData, charges, purchasedSharesData, lockinPeriod,
|
||||
lockPeriodTypeEnum, minimumActivePeriod, minimumActivePeriodTypeEnum, allowDividendCalculationForInactiveClients,
|
||||
productOptions, chargeOptions, clientSavingsAccounts, lockinPeriodFrequencyTypeOptions,
|
||||
minimumActivePeriodFrequencyTypeOption, currenMarketPrice);
|
||||
}
|
||||
|
||||
public void setCurrentMarketPrice(final BigDecimal currentMarketPrice) {
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.shareaccounts.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Read-side view of a single pending Share Account "details" edit (m_share_account_pending_edit), for the Approvals
|
||||
* -> Pending Share Account Edits screen.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class ShareAccountPendingEditData implements Serializable {
|
||||
|
||||
private Long id;
|
||||
private Long shareAccountId;
|
||||
private String accountNo;
|
||||
private Long clientId;
|
||||
private String clientName;
|
||||
private Long makerId;
|
||||
private String makerUsername;
|
||||
private Long checkerId;
|
||||
private String checkerUsername;
|
||||
private LocalDateTime createdOn;
|
||||
private LocalDateTime checkedOn;
|
||||
private String status;
|
||||
private String rejectReason;
|
||||
|
||||
private Long savingsAccountId;
|
||||
private String savingsAccountNo;
|
||||
private Boolean allowDividendCalculationForInactiveClients;
|
||||
private Integer lockinPeriodFrequency;
|
||||
private Integer lockinPeriodFrequencyType;
|
||||
private Integer minimumActivePeriodFrequency;
|
||||
private Integer minimumActivePeriodFrequencyType;
|
||||
|
||||
public ShareAccountPendingEditData() {}
|
||||
}
|
||||
+38
-4
@@ -105,6 +105,9 @@ public class ShareAccount extends AbstractPersistableCustom<Long> {
|
||||
@Column(name = "external_id")
|
||||
private String externalId;
|
||||
|
||||
@Column(name = "resolution_number", nullable = false)
|
||||
private String resolutionNumber;
|
||||
|
||||
@Column(name = "account_no", length = 20, unique = true, nullable = false)
|
||||
private String accountNumber;
|
||||
|
||||
@@ -151,10 +154,11 @@ public class ShareAccount extends AbstractPersistableCustom<Long> {
|
||||
|
||||
}
|
||||
|
||||
public ShareAccount(final Client client, final ShareProduct shareProduct, final String externalId, final MonetaryCurrency currency,
|
||||
final SavingsAccount savingsAccount, final String accountNo, final Long totalSharesApproved, final Long totalSharesPending,
|
||||
final Set<ShareAccountTransaction> purchasedShares, final Boolean allowDividendCalculationForInactiveClients,
|
||||
final Integer lockinPeriodFrequency, final PeriodFrequencyType lockPeriodType, final Integer minimumActivePeriodFrequency,
|
||||
public ShareAccount(final Client client, final ShareProduct shareProduct, final String externalId, final String resolutionNumber,
|
||||
final MonetaryCurrency currency, final SavingsAccount savingsAccount, final String accountNo,
|
||||
final Long totalSharesApproved, final Long totalSharesPending, final Set<ShareAccountTransaction> purchasedShares,
|
||||
final Boolean allowDividendCalculationForInactiveClients, final Integer lockinPeriodFrequency,
|
||||
final PeriodFrequencyType lockPeriodType, final Integer minimumActivePeriodFrequency,
|
||||
final PeriodFrequencyType minimumActivePeriodType, Set<ShareAccountCharge> charges, AppUser submittedBy,
|
||||
final LocalDate submittedDate, AppUser approvedBy, LocalDate approvedDate, AppUser rejectedBy, LocalDate rejectedDate,
|
||||
AppUser activatedBy, LocalDate activatedDate, AppUser closedBy, LocalDate closedDate, AppUser modifiedBy,
|
||||
@@ -163,6 +167,7 @@ public class ShareAccount extends AbstractPersistableCustom<Long> {
|
||||
this.client = client;
|
||||
this.shareProduct = shareProduct;
|
||||
this.externalId = externalId;
|
||||
this.resolutionNumber = resolutionNumber;
|
||||
this.currency = currency;
|
||||
this.savingsAccount = savingsAccount;
|
||||
if (StringUtils.isBlank(accountNo)) {
|
||||
@@ -235,6 +240,19 @@ public class ShareAccount extends AbstractPersistableCustom<Long> {
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public String getResolutionNumber() {
|
||||
return this.resolutionNumber;
|
||||
}
|
||||
|
||||
public boolean setResolutionNumber(final String resolutionNumber) {
|
||||
boolean toReturn = false;
|
||||
if (this.resolutionNumber == null || !this.resolutionNumber.equals(resolutionNumber)) {
|
||||
this.resolutionNumber = resolutionNumber;
|
||||
toReturn = true;
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public boolean setSavingsAccount(final SavingsAccount savingsAccount) {
|
||||
boolean returnValue = false;
|
||||
if (!this.savingsAccount.getId().equals(savingsAccount.getId())) {
|
||||
@@ -561,6 +579,22 @@ public class ShareAccount extends AbstractPersistableCustom<Long> {
|
||||
return this.lockinPeriodFrequencyType;
|
||||
}
|
||||
|
||||
public Integer getMinimumActivePeriodFrequency() {
|
||||
return this.minimumActivePeriodFrequency;
|
||||
}
|
||||
|
||||
public PeriodFrequencyType getMinimumActivePeriodFrequencyType() {
|
||||
return this.minimumActivePeriodFrequencyType;
|
||||
}
|
||||
|
||||
public Boolean getAllowDividendCalculationForInactiveClients() {
|
||||
return this.allowDividendCalculationForInactiveClients;
|
||||
}
|
||||
|
||||
public SavingsAccount getSavingsAccount() {
|
||||
return this.savingsAccount;
|
||||
}
|
||||
|
||||
public LocalDate getActivatedDate() {
|
||||
return this.activatedDate;
|
||||
}
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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.shareaccounts.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
/**
|
||||
* Holds a pending edit request for a Share Account's "details" fields (linked savings account,
|
||||
* dividend-calculation-for-inactive-clients, lockin period, minimum active period). Presence of a row here with
|
||||
* {@code status = PENDING} (enforced via the unique {@link #pendingLockKey}) is itself the lock - nothing is applied
|
||||
* to the real {@code m_share_account} row until a checker approves (see
|
||||
* ShareAccountEditWritePlatformServiceJpaRepositoryImpl).
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_share_account_pending_edit")
|
||||
@Getter
|
||||
@Setter
|
||||
public class ShareAccountPendingEdit extends AbstractPersistableCustom<Long> {
|
||||
|
||||
public static final String STATUS_PENDING = "PENDING";
|
||||
public static final String STATUS_APPROVED = "APPROVED";
|
||||
public static final String STATUS_REJECTED = "REJECTED";
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "share_account_id", nullable = false)
|
||||
private ShareAccount shareAccount;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "createdby_id", nullable = false)
|
||||
private AppUser maker;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "lastmodifiedby_id")
|
||||
private AppUser checker;
|
||||
|
||||
@Column(name = "created_date", nullable = false)
|
||||
private LocalDateTime createdOn;
|
||||
|
||||
@Column(name = "lastmodified_date")
|
||||
private LocalDateTime checkedOn;
|
||||
|
||||
@Column(name = "status", nullable = false, length = 20)
|
||||
private String status;
|
||||
|
||||
@Column(name = "reject_reason", length = 500)
|
||||
private String rejectReason;
|
||||
|
||||
/** "SHARE_ACCOUNT_EDIT:<shareAccountId>" while PENDING, NULL otherwise - the real DB-level lock. */
|
||||
@Column(name = "pending_lock_key", length = 60)
|
||||
private String pendingLockKey;
|
||||
|
||||
/** Links to the audit-only m_portfolio_command_source row created for this edit's submit/approve/reject. */
|
||||
@Column(name = "command_source_id")
|
||||
private Long commandSourceId;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "savings_account_id")
|
||||
private SavingsAccount savingsAccount;
|
||||
|
||||
@Column(name = "allow_dividends_inactive_clients")
|
||||
private Boolean allowDividendCalculationForInactiveClients;
|
||||
|
||||
@Column(name = "lockin_period_frequency")
|
||||
private Integer lockinPeriodFrequency;
|
||||
|
||||
@Enumerated(EnumType.ORDINAL)
|
||||
@Column(name = "lockin_period_frequency_enum")
|
||||
private PeriodFrequencyType lockinPeriodFrequencyType;
|
||||
|
||||
@Column(name = "minimum_active_period_frequency")
|
||||
private Integer minimumActivePeriodFrequency;
|
||||
|
||||
@Enumerated(EnumType.ORDINAL)
|
||||
@Column(name = "minimum_active_period_frequency_enum")
|
||||
private PeriodFrequencyType minimumActivePeriodFrequencyType;
|
||||
|
||||
protected ShareAccountPendingEdit() {}
|
||||
|
||||
public ShareAccountPendingEdit(final ShareAccount shareAccount, final AppUser maker) {
|
||||
this.shareAccount = shareAccount;
|
||||
this.maker = maker;
|
||||
this.createdOn = LocalDateTime.now();
|
||||
this.status = STATUS_PENDING;
|
||||
this.pendingLockKey = lockKeyFor(shareAccount.getId());
|
||||
}
|
||||
|
||||
public static String lockKeyFor(final Long shareAccountId) {
|
||||
return "SHARE_ACCOUNT_EDIT:" + shareAccountId;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return STATUS_PENDING.equals(this.status);
|
||||
}
|
||||
|
||||
public void markApproved(final AppUser checker) {
|
||||
this.status = STATUS_APPROVED;
|
||||
this.checker = checker;
|
||||
this.checkedOn = LocalDateTime.now();
|
||||
this.pendingLockKey = null;
|
||||
}
|
||||
|
||||
public void markRejected(final AppUser checker, final String reason) {
|
||||
this.status = STATUS_REJECTED;
|
||||
this.checker = checker;
|
||||
this.checkedOn = LocalDateTime.now();
|
||||
this.rejectReason = reason;
|
||||
this.pendingLockKey = null;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.shareaccounts.domain;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ShareAccountPendingEditRepository extends JpaRepository<ShareAccountPendingEdit, Long> {
|
||||
|
||||
Optional<ShareAccountPendingEdit> findByShareAccountIdAndStatus(Long shareAccountId, String status);
|
||||
|
||||
List<ShareAccountPendingEdit> findByStatus(String status);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.shareaccounts.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class ShareAccountEditPendingApprovalException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public ShareAccountEditPendingApprovalException(final Long shareAccountId) {
|
||||
super("error.msg.shareaccount.edit.pending.approval",
|
||||
"Share account with identifier " + shareAccountId + " already has a details edit pending approval", shareAccountId);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.shareaccounts.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class ShareAccountEditSelfApprovalNotAllowedException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public ShareAccountEditSelfApprovalNotAllowedException(final Long shareAccountId) {
|
||||
super("error.msg.shareaccount.edit.self.approval.not.allowed",
|
||||
"The maker of the pending details edit for share account " + shareAccountId + " cannot also approve or reject it",
|
||||
shareAccountId);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.shareaccounts.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public class ShareAccountNoPendingEditException extends AbstractPlatformDomainRuleException {
|
||||
|
||||
public ShareAccountNoPendingEditException(final Long shareAccountId) {
|
||||
super("error.msg.shareaccount.no.pending.edit",
|
||||
"Share account with identifier " + shareAccountId + " has no details edit awaiting approval", shareAccountId);
|
||||
}
|
||||
}
|
||||
+17
-4
@@ -145,6 +145,10 @@ public class ShareAccountDataSerializer {
|
||||
final String externalId = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.externalid_paramname, element);
|
||||
// baseDataValidator.reset().parameter(ShareAccountApiConstants.externalid_paramname).value(externalId).notNull();
|
||||
|
||||
final String resolutionNumber = this.fromApiJsonHelper.extractStringNamed(ShareAccountApiConstants.resolutionnumber_paramname,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(ShareAccountApiConstants.resolutionnumber_paramname).value(resolutionNumber).notBlank();
|
||||
|
||||
Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.savingsaccountid_paramname, element);
|
||||
baseDataValidator.reset().parameter(ShareAccountApiConstants.savingsaccountid_paramname).value(savingsAccountId).notNull()
|
||||
.longGreaterThanZero();
|
||||
@@ -216,10 +220,10 @@ public class ShareAccountDataSerializer {
|
||||
Set<ShareAccountTransaction> sharesPurchased = new HashSet<>();
|
||||
sharesPurchased.add(transaction);
|
||||
|
||||
ShareAccount account = new ShareAccount(client, shareProduct, externalId, currency, savingsAccount, accountNo, approvedShares,
|
||||
pendingShares, sharesPurchased, allowdividendsForInactiveClients, lockinPeriod, lockPeriodEnum, minimumActivePeriod,
|
||||
minimumActivePeriodEnum, charges, submittedBy, submittedDate, approvedBy, approvedDate, rejectedBy, rejectedDate,
|
||||
activatedBy, activatedDate, closedBy, closedDate, modifiedBy, modifiedDate);
|
||||
ShareAccount account = new ShareAccount(client, shareProduct, externalId, resolutionNumber, currency, savingsAccount, accountNo,
|
||||
approvedShares, pendingShares, sharesPurchased, allowdividendsForInactiveClients, lockinPeriod, lockPeriodEnum,
|
||||
minimumActivePeriod, minimumActivePeriodEnum, charges, submittedBy, submittedDate, approvedBy, approvedDate, rejectedBy,
|
||||
rejectedDate, activatedBy, activatedDate, closedBy, closedDate, modifiedBy, modifiedDate);
|
||||
|
||||
for (ShareAccountTransaction pur : sharesPurchased) {
|
||||
pur.setShareAccount(account);
|
||||
@@ -300,6 +304,15 @@ public class ShareAccountDataSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.resolutionnumber_paramname, element)) {
|
||||
final String resolutionNumber = this.fromApiJsonHelper
|
||||
.extractStringNamed(ShareAccountApiConstants.resolutionnumber_paramname, element);
|
||||
baseDataValidator.reset().parameter(ShareAccountApiConstants.resolutionnumber_paramname).value(resolutionNumber).notBlank();
|
||||
if (account.setResolutionNumber(resolutionNumber)) {
|
||||
actualChanges.put(ShareAccountApiConstants.resolutionnumber_paramname, resolutionNumber);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(ShareAccountApiConstants.savingsaccountid_paramname, element)) {
|
||||
Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed(ShareAccountApiConstants.savingsaccountid_paramname, element);
|
||||
baseDataValidator.reset().parameter(ShareAccountApiConstants.savingsaccountid_paramname).value(savingsAccountId).notNull()
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.shareaccounts.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountPendingEditData;
|
||||
|
||||
public interface ShareAccountEditWritePlatformService {
|
||||
|
||||
CommandProcessingResult submitEditForApproval(Long shareAccountId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult approveEdit(Long shareAccountId);
|
||||
|
||||
CommandProcessingResult rejectEdit(Long shareAccountId, String rejectReason);
|
||||
|
||||
ShareAccountPendingEditData retrievePendingEdit(Long shareAccountId);
|
||||
|
||||
Collection<ShareAccountPendingEditData> retrieveAllPendingEdits();
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* 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.shareaccounts.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandProcessingResultType;
|
||||
import org.apache.fineract.commands.domain.CommandSource;
|
||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.accounts.constants.ShareAccountApiConstants;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountPendingEditData;
|
||||
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
|
||||
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountPendingEdit;
|
||||
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountPendingEditRepository;
|
||||
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.shareaccounts.exception.ShareAccountEditPendingApprovalException;
|
||||
import org.apache.fineract.portfolio.shareaccounts.exception.ShareAccountEditSelfApprovalNotAllowedException;
|
||||
import org.apache.fineract.portfolio.shareaccounts.exception.ShareAccountNoPendingEditException;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Maker-checker edit flow for a Share Account's "details" fields (linked savings account,
|
||||
* allow-dividend-calculation-for-inactive-clients, lockin period, minimum active period). Mirrors
|
||||
* {@code SavingsAccountEditWritePlatformServiceJpaRepositoryImpl}'s submitEditForApproval/approveEdit/rejectEdit
|
||||
* pattern: a row in {@code m_share_account_pending_edit} is itself the lock, and nothing is applied to the real
|
||||
* {@code m_share_account} row until a checker approves. Unlike the Savings Account version, this edit carries no
|
||||
* balance/hold implications, so the maker's full intended new state of the 6 fields is captured (falling back to the
|
||||
* account's current value for any field the maker didn't touch) rather than a partial toggle diff.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ShareAccountEditWritePlatformServiceJpaRepositoryImpl implements ShareAccountEditWritePlatformService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final ShareAccountRepositoryWrapper shareAccountRepositoryWrapper;
|
||||
private final ShareAccountPendingEditRepository pendingEditRepository;
|
||||
private final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper;
|
||||
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
|
||||
private final CommandSourceRepository commandSourceRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult submitEditForApproval(final Long shareAccountId, final JsonCommand command) {
|
||||
|
||||
final AppUser maker = this.context.authenticatedUser();
|
||||
final ShareAccount account = this.shareAccountRepositoryWrapper.findOneWithNotFoundDetection(shareAccountId);
|
||||
|
||||
if (this.pendingEditRepository.findByShareAccountIdAndStatus(shareAccountId, ShareAccountPendingEdit.STATUS_PENDING)
|
||||
.isPresent()) {
|
||||
throw new ShareAccountEditPendingApprovalException(shareAccountId);
|
||||
}
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("sharesaccount");
|
||||
|
||||
final ShareAccountPendingEdit pendingEdit = new ShareAccountPendingEdit(account, maker);
|
||||
|
||||
if (command.hasParameter(ShareAccountApiConstants.savingsaccountid_paramname)) {
|
||||
final Long savingsAccountId = command.longValueOfParameterNamed(ShareAccountApiConstants.savingsaccountid_paramname);
|
||||
baseDataValidator.reset().parameter(ShareAccountApiConstants.savingsaccountid_paramname).value(savingsAccountId).notNull()
|
||||
.longGreaterThanZero();
|
||||
if (!this.savingsAccountReadPlatformService.isAccountBelongsToClient(account.getClientId(), savingsAccountId,
|
||||
DepositAccountType.SAVINGS_DEPOSIT, account.getShareProduct().getCurrency().getCode())) {
|
||||
throw new SavingsAccountNotFoundException(savingsAccountId);
|
||||
}
|
||||
pendingEdit.setSavingsAccount(this.savingsAccountRepositoryWrapper.findOneWithNotFoundDetection(savingsAccountId));
|
||||
} else {
|
||||
pendingEdit.setSavingsAccount(account.getSavingsAccount());
|
||||
}
|
||||
|
||||
if (command.hasParameter(ShareAccountApiConstants.allowdividendcalculationforinactiveclients_paramname)) {
|
||||
pendingEdit.setAllowDividendCalculationForInactiveClients(
|
||||
command.booleanObjectValueOfParameterNamed(ShareAccountApiConstants.allowdividendcalculationforinactiveclients_paramname));
|
||||
} else {
|
||||
pendingEdit.setAllowDividendCalculationForInactiveClients(account.getAllowDividendCalculationForInactiveClients());
|
||||
}
|
||||
|
||||
if (command.hasParameter(ShareAccountApiConstants.lockinperiod_paramname)) {
|
||||
final Integer lockinPeriod = command.integerValueOfParameterNamed(ShareAccountApiConstants.lockinperiod_paramname);
|
||||
baseDataValidator.reset().parameter(ShareAccountApiConstants.lockinperiod_paramname).value(lockinPeriod).notNull();
|
||||
pendingEdit.setLockinPeriodFrequency(lockinPeriod);
|
||||
} else {
|
||||
pendingEdit.setLockinPeriodFrequency(account.getLockinPeriodFrequency());
|
||||
}
|
||||
|
||||
if (command.hasParameter(ShareAccountApiConstants.lockperiodfrequencytype_paramname)) {
|
||||
pendingEdit.setLockinPeriodFrequencyType(
|
||||
extractPeriodType(command.integerValueOfParameterNamed(ShareAccountApiConstants.lockperiodfrequencytype_paramname)));
|
||||
} else {
|
||||
pendingEdit.setLockinPeriodFrequencyType(account.getLockinPeriodFrequencyType());
|
||||
}
|
||||
|
||||
if (command.hasParameter(ShareAccountApiConstants.minimumactiveperiod_paramname)) {
|
||||
final Integer minimumActivePeriod = command.integerValueOfParameterNamed(ShareAccountApiConstants.minimumactiveperiod_paramname);
|
||||
baseDataValidator.reset().parameter(ShareAccountApiConstants.minimumactiveperiod_paramname).value(minimumActivePeriod)
|
||||
.notNull();
|
||||
pendingEdit.setMinimumActivePeriodFrequency(minimumActivePeriod);
|
||||
} else {
|
||||
pendingEdit.setMinimumActivePeriodFrequency(account.getMinimumActivePeriodFrequency());
|
||||
}
|
||||
|
||||
if (command.hasParameter(ShareAccountApiConstants.minimumactiveperiodfrequencytype_paramname)) {
|
||||
pendingEdit.setMinimumActivePeriodFrequencyType(extractPeriodType(
|
||||
command.integerValueOfParameterNamed(ShareAccountApiConstants.minimumactiveperiodfrequencytype_paramname)));
|
||||
} else {
|
||||
pendingEdit.setMinimumActivePeriodFrequencyType(account.getMinimumActivePeriodFrequencyType());
|
||||
}
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
|
||||
final CommandSource auditEntry = CommandSource.builder() //
|
||||
.actionName("SUBMITEDIT") //
|
||||
.entityName("SHAREACCOUNT") //
|
||||
.resourceGetUrl("/shareaccounts/" + shareAccountId) //
|
||||
.resourceId(shareAccountId) //
|
||||
.commandAsJson(command.json()) //
|
||||
.maker(maker) //
|
||||
.madeOnDate(DateUtils.getAuditOffsetDateTime()) //
|
||||
.status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()) //
|
||||
.idempotencyKey(UUID.randomUUID().toString()) //
|
||||
.sanitized(false) //
|
||||
.build();
|
||||
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||
pendingEdit.setCommandSourceId(auditEntry.getId());
|
||||
|
||||
this.pendingEditRepository.saveAndFlush(pendingEdit);
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(shareAccountId).build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult approveEdit(final Long shareAccountId) {
|
||||
|
||||
final AppUser checker = this.context.authenticatedUser();
|
||||
final ShareAccountPendingEdit pendingEdit = this.pendingEditRepository
|
||||
.findByShareAccountIdAndStatus(shareAccountId, ShareAccountPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new ShareAccountNoPendingEditException(shareAccountId));
|
||||
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new ShareAccountEditSelfApprovalNotAllowedException(shareAccountId);
|
||||
}
|
||||
|
||||
final ShareAccount account = this.shareAccountRepositoryWrapper.findOneWithNotFoundDetection(shareAccountId);
|
||||
|
||||
if (pendingEdit.getSavingsAccount() != null) {
|
||||
account.setSavingsAccount(pendingEdit.getSavingsAccount());
|
||||
}
|
||||
account.setAllowDividendCalculationForInactiveClients(pendingEdit.getAllowDividendCalculationForInactiveClients());
|
||||
if (pendingEdit.getLockinPeriodFrequency() != null) {
|
||||
account.setLockPeriod(pendingEdit.getLockinPeriodFrequency());
|
||||
}
|
||||
if (pendingEdit.getLockinPeriodFrequencyType() != null) {
|
||||
account.setLockPeriodFrequencyEnum(pendingEdit.getLockinPeriodFrequencyType());
|
||||
}
|
||||
if (pendingEdit.getMinimumActivePeriodFrequency() != null) {
|
||||
account.setminimumActivePeriod(pendingEdit.getMinimumActivePeriodFrequency());
|
||||
}
|
||||
if (pendingEdit.getMinimumActivePeriodFrequencyType() != null) {
|
||||
account.setminimumActivePeriodTypeEnum(pendingEdit.getMinimumActivePeriodFrequencyType());
|
||||
}
|
||||
|
||||
this.shareAccountRepositoryWrapper.saveAndFlush(account);
|
||||
|
||||
pendingEdit.markApproved(checker);
|
||||
this.pendingEditRepository.saveAndFlush(pendingEdit);
|
||||
|
||||
updateAuditEntry(pendingEdit, checker, false);
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(shareAccountId).build();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult rejectEdit(final Long shareAccountId, final String rejectReason) {
|
||||
|
||||
final AppUser checker = this.context.authenticatedUser();
|
||||
final ShareAccountPendingEdit pendingEdit = this.pendingEditRepository
|
||||
.findByShareAccountIdAndStatus(shareAccountId, ShareAccountPendingEdit.STATUS_PENDING)
|
||||
.orElseThrow(() -> new ShareAccountNoPendingEditException(shareAccountId));
|
||||
|
||||
if (pendingEdit.getMaker().getId().equals(checker.getId()) && !checker.isCheckerSuperUser()) {
|
||||
throw new ShareAccountEditSelfApprovalNotAllowedException(shareAccountId);
|
||||
}
|
||||
|
||||
pendingEdit.markRejected(checker, rejectReason);
|
||||
this.pendingEditRepository.saveAndFlush(pendingEdit);
|
||||
|
||||
updateAuditEntry(pendingEdit, checker, true);
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(shareAccountId).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShareAccountPendingEditData retrievePendingEdit(final Long shareAccountId) {
|
||||
return this.pendingEditRepository.findByShareAccountIdAndStatus(shareAccountId, ShareAccountPendingEdit.STATUS_PENDING)
|
||||
.map(this::toData).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ShareAccountPendingEditData> retrieveAllPendingEdits() {
|
||||
return this.pendingEditRepository.findByStatus(ShareAccountPendingEdit.STATUS_PENDING).stream().map(this::toData).toList();
|
||||
}
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private PeriodFrequencyType extractPeriodType(final Integer value) {
|
||||
return value == null ? null : PeriodFrequencyType.fromInt(value);
|
||||
}
|
||||
|
||||
private void updateAuditEntry(final ShareAccountPendingEdit pendingEdit, final AppUser checker, final boolean rejected) {
|
||||
Optional<CommandSource> existing = Optional.empty();
|
||||
if (pendingEdit.getCommandSourceId() != null) {
|
||||
existing = this.commandSourceRepository.findById(pendingEdit.getCommandSourceId());
|
||||
}
|
||||
final CommandSource auditEntry = existing.orElseGet(() -> CommandSource.builder() //
|
||||
.actionName(rejected ? "REJECTEDIT" : "APPROVEEDIT") //
|
||||
.entityName("SHAREACCOUNT") //
|
||||
.resourceGetUrl("/shareaccounts/" + pendingEdit.getShareAccount().getId()) //
|
||||
.resourceId(pendingEdit.getShareAccount().getId()) //
|
||||
.maker(pendingEdit.getMaker()) //
|
||||
.madeOnDate(DateUtils.getAuditOffsetDateTime()) //
|
||||
.status(CommandProcessingResultType.AWAITING_APPROVAL.getValue()) //
|
||||
.idempotencyKey(UUID.randomUUID().toString()) //
|
||||
.sanitized(false) //
|
||||
.build());
|
||||
if (rejected) {
|
||||
auditEntry.markAsRejected(checker);
|
||||
} else {
|
||||
auditEntry.markAsChecked(checker);
|
||||
}
|
||||
this.commandSourceRepository.saveAndFlush(auditEntry);
|
||||
}
|
||||
|
||||
private ShareAccountPendingEditData toData(final ShareAccountPendingEdit p) {
|
||||
final ShareAccountPendingEditData data = new ShareAccountPendingEditData();
|
||||
data.setId(p.getId());
|
||||
data.setShareAccountId(p.getShareAccount().getId());
|
||||
data.setAccountNo(p.getShareAccount().getAccountNumber());
|
||||
data.setClientId(p.getShareAccount().getClientId());
|
||||
data.setMakerId(p.getMaker().getId());
|
||||
data.setMakerUsername(p.getMaker().getUsername());
|
||||
if (p.getChecker() != null) {
|
||||
data.setCheckerId(p.getChecker().getId());
|
||||
data.setCheckerUsername(p.getChecker().getUsername());
|
||||
}
|
||||
data.setCreatedOn(p.getCreatedOn());
|
||||
data.setCheckedOn(p.getCheckedOn());
|
||||
data.setStatus(p.getStatus());
|
||||
data.setRejectReason(p.getRejectReason());
|
||||
if (p.getSavingsAccount() != null) {
|
||||
data.setSavingsAccountId(p.getSavingsAccount().getId());
|
||||
data.setSavingsAccountNo(p.getSavingsAccount().getAccountNumber());
|
||||
}
|
||||
data.setAllowDividendCalculationForInactiveClients(p.getAllowDividendCalculationForInactiveClients());
|
||||
data.setLockinPeriodFrequency(p.getLockinPeriodFrequency());
|
||||
if (p.getLockinPeriodFrequencyType() != null) {
|
||||
data.setLockinPeriodFrequencyType(p.getLockinPeriodFrequencyType().getValue());
|
||||
}
|
||||
data.setMinimumActivePeriodFrequency(p.getMinimumActivePeriodFrequency());
|
||||
if (p.getMinimumActivePeriodFrequencyType() != null) {
|
||||
data.setMinimumActivePeriodFrequencyType(p.getMinimumActivePeriodFrequencyType().getValue());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
+11
-7
@@ -241,7 +241,8 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat
|
||||
final Collection<ShareAccountTransactionData> purchasedShares) {
|
||||
this.charges = charges;
|
||||
this.purchasedShares = purchasedShares;
|
||||
StringBuilder buff = new StringBuilder().append("sa.id as id, sa.external_id as externalId, sa.status_enum as statusEnum, ")
|
||||
StringBuilder buff = new StringBuilder().append(
|
||||
"sa.id as id, sa.external_id as externalId, sa.resolution_number as resolutionNumber, sa.status_enum as statusEnum, ")
|
||||
.append("sa.savings_account_id, msa.account_no as savingsAccNo, ")
|
||||
.append("c.id as clientId, c.display_name as clientName, ")
|
||||
.append("sa.account_no as accountNo, sa.total_approved_shares as approvedShares, sa.total_pending_shares as pendingShares, ")
|
||||
@@ -278,6 +279,7 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat
|
||||
final Long id = rs.getLong("id");
|
||||
final String accountNo = rs.getString("accountNo");
|
||||
final String externalId = rs.getString("externalId");
|
||||
final String resolutionNumber = rs.getString("resolutionNumber");
|
||||
final Long savingsAccountId = JdbcSupport.getLong(rs, "savings_account_id");
|
||||
final String savingsAccountNumber = rs.getString("savingsAccNo");
|
||||
final Long clientId = JdbcSupport.getLong(rs, "clientId");
|
||||
@@ -349,9 +351,10 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat
|
||||
final String shortProductName = null;
|
||||
final ShareAccountSummaryData summary = new ShareAccountSummaryData(id, accountNo, externalId, productId, productName,
|
||||
shortProductName, status, currency, totalApprovedShares, totalPendingShares, timeline);
|
||||
return new ShareAccountData(id, accountNo, externalId, savingsAccountId, savingsAccountNumber, clientId, clientName, productId,
|
||||
productName, status, timeline, currency, summary, charges, purchasedShares, lockinPeriodFrequency,
|
||||
lockinPeriodFrequencyType, minimumActivePeriod, minimumActivePeriodType, allowdividendsforinactiveclients);
|
||||
return new ShareAccountData(id, accountNo, externalId, resolutionNumber, savingsAccountId, savingsAccountNumber, clientId,
|
||||
clientName, productId, productName, status, timeline, currency, summary, charges, purchasedShares,
|
||||
lockinPeriodFrequency, lockinPeriodFrequencyType, minimumActivePeriod, minimumActivePeriodType,
|
||||
allowdividendsforinactiveclients);
|
||||
|
||||
}
|
||||
|
||||
@@ -392,6 +395,7 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat
|
||||
final Long totalApprovedShares = null;
|
||||
final Long totalPendingShares = null;
|
||||
final String externalId = null;
|
||||
final String resolutionNumber = null;
|
||||
final Long productId = null;
|
||||
final String productName = null;
|
||||
final String shortProductName = null;
|
||||
@@ -418,9 +422,9 @@ public class ShareAccountReadPlatformServiceImpl implements ShareAccountReadPlat
|
||||
final ShareAccountSummaryData summary = new ShareAccountSummaryData(id, accountNo, externalId, productId, productName,
|
||||
shortProductName, status, currency, totalApprovedShares, totalPendingShares, timeline);
|
||||
|
||||
return new ShareAccountData(id, accountNo, externalId, clientId, clientName, productId, shortProductName, productId,
|
||||
shortProductName, status, timeline, currency, summary, charges, purchasedSharesData, lockinPeriod, lockPeriodTypeEnum,
|
||||
minimumActivePeriod, minimumActivePeriodTypeEnum, allowdividendsforinactiveclients);
|
||||
return new ShareAccountData(id, accountNo, externalId, resolutionNumber, clientId, clientName, productId, shortProductName,
|
||||
productId, shortProductName, status, timeline, currency, summary, charges, purchasedSharesData, lockinPeriod,
|
||||
lockPeriodTypeEnum, minimumActivePeriod, minimumActivePeriodTypeEnum, allowdividendsforinactiveclients);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -34,7 +34,12 @@ import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
|
||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendRepository;
|
||||
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountPendingEditRepository;
|
||||
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.shareaccounts.serialization.ShareAccountDataSerializer;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.PurchasedSharesReadPlatformService;
|
||||
@@ -44,6 +49,8 @@ import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountChargeRea
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountCommandsServiceImpl;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountDividendReadPlatformService;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountDividendReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountEditWritePlatformService;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountEditWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.shareaccounts.service.ShareAccountSchedularService;
|
||||
@@ -121,4 +128,14 @@ public class ShareAccountsConfiguration {
|
||||
accountNumberGenerator, accountNumberFormatRepository, journalEntryWritePlatformService, noteRepository,
|
||||
jdbcTemplate, businessEventNotifierService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ShareAccountEditWritePlatformService.class)
|
||||
public ShareAccountEditWritePlatformService shareAccountEditWritePlatformService(PlatformSecurityContext context,
|
||||
ShareAccountRepositoryWrapper shareAccountRepositoryWrapper, ShareAccountPendingEditRepository pendingEditRepository,
|
||||
SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper,
|
||||
SavingsAccountReadPlatformService savingsAccountReadPlatformService, CommandSourceRepository commandSourceRepository) {
|
||||
return new ShareAccountEditWritePlatformServiceJpaRepositoryImpl(context, shareAccountRepositoryWrapper, pendingEditRepository,
|
||||
savingsAccountRepositoryWrapper, savingsAccountReadPlatformService, commandSourceRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,4 +466,7 @@
|
||||
<include file="parts/0357_add_dd_limit_to_sponsor_dd_gl_mapping.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0358_add_batch_id_to_sponsor_dd_issue_request.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0359_reclassify_dd_gst_payable_as_income.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0360_add_resolution_number_to_share_account.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0361_add_share_account_pending_edit_table.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0362_add_share_account_edit_approval_permissions.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<addColumn tableName="m_share_account">
|
||||
<column name="resolution_number" type="VARCHAR(100)" defaultValue="">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!--
|
||||
Holds a pending edit request for a Share Account's "details" fields (linked savings account,
|
||||
dividend-calculation-for-inactive-clients, lockin period, minimum active period). Presence of a
|
||||
row here with status=PENDING (enforced via the unique pending_lock_key) is itself the lock -
|
||||
nothing is applied to the real m_share_account row until a checker approves
|
||||
(see ShareAccountEditWritePlatformServiceJpaRepositoryImpl).
|
||||
-->
|
||||
<changeSet author="fineract" id="1">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<tableExists tableName="m_share_account_pending_edit" />
|
||||
</not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_share_account_pending_edit">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false" />
|
||||
</column>
|
||||
<column name="share_account_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_shaoe_share_account_id" references="m_share_account(id)" />
|
||||
</column>
|
||||
<column name="createdby_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_shaoe_maker_id" references="m_appuser(id)" />
|
||||
</column>
|
||||
<column name="lastmodifiedby_id" type="BIGINT">
|
||||
<constraints nullable="true" foreignKeyName="fk_shaoe_checker_id" references="m_appuser(id)" />
|
||||
</column>
|
||||
<column name="created_date" type="DATETIME">
|
||||
<constraints nullable="false" />
|
||||
</column>
|
||||
<column name="lastmodified_date" type="DATETIME">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
<column name="status" type="VARCHAR(20)" defaultValue="PENDING">
|
||||
<constraints nullable="false" />
|
||||
</column>
|
||||
<column name="reject_reason" type="VARCHAR(500)" />
|
||||
<column name="pending_lock_key" type="VARCHAR(60)">
|
||||
<constraints nullable="true" unique="true" uniqueConstraintName="shaoe_unq_pending_lock_key" />
|
||||
</column>
|
||||
<column name="command_source_id" type="BIGINT">
|
||||
<constraints nullable="true" foreignKeyName="fk_shaoe_command_source_id" references="m_portfolio_command_source(id)" />
|
||||
</column>
|
||||
|
||||
<!-- Proposed new values for the 6 editable "details" fields. NULL means the maker left that
|
||||
field unchanged from the account's current value at submission time. -->
|
||||
<column name="savings_account_id" type="BIGINT">
|
||||
<constraints nullable="true" foreignKeyName="fk_shaoe_savings_account_id" references="m_savings_account(id)" />
|
||||
</column>
|
||||
<column name="allow_dividends_inactive_clients" type="BOOLEAN" />
|
||||
<column name="lockin_period_frequency" type="INTEGER" />
|
||||
<column name="lockin_period_frequency_enum" type="SMALLINT" />
|
||||
<column name="minimum_active_period_frequency" type="INTEGER" />
|
||||
<column name="minimum_active_period_frequency_enum" type="SMALLINT" />
|
||||
</createTable>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!--
|
||||
Permissions gating the Share Account "Edit Details" maker-checker flow. SUBMITEDIT lets a maker
|
||||
propose the edit (the "Edit Details" action on the account view); APPROVEEDIT/REJECTEDIT let a
|
||||
checker resolve it. The _CHECKER permission follows the "<ACTION>_<ENTITY>_CHECKER" convention so
|
||||
the pending request also surfaces in the generic Checker Inbox for limited checkers.
|
||||
-->
|
||||
<changeSet author="fineract" id="1">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'SUBMITEDIT_SHAREACCOUNT'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="SHAREACCOUNT" />
|
||||
<column name="code" value="SUBMITEDIT_SHAREACCOUNT" />
|
||||
<column name="entity_name" value="SHAREACCOUNT" />
|
||||
<column name="action_name" value="SUBMITEDIT" />
|
||||
<column name="can_maker_checker" valueBoolean="false" />
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="2">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'APPROVEEDIT_SHAREACCOUNT'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="SHAREACCOUNT" />
|
||||
<column name="code" value="APPROVEEDIT_SHAREACCOUNT" />
|
||||
<column name="entity_name" value="SHAREACCOUNT" />
|
||||
<column name="action_name" value="APPROVEEDIT" />
|
||||
<column name="can_maker_checker" valueBoolean="false" />
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="3">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'REJECTEDIT_SHAREACCOUNT'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="SHAREACCOUNT" />
|
||||
<column name="code" value="REJECTEDIT_SHAREACCOUNT" />
|
||||
<column name="entity_name" value="SHAREACCOUNT" />
|
||||
<column name="action_name" value="REJECTEDIT" />
|
||||
<column name="can_maker_checker" valueBoolean="false" />
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="fineract" id="4">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'APPROVEEDIT_SHAREACCOUNT_CHECKER'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="SHAREACCOUNT" />
|
||||
<column name="code" value="APPROVEEDIT_SHAREACCOUNT_CHECKER" />
|
||||
<column name="entity_name" value="SHAREACCOUNT" />
|
||||
<column name="action_name" value="APPROVEEDIT_CHECKER" />
|
||||
<column name="can_maker_checker" valueBoolean="false" />
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+18
@@ -116,6 +116,21 @@ public final class FixedDepositAccountData extends DepositAccountData {
|
||||
private Boolean deceasedSettlement;
|
||||
private Boolean allowDebitTxnAfterDueDate;
|
||||
|
||||
// Live per-account dormancy state (distinct from the product-level
|
||||
// isDormancyTrackingActive/daysToInactive/daysToDormancy/daysToEscheat
|
||||
// thresholds above): the account's actual current sub-status and the
|
||||
// computed countdown, mirroring SavingsAccountData.
|
||||
private SavingsAccountSubStatusEnumData subStatus;
|
||||
private String reasonForBlock;
|
||||
private LocalDate lastActiveTransactionDate;
|
||||
|
||||
public void setDormancyStatus(final SavingsAccountSubStatusEnumData subStatus, final String reasonForBlock,
|
||||
final LocalDate lastActiveTransactionDate) {
|
||||
this.subStatus = subStatus;
|
||||
this.reasonForBlock = reasonForBlock;
|
||||
this.lastActiveTransactionDate = lastActiveTransactionDate;
|
||||
}
|
||||
|
||||
// Joint holders for JOINT account status
|
||||
private java.util.List<java.util.Map<String, Object>> jointHolders;
|
||||
|
||||
@@ -314,6 +329,9 @@ public final class FixedDepositAccountData extends DepositAccountData {
|
||||
this.jointHolders = src.jointHolders;
|
||||
this.isSeniorCitizen = src.isSeniorCitizen;
|
||||
this.seniorCitizenInterestRate = src.seniorCitizenInterestRate;
|
||||
this.subStatus = src.subStatus;
|
||||
this.reasonForBlock = src.reasonForBlock;
|
||||
this.lastActiveTransactionDate = src.lastActiveTransactionDate;
|
||||
}
|
||||
|
||||
private FixedDepositAccountData(Long clientId, Long productId, Long fieldofficerId, LocalDate submittedOnDate,
|
||||
|
||||
+18
@@ -147,6 +147,21 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
||||
private Boolean deceasedSettlement;
|
||||
private Boolean allowDebitTxnAfterDueDate;
|
||||
|
||||
// Live per-account dormancy state (distinct from the product-level
|
||||
// isDormancyTrackingActive/daysToInactive/daysToDormancy/daysToEscheat
|
||||
// thresholds above): the account's actual current sub-status and the
|
||||
// computed countdown, mirroring SavingsAccountData.
|
||||
private SavingsAccountSubStatusEnumData subStatus;
|
||||
private String reasonForBlock;
|
||||
private LocalDate lastActiveTransactionDate;
|
||||
|
||||
public void setDormancyStatus(final SavingsAccountSubStatusEnumData subStatus, final String reasonForBlock,
|
||||
final LocalDate lastActiveTransactionDate) {
|
||||
this.subStatus = subStatus;
|
||||
this.reasonForBlock = reasonForBlock;
|
||||
this.lastActiveTransactionDate = lastActiveTransactionDate;
|
||||
}
|
||||
|
||||
public void setAccountTypeOptions(Collection<CodeValueData> accountTypeOptions) {
|
||||
this.accountTypeOptions = accountTypeOptions;
|
||||
}
|
||||
@@ -238,6 +253,9 @@ public final class RecurringDepositAccountData extends DepositAccountData {
|
||||
this.jointHolders = src.jointHolders;
|
||||
this.isSeniorCitizen = src.isSeniorCitizen;
|
||||
this.seniorCitizenInterestRate = src.seniorCitizenInterestRate;
|
||||
this.subStatus = src.subStatus;
|
||||
this.reasonForBlock = src.reasonForBlock;
|
||||
this.lastActiveTransactionDate = src.lastActiveTransactionDate;
|
||||
}
|
||||
|
||||
public void setCbsProductFields(String accountType, String customerSegment, String productCode,
|
||||
|
||||
+11
-4
@@ -748,10 +748,17 @@ public final class ProductPendingEditFieldMapper {
|
||||
f.setTotalShares(command.longValueOfParameterNamed("totalShares"));
|
||||
present.add("totalShares");
|
||||
}
|
||||
if (command.hasParameter("totalSharesIssued") || command.hasParameter("issuedShares")) {
|
||||
final Long val = command.hasParameter("totalSharesIssued") ? command.longValueOfParameterNamed("totalSharesIssued") : command.longValueOfParameterNamed("issuedShares");
|
||||
if (command.hasParameter("sharesIssued") || command.hasParameter("totalSharesIssued") || command.hasParameter("issuedShares")) {
|
||||
final Long val;
|
||||
if (command.hasParameter("sharesIssued")) {
|
||||
val = command.longValueOfParameterNamed("sharesIssued");
|
||||
} else if (command.hasParameter("totalSharesIssued")) {
|
||||
val = command.longValueOfParameterNamed("totalSharesIssued");
|
||||
} else {
|
||||
val = command.longValueOfParameterNamed("issuedShares");
|
||||
}
|
||||
f.setTotalSharesIssued(val);
|
||||
present.add("totalSharesIssued");
|
||||
present.add("sharesIssued");
|
||||
}
|
||||
if (command.hasParameter("unitPrice")) {
|
||||
f.setUnitPrice(command.bigDecimalValueOfParameterNamed("unitPrice"));
|
||||
@@ -820,7 +827,7 @@ public final class ProductPendingEditFieldMapper {
|
||||
return;
|
||||
}
|
||||
putNumber(root, submitted, "totalShares", f.getTotalShares());
|
||||
putNumber(root, submitted, "totalSharesIssued", f.getTotalSharesIssued());
|
||||
putNumber(root, submitted, "sharesIssued", f.getTotalSharesIssued());
|
||||
putNumber(root, submitted, "unitPrice", f.getUnitPrice());
|
||||
putNumber(root, submitted, "minimumShares", f.getMinimumShares());
|
||||
putNumber(root, submitted, "nominalShares", f.getNominalShares());
|
||||
|
||||
Reference in New Issue
Block a user