DDS: Agent settlement (Agent Tran), Close Party account, reports (Supplementary-DDS/Master Data Export/Close Register), Party-Savings link, product category field
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (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 (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (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 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 Documentation build / build (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 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 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 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 Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (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 (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (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 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 Documentation build / build (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 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 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 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
This commit is contained in:
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.organisation.agent.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.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.agent.data.AgentData;
|
||||
import org.apache.fineract.organisation.agent.service.AgentReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/dds/agents")
|
||||
@Component
|
||||
@Tag(name = "DDS Agents", description = "A DDS collection agent - either a person or, at the top of the hierarchy, the bank itself - with its own settlement savings account and guarantors.")
|
||||
@RequiredArgsConstructor
|
||||
public class AgentsApiResource {
|
||||
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "AGENT";
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final AgentReadPlatformService readPlatformService;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve DDS Agents", description = "Returns the list of DDS agents.")
|
||||
public Collection<AgentData> retrieveAll() {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveAll();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{agentId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve a DDS Agent", description = "Returns the details of a DDS agent, including its guarantors.")
|
||||
public AgentData retrieveOne(@PathParam("agentId") @Parameter(description = "agentId") final Long agentId) {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveOne(agentId);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create a DDS Agent", description = "Mandatory fields: clientId, agentNameId, agentAddress1, agentCity, "
|
||||
+ "agentPinCode, agentSavingAccountType, agentSavingAccountNo, agentOpeningDate, agentBirthDate, homeBranch, "
|
||||
+ "savingBranch, tdsApplicable, commissionCalculation\n" + "\n"
|
||||
+ "Optional fields: agentAddress2, agentTelephone, agentOpeningBalance, tdsPercent, transferBranch, "
|
||||
+ "transferAmountAccountType, transferAccountNo, limit, machineRent, agentSecurityBranch, agentSecurityAccountType, "
|
||||
+ "agentSecurityAccountNo, otherCommission, agentDeposit, festivalAdvance, otherDeduction, limits, guarantors")
|
||||
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.createAgent() //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{agentId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update a DDS Agent", description = "Updates the details of a DDS agent.")
|
||||
public CommandProcessingResult update(@PathParam("agentId") @Parameter(description = "agentId") final Long agentId,
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.updateAgent(agentId) //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.organisation.agent.domain;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface AgentRepository extends JpaRepository<Agent, Long> {
|
||||
|
||||
@Query("select coalesce(max(cast(substring(a.agentCode, 3) as long)), 0) from Agent a where a.agentCode like 'AG%'")
|
||||
Long retrieveMaxAgentCodeSuffix();
|
||||
|
||||
Optional<Agent> findByAgentCode(String agentCode);
|
||||
|
||||
@Query("select a from Agent a where a.id between :agentFrom and :agentTo")
|
||||
List<Agent> findBetweenIds(@Param("agentFrom") Long agentFrom, @Param("agentTo") Long agentTo);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.organisation.agent.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.organisation.agent.service.AgentWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "AGENT", action = "CREATE")
|
||||
public class CreateAgentCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final AgentWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreateAgentCommandHandler(final AgentWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.createAgent(command);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.organisation.agent.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.organisation.agent.service.AgentWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "AGENT", action = "UPDATE")
|
||||
public class UpdateAgentCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final AgentWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdateAgentCommandHandler(final AgentWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.updateAgent(command.entityId(), command);
|
||||
}
|
||||
}
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* 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.organisation.agent.serialization;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Validates create/update commands for an Agent - the DDS collection agent. As of 2026-08-24, Agent
|
||||
* does the daily collection work directly (the SubAgent tier was removed), so this deserializer carries
|
||||
* the collection/commission/limit/security-account fields SubAgent used to own, alongside Agent's
|
||||
* original identity/settlement-account fields.
|
||||
*/
|
||||
@Component
|
||||
public final class AgentCommandFromApiJsonDeserializer {
|
||||
|
||||
public static final String CLIENT_ID = "clientId";
|
||||
public static final String AGENT_NAME_ID = "agentNameId";
|
||||
public static final String AGENT_ADDRESS1 = "agentAddress1";
|
||||
public static final String AGENT_ADDRESS2 = "agentAddress2";
|
||||
public static final String AGENT_CITY = "agentCity";
|
||||
public static final String AGENT_PINCODE = "agentPinCode";
|
||||
public static final String AGENT_TELEPHONE = "agentTelephone";
|
||||
public static final String AGENT_SAVING_ACCOUNT_TYPE = "agentSavingAccountType";
|
||||
public static final String AGENT_SAVING_ACCOUNT_NO = "agentSavingAccountNo";
|
||||
public static final String AGENT_OPENING_DATE = "agentOpeningDate";
|
||||
public static final String AGENT_BIRTH_DATE = "agentBirthDate";
|
||||
public static final String AGENT_OPENING_BALANCE = "agentOpeningBalance";
|
||||
public static final String HOME_BRANCH = "homeBranch";
|
||||
public static final String SAVING_BRANCH = "savingBranch";
|
||||
public static final String TDS_APPLICABLE = "tdsApplicable";
|
||||
public static final String TDS_PERCENT = "tdsPercent";
|
||||
public static final String TRANSFER_BRANCH = "transferBranch";
|
||||
public static final String TRANSFER_AMOUNT_ACCOUNT_TYPE = "transferAmountAccountType";
|
||||
public static final String TRANSFER_ACCOUNT_NO = "transferAccountNo";
|
||||
public static final String COMMISSION_CALCULATION = "commissionCalculation";
|
||||
public static final String LIMIT = "limit";
|
||||
public static final String MACHINE_RENT = "machineRent";
|
||||
public static final String AGENT_SECURITY_BRANCH = "agentSecurityBranch";
|
||||
public static final String AGENT_SECURITY_ACCOUNT_TYPE = "agentSecurityAccountType";
|
||||
public static final String AGENT_SECURITY_ACCOUNT_NO = "agentSecurityAccountNo";
|
||||
public static final String OTHER_COMMISSION = "otherCommission";
|
||||
public static final String AGENT_DEPOSIT = "agentDeposit";
|
||||
public static final String FESTIVAL_ADVANCE = "festivalAdvance";
|
||||
public static final String OTHER_DEDUCTION = "otherDeduction";
|
||||
public static final String LIMITS = "limits";
|
||||
public static final String GUARANTORS = "guarantors";
|
||||
public static final String DATE_FORMAT = "dateFormat";
|
||||
public static final String LOCALE = "locale";
|
||||
|
||||
private static final String GUARANTOR_CLIENT_ID = "clientId";
|
||||
private static final String GUARANTOR_NAME = "guarantorName";
|
||||
private static final String GUARANTOR_ADDRESS1 = "address1";
|
||||
private static final String GUARANTOR_ADDRESS2 = "address2";
|
||||
private static final String GUARANTOR_CITY = "city";
|
||||
private static final String GUARANTOR_PINCODE = "pinCode";
|
||||
private static final String GUARANTOR_TELEPHONE = "telephone";
|
||||
|
||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList(CLIENT_ID, AGENT_NAME_ID, AGENT_ADDRESS1,
|
||||
AGENT_ADDRESS2, AGENT_CITY, AGENT_PINCODE, AGENT_TELEPHONE, AGENT_SAVING_ACCOUNT_TYPE, AGENT_SAVING_ACCOUNT_NO,
|
||||
AGENT_OPENING_DATE, AGENT_BIRTH_DATE, AGENT_OPENING_BALANCE, HOME_BRANCH, SAVING_BRANCH, TDS_APPLICABLE, TDS_PERCENT,
|
||||
TRANSFER_BRANCH, TRANSFER_AMOUNT_ACCOUNT_TYPE, TRANSFER_ACCOUNT_NO, COMMISSION_CALCULATION, LIMIT, MACHINE_RENT,
|
||||
AGENT_SECURITY_BRANCH, AGENT_SECURITY_ACCOUNT_TYPE, AGENT_SECURITY_ACCOUNT_NO, OTHER_COMMISSION, AGENT_DEPOSIT,
|
||||
FESTIVAL_ADVANCE, OTHER_DEDUCTION, LIMITS, GUARANTORS, DATE_FORMAT, LOCALE));
|
||||
|
||||
private static final Set<String> SUPPORTED_LIMIT_PARAMETERS = new HashSet<>(Arrays.asList("fromDate", "toDate", "limit"));
|
||||
|
||||
private static final Set<String> SUPPORTED_GUARANTOR_PARAMETERS = new HashSet<>(Arrays.asList(GUARANTOR_CLIENT_ID, GUARANTOR_NAME,
|
||||
GUARANTOR_ADDRESS1, GUARANTOR_ADDRESS2, GUARANTOR_CITY, GUARANTOR_PINCODE, GUARANTOR_TELEPHONE));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public AgentCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForCreate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("agent");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
final Long clientId = this.fromApiJsonHelper.extractLongNamed(CLIENT_ID, element);
|
||||
baseDataValidator.reset().parameter(CLIENT_ID).value(clientId).notNull().integerGreaterThanZero();
|
||||
|
||||
final String agentName = this.fromApiJsonHelper.extractStringNamed(AGENT_NAME_ID, element);
|
||||
baseDataValidator.reset().parameter(AGENT_NAME_ID).value(agentName).notBlank().notExceedingLengthOf(100);
|
||||
|
||||
final String agentAddress1 = this.fromApiJsonHelper.extractStringNamed(AGENT_ADDRESS1, element);
|
||||
baseDataValidator.reset().parameter(AGENT_ADDRESS1).value(agentAddress1).notBlank().notExceedingLengthOf(200);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_ADDRESS2, element)) {
|
||||
final String agentAddress2 = this.fromApiJsonHelper.extractStringNamed(AGENT_ADDRESS2, element);
|
||||
baseDataValidator.reset().parameter(AGENT_ADDRESS2).value(agentAddress2).ignoreIfNull().notExceedingLengthOf(200);
|
||||
}
|
||||
|
||||
final String agentCity = this.fromApiJsonHelper.extractStringNamed(AGENT_CITY, element);
|
||||
baseDataValidator.reset().parameter(AGENT_CITY).value(agentCity).notBlank().notExceedingLengthOf(100);
|
||||
|
||||
final String agentPinCode = this.fromApiJsonHelper.extractStringNamed(AGENT_PINCODE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_PINCODE).value(agentPinCode).notBlank().notExceedingLengthOf(20);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_TELEPHONE, element)) {
|
||||
final String agentTelephone = this.fromApiJsonHelper.extractStringNamed(AGENT_TELEPHONE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_TELEPHONE).value(agentTelephone).ignoreIfNull().notExceedingLengthOf(20);
|
||||
}
|
||||
|
||||
final String agentSavingAccountType = this.fromApiJsonHelper.extractStringNamed(AGENT_SAVING_ACCOUNT_TYPE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_SAVING_ACCOUNT_TYPE).value(agentSavingAccountType).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
|
||||
final String agentSavingAccountNo = this.fromApiJsonHelper.extractStringNamed(AGENT_SAVING_ACCOUNT_NO, element);
|
||||
baseDataValidator.reset().parameter(AGENT_SAVING_ACCOUNT_NO).value(agentSavingAccountNo).notBlank().notExceedingLengthOf(50);
|
||||
|
||||
final var agentOpeningDate = this.fromApiJsonHelper.extractLocalDateNamed(AGENT_OPENING_DATE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_OPENING_DATE).value(agentOpeningDate).notNull();
|
||||
|
||||
final var agentBirthDate = this.fromApiJsonHelper.extractLocalDateNamed(AGENT_BIRTH_DATE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_BIRTH_DATE).value(agentBirthDate).notNull();
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_OPENING_BALANCE, element)) {
|
||||
final var agentOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(AGENT_OPENING_BALANCE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_OPENING_BALANCE).value(agentOpeningBalance).ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
final String homeBranch = this.fromApiJsonHelper.extractStringNamed(HOME_BRANCH, element);
|
||||
baseDataValidator.reset().parameter(HOME_BRANCH).value(homeBranch).notBlank().notExceedingLengthOf(100);
|
||||
|
||||
final String savingBranch = this.fromApiJsonHelper.extractStringNamed(SAVING_BRANCH, element);
|
||||
baseDataValidator.reset().parameter(SAVING_BRANCH).value(savingBranch).notBlank().notExceedingLengthOf(100);
|
||||
|
||||
baseDataValidator.reset().parameter(TDS_APPLICABLE).trueOrFalseRequired(this.fromApiJsonHelper.extractStringNamed(TDS_APPLICABLE,
|
||||
element));
|
||||
baseDataValidator.reset().parameter(COMMISSION_CALCULATION)
|
||||
.trueOrFalseRequired(this.fromApiJsonHelper.extractStringNamed(COMMISSION_CALCULATION, element));
|
||||
|
||||
validateOptionalStrings(element, baseDataValidator);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(LIMITS, element)) {
|
||||
validateLimits(element.getAsJsonObject(), dataValidationErrors);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(GUARANTORS, element)) {
|
||||
validateGuarantors(element.getAsJsonObject(), dataValidationErrors);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("agent");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(CLIENT_ID, element)) {
|
||||
final Long clientId = this.fromApiJsonHelper.extractLongNamed(CLIENT_ID, element);
|
||||
baseDataValidator.reset().parameter(CLIENT_ID).value(clientId).notNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_NAME_ID, element)) {
|
||||
final String agentName = this.fromApiJsonHelper.extractStringNamed(AGENT_NAME_ID, element);
|
||||
baseDataValidator.reset().parameter(AGENT_NAME_ID).value(agentName).notBlank().notExceedingLengthOf(100);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_ADDRESS1, element)) {
|
||||
final String agentAddress1 = this.fromApiJsonHelper.extractStringNamed(AGENT_ADDRESS1, element);
|
||||
baseDataValidator.reset().parameter(AGENT_ADDRESS1).value(agentAddress1).notBlank().notExceedingLengthOf(200);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_ADDRESS2, element)) {
|
||||
final String agentAddress2 = this.fromApiJsonHelper.extractStringNamed(AGENT_ADDRESS2, element);
|
||||
baseDataValidator.reset().parameter(AGENT_ADDRESS2).value(agentAddress2).ignoreIfNull().notExceedingLengthOf(200);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_CITY, element)) {
|
||||
final String agentCity = this.fromApiJsonHelper.extractStringNamed(AGENT_CITY, element);
|
||||
baseDataValidator.reset().parameter(AGENT_CITY).value(agentCity).notBlank().notExceedingLengthOf(100);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_PINCODE, element)) {
|
||||
final String agentPinCode = this.fromApiJsonHelper.extractStringNamed(AGENT_PINCODE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_PINCODE).value(agentPinCode).notBlank().notExceedingLengthOf(20);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_TELEPHONE, element)) {
|
||||
final String agentTelephone = this.fromApiJsonHelper.extractStringNamed(AGENT_TELEPHONE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_TELEPHONE).value(agentTelephone).ignoreIfNull().notExceedingLengthOf(20);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SAVING_ACCOUNT_TYPE, element)) {
|
||||
final String agentSavingAccountType = this.fromApiJsonHelper.extractStringNamed(AGENT_SAVING_ACCOUNT_TYPE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_SAVING_ACCOUNT_TYPE).value(agentSavingAccountType).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SAVING_ACCOUNT_NO, element)) {
|
||||
final String agentSavingAccountNo = this.fromApiJsonHelper.extractStringNamed(AGENT_SAVING_ACCOUNT_NO, element);
|
||||
baseDataValidator.reset().parameter(AGENT_SAVING_ACCOUNT_NO).value(agentSavingAccountNo).notBlank().notExceedingLengthOf(50);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_OPENING_DATE, element)) {
|
||||
final var agentOpeningDate = this.fromApiJsonHelper.extractLocalDateNamed(AGENT_OPENING_DATE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_OPENING_DATE).value(agentOpeningDate).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_BIRTH_DATE, element)) {
|
||||
final var agentBirthDate = this.fromApiJsonHelper.extractLocalDateNamed(AGENT_BIRTH_DATE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_BIRTH_DATE).value(agentBirthDate).notNull();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_OPENING_BALANCE, element)) {
|
||||
final var agentOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(AGENT_OPENING_BALANCE, element);
|
||||
baseDataValidator.reset().parameter(AGENT_OPENING_BALANCE).value(agentOpeningBalance).ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(HOME_BRANCH, element)) {
|
||||
final String homeBranch = this.fromApiJsonHelper.extractStringNamed(HOME_BRANCH, element);
|
||||
baseDataValidator.reset().parameter(HOME_BRANCH).value(homeBranch).notBlank().notExceedingLengthOf(100);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(SAVING_BRANCH, element)) {
|
||||
final String savingBranch = this.fromApiJsonHelper.extractStringNamed(SAVING_BRANCH, element);
|
||||
baseDataValidator.reset().parameter(SAVING_BRANCH).value(savingBranch).notBlank().notExceedingLengthOf(100);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(TDS_APPLICABLE, element)) {
|
||||
baseDataValidator.reset().parameter(TDS_APPLICABLE)
|
||||
.trueOrFalseRequired(this.fromApiJsonHelper.extractStringNamed(TDS_APPLICABLE, element));
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(COMMISSION_CALCULATION, element)) {
|
||||
baseDataValidator.reset().parameter(COMMISSION_CALCULATION)
|
||||
.trueOrFalseRequired(this.fromApiJsonHelper.extractStringNamed(COMMISSION_CALCULATION, element));
|
||||
}
|
||||
|
||||
validateOptionalStrings(element, baseDataValidator);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(LIMITS, element)) {
|
||||
validateLimits(element.getAsJsonObject(), dataValidationErrors);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(GUARANTORS, element)) {
|
||||
validateGuarantors(element.getAsJsonObject(), dataValidationErrors);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void validateOptionalStrings(final JsonElement element, final DataValidatorBuilder v) {
|
||||
if (this.fromApiJsonHelper.parameterExists(TDS_PERCENT, element)) {
|
||||
v.reset().parameter(TDS_PERCENT).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(TDS_PERCENT, element))
|
||||
.ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(TRANSFER_BRANCH, element)) {
|
||||
v.reset().parameter(TRANSFER_BRANCH).value(this.fromApiJsonHelper.extractStringNamed(TRANSFER_BRANCH, element))
|
||||
.ignoreIfNull().notExceedingLengthOf(100);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(TRANSFER_AMOUNT_ACCOUNT_TYPE, element)) {
|
||||
v.reset().parameter(TRANSFER_AMOUNT_ACCOUNT_TYPE)
|
||||
.value(this.fromApiJsonHelper.extractStringNamed(TRANSFER_AMOUNT_ACCOUNT_TYPE, element)).ignoreIfNull()
|
||||
.notExceedingLengthOf(100);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(TRANSFER_ACCOUNT_NO, element)) {
|
||||
v.reset().parameter(TRANSFER_ACCOUNT_NO).value(this.fromApiJsonHelper.extractStringNamed(TRANSFER_ACCOUNT_NO, element))
|
||||
.ignoreIfNull().notExceedingLengthOf(50);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(LIMIT, element)) {
|
||||
v.reset().parameter(LIMIT).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LIMIT, element)).ignoreIfNull()
|
||||
.zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(MACHINE_RENT, element)) {
|
||||
v.reset().parameter(MACHINE_RENT).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(MACHINE_RENT, element))
|
||||
.ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SECURITY_BRANCH, element)) {
|
||||
v.reset().parameter(AGENT_SECURITY_BRANCH).value(this.fromApiJsonHelper.extractStringNamed(AGENT_SECURITY_BRANCH, element))
|
||||
.ignoreIfNull().notExceedingLengthOf(100);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SECURITY_ACCOUNT_TYPE, element)) {
|
||||
v.reset().parameter(AGENT_SECURITY_ACCOUNT_TYPE)
|
||||
.value(this.fromApiJsonHelper.extractStringNamed(AGENT_SECURITY_ACCOUNT_TYPE, element)).ignoreIfNull()
|
||||
.notExceedingLengthOf(100);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SECURITY_ACCOUNT_NO, element)) {
|
||||
v.reset().parameter(AGENT_SECURITY_ACCOUNT_NO)
|
||||
.value(this.fromApiJsonHelper.extractStringNamed(AGENT_SECURITY_ACCOUNT_NO, element)).ignoreIfNull()
|
||||
.notExceedingLengthOf(50);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(OTHER_COMMISSION, element)) {
|
||||
v.reset().parameter(OTHER_COMMISSION).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(OTHER_COMMISSION, element))
|
||||
.ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(AGENT_DEPOSIT, element)) {
|
||||
v.reset().parameter(AGENT_DEPOSIT).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(AGENT_DEPOSIT, element))
|
||||
.ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(FESTIVAL_ADVANCE, element)) {
|
||||
v.reset().parameter(FESTIVAL_ADVANCE).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(FESTIVAL_ADVANCE, element))
|
||||
.ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(OTHER_DEDUCTION, element)) {
|
||||
v.reset().parameter(OTHER_DEDUCTION).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(OTHER_DEDUCTION, element))
|
||||
.ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLimits(final JsonObject topLevelJson, final List<ApiParameterError> dataValidationErrors) {
|
||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("agent.limit");
|
||||
final var limitsArray = topLevelJson.getAsJsonArray(LIMITS);
|
||||
for (int i = 0; i < limitsArray.size(); i++) {
|
||||
final JsonObject limitJson = limitsArray.get(i).getAsJsonObject();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(limitJson, SUPPORTED_LIMIT_PARAMETERS);
|
||||
|
||||
v.reset().parameterAtIndexArray("fromDate", i).value(extractString(limitJson, "fromDate")).notBlank();
|
||||
v.reset().parameterAtIndexArray("toDate", i).value(extractString(limitJson, "toDate")).notBlank();
|
||||
v.reset().parameterAtIndexArray("limit", i).value(extractBigDecimal(limitJson, "limit")).notNull().zeroOrPositiveAmount();
|
||||
}
|
||||
}
|
||||
|
||||
private void validateGuarantors(final JsonObject topLevelJson, final List<ApiParameterError> dataValidationErrors) {
|
||||
final DataValidatorBuilder guarantorValidator = new DataValidatorBuilder(dataValidationErrors).resource("agent.guarantor");
|
||||
|
||||
final var guarantorsArray = topLevelJson.getAsJsonArray(GUARANTORS);
|
||||
for (int i = 0; i < guarantorsArray.size(); i++) {
|
||||
final JsonObject guarantorJson = guarantorsArray.get(i).getAsJsonObject();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(guarantorJson, SUPPORTED_GUARANTOR_PARAMETERS);
|
||||
|
||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_CLIENT_ID, i)
|
||||
.value(extractLong(guarantorJson, GUARANTOR_CLIENT_ID)).notNull().integerGreaterThanZero();
|
||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_NAME, i).value(extractString(guarantorJson, GUARANTOR_NAME))
|
||||
.notBlank().notExceedingLengthOf(100);
|
||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_ADDRESS1, i).value(extractString(guarantorJson, GUARANTOR_ADDRESS1))
|
||||
.notBlank().notExceedingLengthOf(200);
|
||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_CITY, i).value(extractString(guarantorJson, GUARANTOR_CITY))
|
||||
.notBlank().notExceedingLengthOf(100);
|
||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_PINCODE, i).value(extractString(guarantorJson, GUARANTOR_PINCODE))
|
||||
.notBlank().notExceedingLengthOf(20);
|
||||
}
|
||||
}
|
||||
|
||||
private String extractString(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
||||
}
|
||||
|
||||
private Long extractLong(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsLong() : null;
|
||||
}
|
||||
|
||||
private java.math.BigDecimal extractBigDecimal(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsBigDecimal() : null;
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.organisation.agent.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.organisation.agent.data.AgentData;
|
||||
|
||||
public interface AgentReadPlatformService {
|
||||
|
||||
Collection<AgentData> retrieveAll();
|
||||
|
||||
AgentData retrieveOne(Long agentId);
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 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.organisation.agent.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.organisation.agent.data.AgentData;
|
||||
import org.apache.fineract.organisation.agent.data.AgentGuarantorData;
|
||||
import org.apache.fineract.organisation.agent.data.AgentLimitData;
|
||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class AgentReadPlatformServiceImpl implements AgentReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static final AgentMapper AGENT_MAPPER = new AgentMapper();
|
||||
private static final AgentLimitMapper LIMIT_MAPPER = new AgentLimitMapper();
|
||||
private static final AgentGuarantorMapper GUARANTOR_MAPPER = new AgentGuarantorMapper();
|
||||
|
||||
private static final class AgentMapper implements RowMapper<AgentData> {
|
||||
|
||||
public String schema() {
|
||||
return " a.id as id, a.client_id as clientId, c.display_name as clientName, a.agent_code as agentCode, "
|
||||
+ " a.agent_name as agentNameId, a.agent_address1 as agentAddress1, a.agent_address2 as agentAddress2, "
|
||||
+ " a.agent_city as agentCity, a.agent_pincode as agentPinCode, a.agent_telephone as agentTelephone, "
|
||||
+ " a.agent_saving_account_type as agentSavingAccountType, a.agent_saving_account_no as agentSavingAccountNo, "
|
||||
+ " a.agent_opening_date as agentOpeningDate, a.agent_birth_date as agentBirthDate, "
|
||||
+ " a.agent_current_balance as agentCurrentBalance, a.agent_opening_balance as agentOpeningBalance, "
|
||||
+ " a.home_branch as homeBranch, a.saving_branch as savingBranch, a.tds_applicable as tdsApplicable, "
|
||||
+ " a.tds_percent as tdsPercent, a.current_tds as currentTds, a.tds_amount as tdsAmount, "
|
||||
+ " a.transfer_branch as transferBranch, a.transfer_amount_account_type as transferAmountAccountType, "
|
||||
+ " a.transfer_account_no as transferAccountNo, a.collector_commission_calculation as commissionCalculation, "
|
||||
+ " a.collector_limit_amount as limitAmount, a.machine_rent as machineRent, "
|
||||
+ " a.agent_security_branch as agentSecurityBranch, a.agent_security_account_type as agentSecurityAccountType, "
|
||||
+ " a.agent_security_account_no as agentSecurityAccountNo, a.commission as commission, "
|
||||
+ " a.other_commission as otherCommission, a.agent_deposit as agentDeposit, "
|
||||
+ " a.festival_advance as festivalAdvance, a.other_deduction as otherDeduction "
|
||||
+ " from m_dds_agent a join m_client c on c.id = a.client_id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
return AgentData.instance(rs.getLong("id"), rs.getLong("clientId"), rs.getString("clientName"), rs.getString("agentCode"),
|
||||
rs.getString("agentNameId"), rs.getString("agentAddress1"), rs.getString("agentAddress2"),
|
||||
rs.getString("agentCity"), rs.getString("agentPinCode"), rs.getString("agentTelephone"),
|
||||
rs.getString("agentSavingAccountType"), rs.getString("agentSavingAccountNo"),
|
||||
JdbcSupport.getLocalDate(rs, "agentOpeningDate"), JdbcSupport.getLocalDate(rs, "agentBirthDate"),
|
||||
rs.getBigDecimal("agentCurrentBalance"), rs.getBigDecimal("agentOpeningBalance"), rs.getString("homeBranch"),
|
||||
rs.getString("savingBranch"), rs.getBoolean("tdsApplicable"), rs.getBigDecimal("tdsPercent"),
|
||||
rs.getBigDecimal("currentTds"), rs.getBigDecimal("tdsAmount"), rs.getString("transferBranch"),
|
||||
rs.getString("transferAmountAccountType"), rs.getString("transferAccountNo"),
|
||||
rs.getBoolean("commissionCalculation"), rs.getBigDecimal("limitAmount"), rs.getBigDecimal("machineRent"),
|
||||
rs.getString("agentSecurityBranch"), rs.getString("agentSecurityAccountType"),
|
||||
rs.getString("agentSecurityAccountNo"), rs.getBigDecimal("commission"), rs.getBigDecimal("otherCommission"),
|
||||
rs.getBigDecimal("agentDeposit"), rs.getBigDecimal("festivalAdvance"), rs.getBigDecimal("otherDeduction"),
|
||||
new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AgentLimitMapper implements RowMapper<Map.Entry<Long, AgentLimitData>> {
|
||||
|
||||
public String schema() {
|
||||
return " l.agent_id as agentId, l.id as id, l.from_date as fromDate, l.to_date as toDate, "
|
||||
+ " l.limit_amount as limitAmount from m_dds_agent_limit l";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<Long, AgentLimitData> mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
final Long agentId = rs.getLong("agentId");
|
||||
final AgentLimitData data = AgentLimitData.instance(rs.getLong("id"), JdbcSupport.getLocalDate(rs, "fromDate"),
|
||||
JdbcSupport.getLocalDate(rs, "toDate"), rs.getBigDecimal("limitAmount"));
|
||||
return Map.entry(agentId, data);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AgentGuarantorMapper implements RowMapper<Map.Entry<Long, AgentGuarantorData>> {
|
||||
|
||||
public String schema() {
|
||||
return " g.agent_id as agentId, g.id as id, g.client_id as clientId, g.guarantor_name as guarantorName, "
|
||||
+ " g.address1 as address1, g.address2 as address2, g.city as city, g.pin_code as pinCode, "
|
||||
+ " g.telephone as telephone from m_dds_agent_guarantor g";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<Long, AgentGuarantorData> mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
final Long agentId = rs.getLong("agentId");
|
||||
final AgentGuarantorData data = AgentGuarantorData.instance(rs.getLong("id"), rs.getLong("clientId"),
|
||||
rs.getString("guarantorName"), rs.getString("address1"), rs.getString("address2"), rs.getString("city"),
|
||||
rs.getString("pinCode"), rs.getString("telephone"));
|
||||
return Map.entry(agentId, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<AgentData> retrieveAll() {
|
||||
final String sql = "select " + AGENT_MAPPER.schema() + " order by a.id";
|
||||
final List<AgentData> agents = this.jdbcTemplate.query(sql, AGENT_MAPPER); // NOSONAR
|
||||
return attachChildren(agents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentData retrieveOne(final Long agentId) {
|
||||
try {
|
||||
final String sql = "select " + AGENT_MAPPER.schema() + " where a.id = ?";
|
||||
final AgentData agent = this.jdbcTemplate.queryForObject(sql, AGENT_MAPPER, agentId); // NOSONAR
|
||||
return attachChildren(List.of(agent)).iterator().next();
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new AgentNotFoundException(agentId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<AgentData> attachChildren(final List<AgentData> agents) {
|
||||
if (agents.isEmpty()) {
|
||||
return agents;
|
||||
}
|
||||
|
||||
final String limitSql = "select " + LIMIT_MAPPER.schema() + " order by l.id";
|
||||
final List<Map.Entry<Long, AgentLimitData>> limitRows = this.jdbcTemplate.query(limitSql, LIMIT_MAPPER); // NOSONAR
|
||||
final Map<Long, List<AgentLimitData>> limitsByAgentId = new LinkedHashMap<>();
|
||||
for (final Map.Entry<Long, AgentLimitData> row : limitRows) {
|
||||
limitsByAgentId.computeIfAbsent(row.getKey(), k -> new ArrayList<>()).add(row.getValue());
|
||||
}
|
||||
|
||||
final String guarantorSql = "select " + GUARANTOR_MAPPER.schema() + " order by g.id";
|
||||
final List<Map.Entry<Long, AgentGuarantorData>> guarantorRows = this.jdbcTemplate.query(guarantorSql, GUARANTOR_MAPPER); // NOSONAR
|
||||
final Map<Long, List<AgentGuarantorData>> guarantorsByAgentId = new LinkedHashMap<>();
|
||||
for (final Map.Entry<Long, AgentGuarantorData> row : guarantorRows) {
|
||||
guarantorsByAgentId.computeIfAbsent(row.getKey(), k -> new ArrayList<>()).add(row.getValue());
|
||||
}
|
||||
|
||||
final List<AgentData> result = new ArrayList<>(agents.size());
|
||||
for (final AgentData agent : agents) {
|
||||
final List<AgentLimitData> limits = limitsByAgentId.getOrDefault(agent.getId(), new ArrayList<>());
|
||||
final List<AgentGuarantorData> guarantors = guarantorsByAgentId.getOrDefault(agent.getId(), new ArrayList<>());
|
||||
result.add(AgentData.instance(agent.getId(), agent.getClientId(), agent.getClientName(), agent.getAgentCode(),
|
||||
agent.getAgentNameId(), agent.getAgentAddress1(), agent.getAgentAddress2(), agent.getAgentCity(),
|
||||
agent.getAgentPinCode(), agent.getAgentTelephone(), agent.getAgentSavingAccountType(),
|
||||
agent.getAgentSavingAccountNo(), agent.getAgentOpeningDate(), agent.getAgentBirthDate(),
|
||||
agent.getAgentCurrentBalance(), agent.getAgentOpeningBalance(), agent.getHomeBranch(), agent.getSavingBranch(),
|
||||
agent.getTdsApplicable(), agent.getTdsPercent(), agent.getCurrentTds(), agent.getTdsAmount(),
|
||||
agent.getTransferBranch(), agent.getTransferAmountAccountType(), agent.getTransferAccountNo(),
|
||||
agent.getCommissionCalculation(), agent.getLimit(), agent.getMachineRent(), agent.getAgentSecurityBranch(),
|
||||
agent.getAgentSecurityAccountType(), agent.getAgentSecurityAccountNo(), agent.getCommission(),
|
||||
agent.getOtherCommission(), agent.getAgentDeposit(), agent.getFestivalAdvance(), agent.getOtherDeduction(),
|
||||
limits, guarantors));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+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.organisation.agent.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface AgentWritePlatformService {
|
||||
|
||||
CommandProcessingResult createAgent(JsonCommand command);
|
||||
|
||||
CommandProcessingResult updateAgent(Long agentId, JsonCommand command);
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* 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.organisation.agent.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
||||
import org.apache.fineract.organisation.agent.domain.AgentGuarantor;
|
||||
import org.apache.fineract.organisation.agent.domain.AgentLimit;
|
||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
||||
import org.apache.fineract.organisation.agent.serialization.AgentCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AgentWritePlatformServiceJpaRepositoryImpl implements AgentWritePlatformService {
|
||||
|
||||
private final AgentCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final AgentRepository agentRepository;
|
||||
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createAgent(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||
|
||||
final Long clientId = command.longValueOfParameterNamed("clientId");
|
||||
final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
||||
|
||||
final String agentCode = generateNextAgentCode();
|
||||
final Agent agent = Agent.fromJson(client, agentCode, command);
|
||||
|
||||
addLimitsFromCommand(agent, command);
|
||||
addGuarantorsFromCommand(agent, command);
|
||||
|
||||
this.agentRepository.saveAndFlush(agent);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(agent.getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleAgentDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleAgentDataIntegrityIssues(throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateAgent(final Long agentId, final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.fromApiJsonDeserializer.validateForUpdate(command.json());
|
||||
|
||||
final Agent agentForUpdate = this.agentRepository.findById(agentId).orElseThrow(() -> new AgentNotFoundException(agentId));
|
||||
final Map<String, Object> changesOnly = agentForUpdate.update(command);
|
||||
|
||||
if (changesOnly.containsKey("clientId")) {
|
||||
final Long newClientId = (Long) changesOnly.get("clientId");
|
||||
final Client newClient = this.clientRepositoryWrapper.findOneWithNotFoundDetection(newClientId);
|
||||
agentForUpdate.changeClient(newClient);
|
||||
}
|
||||
|
||||
if (command.parameterExists("limits")) {
|
||||
agentForUpdate.resetLimits();
|
||||
addLimitsFromCommand(agentForUpdate, command);
|
||||
changesOnly.put("limits", command.jsonFragment("limits"));
|
||||
}
|
||||
|
||||
if (command.parameterExists("guarantors")) {
|
||||
agentForUpdate.resetGuarantors();
|
||||
addGuarantorsFromCommand(agentForUpdate, command);
|
||||
changesOnly.put("guarantors", command.jsonFragment("guarantors"));
|
||||
}
|
||||
|
||||
this.agentRepository.saveAndFlush(agentForUpdate);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(agentId).with(changesOnly)
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleAgentDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleAgentDataIntegrityIssues(throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void addLimitsFromCommand(final Agent agent, final JsonCommand command) {
|
||||
if (!command.parameterExists("limits")) {
|
||||
return;
|
||||
}
|
||||
final JsonArray limitsArray = command.arrayOfParameterNamed("limits");
|
||||
if (limitsArray == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < limitsArray.size(); i++) {
|
||||
final JsonObject limitJson = limitsArray.get(i).getAsJsonObject();
|
||||
final LocalDate fromDate = LocalDate.parse(limitJson.get("fromDate").getAsString());
|
||||
final LocalDate toDate = LocalDate.parse(limitJson.get("toDate").getAsString());
|
||||
final BigDecimal limit = limitJson.get("limit").getAsBigDecimal();
|
||||
agent.addLimit(new AgentLimit(fromDate, toDate, limit));
|
||||
}
|
||||
}
|
||||
|
||||
private void addGuarantorsFromCommand(final Agent agent, final JsonCommand command) {
|
||||
if (!command.parameterExists("guarantors")) {
|
||||
return;
|
||||
}
|
||||
final JsonArray guarantorsArray = command.arrayOfParameterNamed("guarantors");
|
||||
if (guarantorsArray == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < guarantorsArray.size(); i++) {
|
||||
final JsonObject guarantorJson = guarantorsArray.get(i).getAsJsonObject();
|
||||
final Long guarantorClientId = guarantorJson.get("clientId").getAsLong();
|
||||
final String guarantorName = guarantorJson.get("guarantorName").getAsString();
|
||||
final String address1 = guarantorJson.get("address1").getAsString();
|
||||
final String address2 = guarantorJson.has("address2") && !guarantorJson.get("address2").isJsonNull()
|
||||
? guarantorJson.get("address2").getAsString()
|
||||
: null;
|
||||
final String city = guarantorJson.get("city").getAsString();
|
||||
final String pinCode = guarantorJson.get("pinCode").getAsString();
|
||||
final String telephone = guarantorJson.has("telephone") && !guarantorJson.get("telephone").isJsonNull()
|
||||
? guarantorJson.get("telephone").getAsString()
|
||||
: null;
|
||||
|
||||
agent.addGuarantor(new AgentGuarantor(guarantorClientId, guarantorName, address1, address2, city, pinCode, telephone));
|
||||
}
|
||||
}
|
||||
|
||||
private String generateNextAgentCode() {
|
||||
final Long maxSuffix = this.agentRepository.retrieveMaxAgentCodeSuffix();
|
||||
final long nextSuffix = (maxSuffix == null ? 0L : maxSuffix) + 1L;
|
||||
return String.format("AG%04d", nextSuffix);
|
||||
}
|
||||
|
||||
private void handleAgentDataIntegrityIssues(final Throwable realCause, final Exception dve) {
|
||||
if (realCause.getMessage() != null && realCause.getMessage().contains("agent_code")) {
|
||||
throw new PlatformDataIntegrityException("error.msg.agent.duplicate.agentCode", "An agent with the generated code already exists, please retry");
|
||||
}
|
||||
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.agent.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.organisation.agent.starter;
|
||||
|
||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
||||
import org.apache.fineract.organisation.agent.serialization.AgentCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.organisation.agent.service.AgentReadPlatformService;
|
||||
import org.apache.fineract.organisation.agent.service.AgentReadPlatformServiceImpl;
|
||||
import org.apache.fineract.organisation.agent.service.AgentWritePlatformService;
|
||||
import org.apache.fineract.organisation.agent.service.AgentWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class OrganisationAgentConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AgentReadPlatformService.class)
|
||||
public AgentReadPlatformService agentReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new AgentReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AgentWritePlatformService.class)
|
||||
public AgentWritePlatformService agentWritePlatformService(AgentCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||
AgentRepository agentRepository, ClientRepositoryWrapper clientRepositoryWrapper) {
|
||||
return new AgentWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, agentRepository, clientRepositoryWrapper);
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.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.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.commissionslab.data.CommissionSlabData;
|
||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/dds/commissionslabs")
|
||||
@Component
|
||||
@Tag(name = "DDS Commission Slabs", description = "The agent commission chart: ACCOUNT slabs (commission percentage on collection volume) and COMMISSION slabs (the override a senior agent earns on a sub-agent's commission). Append-only reference data - no update or delete API.")
|
||||
@RequiredArgsConstructor
|
||||
public class CommissionSlabsApiResource {
|
||||
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "COMMISSIONSLAB";
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final CommissionSlabReadPlatformService readPlatformService;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve DDS Commission Slabs", description = "Returns the full commission slab chart.")
|
||||
public Collection<CommissionSlabData> retrieveAll() {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveAll();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Add DDS Commission Slabs", description = "Mandatory fields: slabs (array of {slabType: ACCOUNT|COMMISSION, "
|
||||
+ "slabFrom, slabTo, commissionPercent}). Adds new slab rows to the chart - it does not replace or edit existing rows.")
|
||||
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.createCommissionSlabs() //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface CommissionSlabRepository extends JpaRepository<CommissionSlab, Long> {
|
||||
|
||||
@Query("select s from CommissionSlab s where s.slabType = :slabType and :amount between s.slabFrom and s.slabTo")
|
||||
List<CommissionSlab> findMatchingSlabs(@Param("slabType") String slabType, @Param("amount") BigDecimal amount);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "COMMISSIONSLAB", action = "CREATE")
|
||||
public class CreateCommissionSlabsCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final CommissionSlabWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreateCommissionSlabsCommandHandler(final CommissionSlabWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.createCommissionSlabs(command);
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.serialization;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public final class CommissionSlabCommandFromApiJsonDeserializer {
|
||||
|
||||
private static final Set<String> SUPPORTED_SLAB_TYPES = new HashSet<>(Arrays.asList("ACCOUNT", "COMMISSION"));
|
||||
|
||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("slabs", "dateFormat", "locale"));
|
||||
|
||||
private static final Set<String> SUPPORTED_SLAB_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList("slabType", "slabFrom", "slabTo", "commissionPercent"));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public CommissionSlabCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForCreate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("commissionslab");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
if (!this.fromApiJsonHelper.parameterExists("slabs", element)) {
|
||||
v.reset().parameter("slabs").value(null).notNull();
|
||||
} else {
|
||||
final JsonObject topLevel = element.getAsJsonObject();
|
||||
final var slabsArray = topLevel.getAsJsonArray("slabs");
|
||||
v.reset().parameter("slabs").value(slabsArray).jsonArrayNotEmpty();
|
||||
|
||||
for (int i = 0; i < slabsArray.size(); i++) {
|
||||
final JsonObject slabJson = slabsArray.get(i).getAsJsonObject();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(slabJson, SUPPORTED_SLAB_PARAMETERS);
|
||||
|
||||
final String slabType = extractString(slabJson, "slabType");
|
||||
v.reset().parameterAtIndexArray("slabType", i).value(slabType).notBlank()
|
||||
.isOneOfTheseStringValues(SUPPORTED_SLAB_TYPES.toArray());
|
||||
v.reset().parameterAtIndexArray("slabFrom", i).value(extractBigDecimal(slabJson, "slabFrom")).notNull()
|
||||
.zeroOrPositiveAmount();
|
||||
v.reset().parameterAtIndexArray("slabTo", i).value(extractBigDecimal(slabJson, "slabTo")).notNull()
|
||||
.zeroOrPositiveAmount();
|
||||
v.reset().parameterAtIndexArray("commissionPercent", i).value(extractBigDecimal(slabJson, "commissionPercent"))
|
||||
.notNull().zeroOrPositiveAmount();
|
||||
}
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private String extractString(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
||||
}
|
||||
|
||||
private BigDecimal extractBigDecimal(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsBigDecimal() : null;
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.organisation.commissionslab.data.CommissionSlabData;
|
||||
|
||||
public interface CommissionSlabReadPlatformService {
|
||||
|
||||
Collection<CommissionSlabData> retrieveAll();
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.organisation.commissionslab.data.CommissionSlabData;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class CommissionSlabReadPlatformServiceImpl implements CommissionSlabReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static final CommissionSlabMapper MAPPER = new CommissionSlabMapper();
|
||||
|
||||
private static final class CommissionSlabMapper implements RowMapper<CommissionSlabData> {
|
||||
|
||||
public String schema() {
|
||||
return " s.id as id, s.slab_type as slabType, s.slab_from as slabFrom, s.slab_to as slabTo, "
|
||||
+ " s.commission_percent as commissionPercent from m_dds_commission_slab s";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommissionSlabData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
return CommissionSlabData.builder() //
|
||||
.id(rs.getLong("id")) //
|
||||
.slabType(rs.getString("slabType")) //
|
||||
.slabFrom(rs.getBigDecimal("slabFrom")) //
|
||||
.slabTo(rs.getBigDecimal("slabTo")) //
|
||||
.commissionPercent(rs.getBigDecimal("commissionPercent")) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CommissionSlabData> retrieveAll() {
|
||||
final String sql = "select " + MAPPER.schema() + " order by s.slab_type, s.slab_from";
|
||||
return this.jdbcTemplate.query(sql, MAPPER); // NOSONAR
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface CommissionSlabWritePlatformService {
|
||||
|
||||
CommandProcessingResult createCommissionSlabs(JsonCommand command);
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlab;
|
||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlabRepository;
|
||||
import org.apache.fineract.organisation.commissionslab.serialization.CommissionSlabCommandFromApiJsonDeserializer;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CommissionSlabWritePlatformServiceJpaRepositoryImpl implements CommissionSlabWritePlatformService {
|
||||
|
||||
private final CommissionSlabCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final CommissionSlabRepository commissionSlabRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createCommissionSlabs(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||
|
||||
final List<CommissionSlab> slabs = new ArrayList<>();
|
||||
final JsonArray slabsArray = command.arrayOfParameterNamed("slabs");
|
||||
for (int i = 0; i < slabsArray.size(); i++) {
|
||||
final JsonObject slabJson = slabsArray.get(i).getAsJsonObject();
|
||||
slabs.add(new CommissionSlab(slabJson.get("slabType").getAsString(), slabJson.get("slabFrom").getAsBigDecimal(),
|
||||
slabJson.get("slabTo").getAsBigDecimal(), slabJson.get("commissionPercent").getAsBigDecimal()));
|
||||
}
|
||||
|
||||
this.commissionSlabRepository.saveAllAndFlush(slabs);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(slabs.isEmpty() ? null : slabs.get(0).getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.commissionslab.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + ExceptionUtils.getRootCauseMessage(dve));
|
||||
} catch (final PersistenceException dve) {
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.commissionslab.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + ExceptionUtils.getRootCauseMessage(dve));
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.organisation.commissionslab.starter;
|
||||
|
||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlabRepository;
|
||||
import org.apache.fineract.organisation.commissionslab.serialization.CommissionSlabCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabReadPlatformService;
|
||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabReadPlatformServiceImpl;
|
||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabWritePlatformService;
|
||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class OrganisationCommissionSlabConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CommissionSlabReadPlatformService.class)
|
||||
public CommissionSlabReadPlatformService commissionSlabReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new CommissionSlabReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CommissionSlabWritePlatformService.class)
|
||||
public CommissionSlabWritePlatformService commissionSlabWritePlatformService(
|
||||
CommissionSlabCommandFromApiJsonDeserializer fromApiJsonDeserializer, CommissionSlabRepository commissionSlabRepository) {
|
||||
return new CommissionSlabWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, commissionSlabRepository);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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.dds.batch.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.MediaType;
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.dds.batch.data.BatchRunData;
|
||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/dds/batchruns")
|
||||
@Component
|
||||
@Tag(name = "DDS Batch Runs", description = "The Interest/Commission calculate-then-post batch: Generate previews a run's rows without changing any balance; Post applies exactly those previewed rows. A run can only be posted once.")
|
||||
@RequiredArgsConstructor
|
||||
public class BatchRunsApiResource {
|
||||
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "DDSBATCH";
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final BatchRunReadPlatformService readPlatformService;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve DDS Batch Runs", description = "Returns the list of DDS batch runs, most recent first.")
|
||||
public Collection<BatchRunData> retrieveAll() {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveAll();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{batchRunId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve a DDS Batch Run", description = "Returns the details of a batch run, including its previewed/posted rows.")
|
||||
public BatchRunData retrieveOne(@PathParam("batchRunId") @Parameter(description = "batchRunId") final Long batchRunId) {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveOne(batchRunId);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Generate a DDS Batch Run", description = "Mandatory fields: jobType (INTEREST_CALCULATION, "
|
||||
+ "OD_INTEREST_CALCULATION or COMMISSION_CALCULATION), branchScope (All or Selected), jobDate, agentFrom, agentTo\n"
|
||||
+ "\n" + "Optional fields: partyFrom, partyTo (interest jobs only)")
|
||||
public CommandProcessingResult generate(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.generateDdsBatchRun() //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{batchRunId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Post a DDS Batch Run", description = "Applies exactly the rows a prior Generate call previewed: credits "
|
||||
+ "each row's amount to the Party (interest jobs) or to the Agent's running commission total (commission jobs). "
|
||||
+ "Fails if the run has already been posted.")
|
||||
public CommandProcessingResult post(@PathParam("batchRunId") @Parameter(description = "batchRunId") final Long batchRunId,
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.postDdsBatchRun(batchRunId) //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.dds.batch.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface BatchRunRepository extends JpaRepository<BatchRun, Long> {
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.dds.batch.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "DDSBATCH", action = "GENERATE")
|
||||
public class GenerateBatchRunCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final BatchRunWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public GenerateBatchRunCommandHandler(final BatchRunWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.generateBatchRun(command);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.dds.batch.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "DDSBATCH", action = "POST")
|
||||
public class PostBatchRunCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final BatchRunWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public PostBatchRunCommandHandler(final BatchRunWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.postBatchRun(command.entityId(), command);
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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.dds.batch.serialization;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public final class BatchRunCommandFromApiJsonDeserializer {
|
||||
|
||||
public static final Set<String> SUPPORTED_JOB_TYPES = new HashSet<>(
|
||||
Arrays.asList("INTEREST_CALCULATION", "OD_INTEREST_CALCULATION", "COMMISSION_CALCULATION"));
|
||||
|
||||
private static final Set<String> SUPPORTED_BRANCH_SCOPES = new HashSet<>(Arrays.asList("All", "Selected"));
|
||||
|
||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("jobType", "branchScope", "officeNames",
|
||||
"jobDate", "agentFrom", "agentTo", "partyFrom", "partyTo", "dateFormat", "locale"));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public BatchRunCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForGenerate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("ddsbatchrun");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
v.reset().parameter("jobType").value(this.fromApiJsonHelper.extractStringNamed("jobType", element)).notBlank()
|
||||
.isOneOfTheseStringValues(SUPPORTED_JOB_TYPES.toArray());
|
||||
final String branchScope = this.fromApiJsonHelper.extractStringNamed("branchScope", element);
|
||||
v.reset().parameter("branchScope").value(branchScope).notBlank().isOneOfTheseStringValues(SUPPORTED_BRANCH_SCOPES.toArray());
|
||||
|
||||
if ("Selected".equals(branchScope)) {
|
||||
v.reset().parameter("officeNames").value(this.fromApiJsonHelper.extractJsonArrayNamed("officeNames", element))
|
||||
.jsonArrayNotEmpty();
|
||||
}
|
||||
|
||||
v.reset().parameter("jobDate").value(this.fromApiJsonHelper.extractLocalDateNamed("jobDate", element)).notNull();
|
||||
v.reset().parameter("agentFrom").value(this.fromApiJsonHelper.extractLongNamed("agentFrom", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
v.reset().parameter("agentTo").value(this.fromApiJsonHelper.extractLongNamed("agentTo", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists("partyFrom", element)) {
|
||||
v.reset().parameter("partyFrom").value(this.fromApiJsonHelper.extractLongNamed("partyFrom", element)).ignoreIfNull()
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("partyTo", element)) {
|
||||
v.reset().parameter("partyTo").value(this.fromApiJsonHelper.extractLongNamed("partyTo", element)).ignoreIfNull()
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.dds.batch.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.portfolio.dds.batch.data.BatchRunData;
|
||||
|
||||
public interface BatchRunReadPlatformService {
|
||||
|
||||
Collection<BatchRunData> retrieveAll();
|
||||
|
||||
BatchRunData retrieveOne(Long batchRunId);
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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.dds.batch.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.portfolio.dds.batch.data.BatchRunData;
|
||||
import org.apache.fineract.portfolio.dds.batch.data.BatchRunRowData;
|
||||
import org.apache.fineract.portfolio.dds.batch.exception.BatchRunNotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class BatchRunReadPlatformServiceImpl implements BatchRunReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static final BatchRunMapper BATCH_RUN_MAPPER = new BatchRunMapper();
|
||||
private static final BatchRunRowMapper ROW_MAPPER = new BatchRunRowMapper();
|
||||
|
||||
private static final class BatchRunMapper implements RowMapper<BatchRunData> {
|
||||
|
||||
public String schema() {
|
||||
return " b.id as id, b.job_type as jobType, b.branch_scope as branchScope, b.job_date as jobDate, "
|
||||
+ " b.agent_from as agentFrom, b.agent_to as agentTo, b.party_from as partyFrom, "
|
||||
+ " b.party_to as partyTo, b.status as status, b.total_rows as totalRows, b.total_amount as totalAmount, "
|
||||
+ " b.generated_on_date as generatedOnDate, b.posted_on_date as postedOnDate from m_dds_batch_run b";
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchRunData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
return BatchRunData.builder() //
|
||||
.id(rs.getLong("id")) //
|
||||
.jobType(rs.getString("jobType")) //
|
||||
.branchScope(rs.getString("branchScope")) //
|
||||
.jobDate(JdbcSupport.getLocalDate(rs, "jobDate")) //
|
||||
.agentFrom(rs.getLong("agentFrom")) //
|
||||
.agentTo(rs.getLong("agentTo")) //
|
||||
.partyFrom(JdbcSupport.getLong(rs, "partyFrom")) //
|
||||
.partyTo(JdbcSupport.getLong(rs, "partyTo")) //
|
||||
.status(rs.getString("status")) //
|
||||
.totalRows(rs.getInt("totalRows")) //
|
||||
.totalAmount(rs.getBigDecimal("totalAmount")) //
|
||||
.generatedOnDate(JdbcSupport.getLocalDate(rs, "generatedOnDate")) //
|
||||
.postedOnDate(JdbcSupport.getLocalDate(rs, "postedOnDate")) //
|
||||
.rows(new ArrayList<>()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BatchRunRowMapper implements RowMapper<Map.Entry<Long, BatchRunRowData>> {
|
||||
|
||||
public String schema() {
|
||||
return " r.batch_run_id as batchRunId, r.id as id, r.party_id as partyId, p.party_code as partyCode, "
|
||||
+ " p.party_name as partyName, r.agent_id as agentId, a.agent_code as agentCode, "
|
||||
+ " r.amount as amount from m_dds_batch_run_row r " + " left join m_dds_party p on p.id = r.party_id "
|
||||
+ " join m_dds_agent a on a.id = r.agent_id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<Long, BatchRunRowData> mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
final Long batchRunId = rs.getLong("batchRunId");
|
||||
final BatchRunRowData data = BatchRunRowData.builder() //
|
||||
.id(rs.getLong("id")) //
|
||||
.partyId(JdbcSupport.getLong(rs, "partyId")) //
|
||||
.partyCode(rs.getString("partyCode")) //
|
||||
.partyName(rs.getString("partyName")) //
|
||||
.agentId(rs.getLong("agentId")) //
|
||||
.agentCode(rs.getString("agentCode")) //
|
||||
.amount(rs.getBigDecimal("amount")) //
|
||||
.build();
|
||||
return Map.entry(batchRunId, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BatchRunData> retrieveAll() {
|
||||
final String sql = "select " + BATCH_RUN_MAPPER.schema() + " order by b.id desc";
|
||||
final List<BatchRunData> batchRuns = this.jdbcTemplate.query(sql, BATCH_RUN_MAPPER); // NOSONAR
|
||||
return attachRows(batchRuns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchRunData retrieveOne(final Long batchRunId) {
|
||||
try {
|
||||
final String sql = "select " + BATCH_RUN_MAPPER.schema() + " where b.id = ?";
|
||||
final BatchRunData batchRun = this.jdbcTemplate.queryForObject(sql, BATCH_RUN_MAPPER, batchRunId); // NOSONAR
|
||||
return attachRows(List.of(batchRun)).iterator().next();
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new BatchRunNotFoundException(batchRunId);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<BatchRunData> attachRows(final List<BatchRunData> batchRuns) {
|
||||
if (batchRuns.isEmpty()) {
|
||||
return batchRuns;
|
||||
}
|
||||
|
||||
final String sql = "select " + ROW_MAPPER.schema() + " order by r.id";
|
||||
final List<Map.Entry<Long, BatchRunRowData>> rows = this.jdbcTemplate.query(sql, ROW_MAPPER); // NOSONAR
|
||||
|
||||
final Map<Long, List<BatchRunRowData>> rowsByBatchRunId = new LinkedHashMap<>();
|
||||
for (final Map.Entry<Long, BatchRunRowData> row : rows) {
|
||||
rowsByBatchRunId.computeIfAbsent(row.getKey(), k -> new ArrayList<>()).add(row.getValue());
|
||||
}
|
||||
|
||||
final List<BatchRunData> result = new ArrayList<>(batchRuns.size());
|
||||
for (final BatchRunData b : batchRuns) {
|
||||
result.add(b.toBuilder().rows(rowsByBatchRunId.getOrDefault(b.getId(), new ArrayList<>())).build());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+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.dds.batch.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface BatchRunWritePlatformService {
|
||||
|
||||
CommandProcessingResult generateBatchRun(JsonCommand command);
|
||||
|
||||
CommandProcessingResult postBatchRun(Long batchRunId, JsonCommand command);
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* 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.dds.batch.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlab;
|
||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlabRepository;
|
||||
import org.apache.fineract.portfolio.dds.batch.domain.BatchRun;
|
||||
import org.apache.fineract.portfolio.dds.batch.domain.BatchRunRepository;
|
||||
import org.apache.fineract.portfolio.dds.batch.domain.BatchRunRow;
|
||||
import org.apache.fineract.portfolio.dds.batch.exception.BatchRunAlreadyPostedException;
|
||||
import org.apache.fineract.portfolio.dds.batch.exception.BatchRunNotFoundException;
|
||||
import org.apache.fineract.portfolio.dds.batch.serialization.BatchRunCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartyNotFoundException;
|
||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransaction;
|
||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransactionRepository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class BatchRunWritePlatformServiceJpaRepositoryImpl implements BatchRunWritePlatformService {
|
||||
|
||||
private static final BigDecimal DAYS_IN_YEAR = BigDecimal.valueOf(365);
|
||||
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
|
||||
|
||||
private final BatchRunCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final BatchRunRepository batchRunRepository;
|
||||
private final PartyRepository partyRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
private final PartyTransactionRepository partyTransactionRepository;
|
||||
private final CommissionSlabRepository commissionSlabRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult generateBatchRun(final JsonCommand command) {
|
||||
|
||||
this.fromApiJsonDeserializer.validateForGenerate(command.json());
|
||||
|
||||
final String jobType = command.stringValueOfParameterNamed("jobType");
|
||||
final String branchScope = command.stringValueOfParameterNamed("branchScope");
|
||||
final LocalDate jobDate = command.localDateValueOfParameterNamed("jobDate");
|
||||
final Long agentFrom = command.longValueOfParameterNamed("agentFrom");
|
||||
final Long agentTo = command.longValueOfParameterNamed("agentTo");
|
||||
final Long partyFrom = command.parameterExists("partyFrom") ? command.longValueOfParameterNamed("partyFrom") : 1L;
|
||||
final Long partyTo = command.parameterExists("partyTo") ? command.longValueOfParameterNamed("partyTo") : Long.MAX_VALUE;
|
||||
|
||||
final Set<String> officeNames = "Selected".equals(branchScope) ? extractOfficeNames(command) : null;
|
||||
|
||||
final BatchRun batchRun = new BatchRun(jobType, branchScope, jobDate, agentFrom, agentTo, partyFrom, partyTo, LocalDate.now());
|
||||
|
||||
switch (jobType) {
|
||||
case "INTEREST_CALCULATION" -> generateInterestRows(batchRun, agentFrom, agentTo, partyFrom, partyTo, jobDate, false,
|
||||
officeNames);
|
||||
case "OD_INTEREST_CALCULATION" -> generateInterestRows(batchRun, agentFrom, agentTo, partyFrom, partyTo, jobDate, true,
|
||||
officeNames);
|
||||
case "COMMISSION_CALCULATION" -> generateCommissionRows(batchRun, agentFrom, agentTo, jobDate, officeNames);
|
||||
default -> throw new IllegalStateException("Unsupported DDS batch job type: " + jobType);
|
||||
}
|
||||
|
||||
this.batchRunRepository.saveAndFlush(batchRun);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(batchRun.getId()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Parses the "Branch" checklist selection - null/empty means no filter ("All"). Matched
|
||||
* against Agent.homeBranch by name, since that field is plain text (not a real Office FK)
|
||||
* mirroring the same free-text branch concept the legacy screen itself used. */
|
||||
private Set<String> extractOfficeNames(final JsonCommand command) {
|
||||
if (!command.parameterExists("officeNames")) {
|
||||
return null;
|
||||
}
|
||||
final JsonArray officeNamesArray = command.arrayOfParameterNamed("officeNames");
|
||||
final Set<String> officeNames = new HashSet<>();
|
||||
for (int i = 0; i < officeNamesArray.size(); i++) {
|
||||
officeNames.add(officeNamesArray.get(i).getAsString().trim().toUpperCase());
|
||||
}
|
||||
return officeNames;
|
||||
}
|
||||
|
||||
private boolean matchesSelectedBranch(final String homeBranch, final Set<String> officeNames) {
|
||||
if (officeNames == null || officeNames.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return homeBranch != null && officeNames.contains(homeBranch.trim().toUpperCase());
|
||||
}
|
||||
|
||||
private void generateInterestRows(final BatchRun batchRun, final Long agentFrom, final Long agentTo, final Long partyFrom,
|
||||
final Long partyTo, final LocalDate jobDate, final boolean odOnly, final Set<String> officeNames) {
|
||||
final List<Party> parties = odOnly
|
||||
? this.partyRepository.findForOdInterestBatch(agentFrom, agentTo, partyFrom, partyTo)
|
||||
: this.partyRepository.findForInterestBatch(agentFrom, agentTo, partyFrom, partyTo);
|
||||
|
||||
for (final Party party : parties) {
|
||||
if (!matchesSelectedBranch(party.getAgent().getHomeBranch(), officeNames)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final BigDecimal rate = party.getInterestRate();
|
||||
final BigDecimal balance = party.getCurrentBalance();
|
||||
if (rate == null || rate.compareTo(BigDecimal.ZERO) <= 0 || balance.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final LocalDate accrueFrom = party.getInterestDate() != null ? party.getInterestDate() : party.getOpeningDate();
|
||||
final long days = ChronoUnit.DAYS.between(accrueFrom, jobDate);
|
||||
if (days <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final BigDecimal amount = balance.multiply(rate).multiply(BigDecimal.valueOf(days))
|
||||
.divide(HUNDRED.multiply(DAYS_IN_YEAR), 6, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
batchRun.addRow(new BatchRunRow(party.getId(), party.getAgent().getId(), amount));
|
||||
}
|
||||
}
|
||||
|
||||
private void generateCommissionRows(final BatchRun batchRun, final Long agentFrom, final Long agentTo, final LocalDate jobDate,
|
||||
final Set<String> officeNames) {
|
||||
final List<Agent> agents = this.agentRepository.findBetweenIds(agentFrom, agentTo);
|
||||
|
||||
for (final Agent agent : agents) {
|
||||
if (!matchesSelectedBranch(agent.getHomeBranch(), officeNames)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final LocalDate fromDate = agent.getLastCommissionDate() != null ? agent.getLastCommissionDate().plusDays(1)
|
||||
: agent.getAgentOpeningDate();
|
||||
if (fromDate.isAfter(jobDate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final BigDecimal collectionVolume = this.partyTransactionRepository.sumPartyTranCredits(agent.getId(), fromDate, jobDate);
|
||||
if (collectionVolume == null || collectionVolume.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final List<CommissionSlab> matchingSlabs = this.commissionSlabRepository.findMatchingSlabs("ACCOUNT", collectionVolume);
|
||||
if (matchingSlabs.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final BigDecimal commissionPercent = matchingSlabs.get(0).getCommissionPercent();
|
||||
final BigDecimal amount = collectionVolume.multiply(commissionPercent).divide(HUNDRED, 2, RoundingMode.HALF_UP);
|
||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
batchRun.addRow(new BatchRunRow(null, agent.getId(), amount));
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult postBatchRun(final Long batchRunId, final JsonCommand command) {
|
||||
|
||||
final BatchRun batchRun = this.batchRunRepository.findById(batchRunId).orElseThrow(() -> new BatchRunNotFoundException(batchRunId));
|
||||
if (batchRun.isPosted()) {
|
||||
throw new BatchRunAlreadyPostedException(batchRunId);
|
||||
}
|
||||
|
||||
final boolean isCommissionJob = "COMMISSION_CALCULATION".equals(batchRun.getJobType());
|
||||
final String transactionType = "OD_INTEREST_CALCULATION".equals(batchRun.getJobType()) ? "OD_INTEREST_POST" : "INTEREST_POST";
|
||||
|
||||
for (final BatchRunRow row : batchRun.getRows()) {
|
||||
if (isCommissionJob) {
|
||||
final Agent agent = this.agentRepository.findById(row.getAgentId())
|
||||
.orElseThrow(() -> new AgentNotFoundException(row.getAgentId()));
|
||||
agent.postCommission(row.getAmount(), batchRun.getJobDate());
|
||||
} else {
|
||||
final Party party = this.partyRepository.findById(row.getPartyId())
|
||||
.orElseThrow(() -> new PartyNotFoundException(row.getPartyId()));
|
||||
final Agent agent = party.getAgent();
|
||||
final BigDecimal newBalance = party.credit(row.getAmount());
|
||||
party.markInterestCalculatedThrough(batchRun.getJobDate());
|
||||
|
||||
final PartyTransaction transaction = new PartyTransaction(party, agent, batchRun.getJobDate(), transactionType,
|
||||
"CREDIT", null, null, null, row.getAmount(), null, null, "DDS batch run #" + batchRun.getId(), newBalance);
|
||||
this.partyTransactionRepository.save(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
batchRun.markPosted(LocalDate.now());
|
||||
this.batchRunRepository.saveAndFlush(batchRun);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(batchRunId) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.dds.batch.starter;
|
||||
|
||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlabRepository;
|
||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
||||
import org.apache.fineract.portfolio.dds.batch.domain.BatchRunRepository;
|
||||
import org.apache.fineract.portfolio.dds.batch.serialization.BatchRunCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunReadPlatformService;
|
||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunWritePlatformService;
|
||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransactionRepository;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class DdsBatchConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(BatchRunReadPlatformService.class)
|
||||
public BatchRunReadPlatformService batchRunReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new BatchRunReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(BatchRunWritePlatformService.class)
|
||||
public BatchRunWritePlatformService batchRunWritePlatformService(BatchRunCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||
BatchRunRepository batchRunRepository, PartyRepository partyRepository, AgentRepository agentRepository,
|
||||
PartyTransactionRepository partyTransactionRepository, CommissionSlabRepository commissionSlabRepository) {
|
||||
return new BatchRunWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, batchRunRepository, partyRepository,
|
||||
agentRepository, partyTransactionRepository, commissionSlabRepository);
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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.dds.party.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.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.dds.party.data.PartyData;
|
||||
import org.apache.fineract.portfolio.dds.party.service.PartyReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/dds/parties")
|
||||
@Component
|
||||
@Tag(name = "DDS Parties", description = "A DDS Party is a customer's daily-deposit scheme account, linked to the Agent who collects for it. Carries the scheme's KYC, terms, two-stage overdue flags and a nominee list.")
|
||||
@RequiredArgsConstructor
|
||||
public class PartiesApiResource {
|
||||
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "PARTY";
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final PartyReadPlatformService readPlatformService;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve DDS Parties", description = "Returns the list of DDS parties.")
|
||||
public Collection<PartyData> retrieveAll() {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveAll();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{partyId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve a DDS Party", description = "Returns the details of a DDS party, including its nominees.")
|
||||
public PartyData retrieveOne(@PathParam("partyId") @Parameter(description = "partyId") final Long partyId) {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveOne(partyId);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Create a DDS Party", description = "Mandatory fields: clientId, agentId, subType, partyName, address1, "
|
||||
+ "city, pincode, mobileNoSms, operation, adultMinor, openingDate, dailyDepositAmount, days\n" + "\n"
|
||||
+ "Optional fields: lastAccount, nominal, aadharNo, partyName1, address2, address3, gender, panCardNo, telephone, "
|
||||
+ "birthDate, occupation, risk, ckycId, minorDate, guardianClientId, minAccountOpt, relation, month, maturityDate, "
|
||||
+ "reopenDate, depositAmount, ledgerNo, withdrawType, lien, payDate, paymentAmount, openingBalance, isSmsActive, "
|
||||
+ "isEmailActive, interestDate, spDescription, freezeCode, advCode, creditToBranch, creditToAccountType, "
|
||||
+ "creditToAccount, nomineeApplicable, nomineeSplitMode, nominees, savingsAccountNo (links this party to an existing, "
|
||||
+ "non-closed real savings account of the same client, resolved by account number - omit if the client has none)")
|
||||
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.createParty() //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("{partyId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Update a DDS Party", description = "Updates the details of a DDS party.")
|
||||
public CommandProcessingResult update(@PathParam("partyId") @Parameter(description = "partyId") final Long partyId,
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.updateParty(partyId) //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{partyId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Close a DDS Party", description = "command=close is the only supported command. Mandatory field: "
|
||||
+ "closeDate. The party's balance must already be zero (settled with a full Withdraw Deposite payout) before it "
|
||||
+ "can be closed.")
|
||||
public CommandProcessingResult handleCommand(@PathParam("partyId") @Parameter(description = "partyId") final Long partyId,
|
||||
@QueryParam("command") @Parameter(description = "command") final String commandParam,
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
if (!"close".equals(commandParam)) {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.closeParty(partyId) //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.dds.party.domain;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface PartyRepository extends JpaRepository<Party, Long> {
|
||||
|
||||
@Query("select coalesce(max(cast(substring(p.partyCode, 3) as long)), 0) from Party p where p.partyCode like 'PT%'")
|
||||
Long retrieveMaxPartyCodeSuffix();
|
||||
|
||||
Optional<Party> findByPartyCode(String partyCode);
|
||||
|
||||
@Query("select p from Party p where p.agent.id between :agentFrom and :agentTo and p.id between :partyFrom and :partyTo")
|
||||
List<Party> findForInterestBatch(@Param("agentFrom") Long agentFrom, @Param("agentTo") Long agentTo,
|
||||
@Param("partyFrom") Long partyFrom, @Param("partyTo") Long partyTo);
|
||||
|
||||
@Query("select p from Party p where p.agent.id between :agentFrom and :agentTo and p.id between :partyFrom and :partyTo "
|
||||
+ "and (p.od1Flag = true or p.od2Flag = true)")
|
||||
List<Party> findForOdInterestBatch(@Param("agentFrom") Long agentFrom, @Param("agentTo") Long agentTo,
|
||||
@Param("partyFrom") Long partyFrom, @Param("partyTo") Long partyTo);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.dds.party.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "PARTY", action = "CLOSE")
|
||||
public class ClosePartyCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final PartyWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public ClosePartyCommandHandler(final PartyWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.closeParty(command.entityId(), command);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.dds.party.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "PARTY", action = "CREATE")
|
||||
public class CreatePartyCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final PartyWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreatePartyCommandHandler(final PartyWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.createParty(command);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.dds.party.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "PARTY", action = "UPDATE")
|
||||
public class UpdatePartyCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final PartyWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public UpdatePartyCommandHandler(final PartyWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.updateParty(command.entityId(), command);
|
||||
}
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* 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.dds.party.serialization;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public final class PartyCommandFromApiJsonDeserializer {
|
||||
|
||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("clientId", "agentId", "subType",
|
||||
"lastAccount", "nominal", "aadharNo", "partyName", "partyName1", "address1", "address2", "address3", "city", "pincode",
|
||||
"gender", "panCardNo", "telephone", "mobileNoSms", "operation", "birthDate", "occupation", "risk", "ckycId", "adultMinor",
|
||||
"minorDate", "guardianClientId", "minAccountOpt", "relation", "openingDate", "month", "maturityDate", "reopenDate",
|
||||
"depositAmount", "dailyDepositAmount", "days", "ledgerNo", "interestRate", "withdrawType", "lien", "payDate",
|
||||
"paymentAmount", "openingBalance", "isSmsActive", "isEmailActive", "interestDate", "spDescription", "freezeCode", "advCode",
|
||||
"creditToBranch", "creditToAccountType", "creditToAccount", "nomineeApplicable", "nomineeSplitMode", "nominees",
|
||||
"savingsAccountNo", "dateFormat", "locale"));
|
||||
|
||||
private static final Set<String> SUPPORTED_NOMINEE_PARAMETERS = new HashSet<>(
|
||||
Arrays.asList("nominationDate", "name", "mobileNo", "birthDate", "minor", "relation", "gender", "guardianName"));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public PartyCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForCreate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("party");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
v.reset().parameter("clientId").value(this.fromApiJsonHelper.extractLongNamed("clientId", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
v.reset().parameter("agentId").value(this.fromApiJsonHelper.extractLongNamed("agentId", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
v.reset().parameter("subType").value(this.fromApiJsonHelper.extractStringNamed("subType", element)).notBlank()
|
||||
.notExceedingLengthOf(60);
|
||||
v.reset().parameter("partyName").value(this.fromApiJsonHelper.extractStringNamed("partyName", element)).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
v.reset().parameter("address1").value(this.fromApiJsonHelper.extractStringNamed("address1", element)).notBlank()
|
||||
.notExceedingLengthOf(200);
|
||||
v.reset().parameter("city").value(this.fromApiJsonHelper.extractStringNamed("city", element)).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
v.reset().parameter("pincode").value(this.fromApiJsonHelper.extractStringNamed("pincode", element)).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
v.reset().parameter("mobileNoSms").value(this.fromApiJsonHelper.extractStringNamed("mobileNoSms", element)).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
v.reset().parameter("operation").value(this.fromApiJsonHelper.extractStringNamed("operation", element)).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
v.reset().parameter("adultMinor").value(this.fromApiJsonHelper.extractStringNamed("adultMinor", element)).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
v.reset().parameter("openingDate").value(this.fromApiJsonHelper.extractLocalDateNamed("openingDate", element)).notNull();
|
||||
v.reset().parameter("dailyDepositAmount").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("dailyDepositAmount", element))
|
||||
.notNull().positiveAmount();
|
||||
v.reset().parameter("days").value(this.fromApiJsonHelper.extractIntegerSansLocaleNamed("days", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
|
||||
validateOptionalFields(element, v);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists("nominees", element)) {
|
||||
validateNominees(element.getAsJsonObject(), dataValidationErrors);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForUpdate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("party");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists("clientId", element)) {
|
||||
v.reset().parameter("clientId").value(this.fromApiJsonHelper.extractLongNamed("clientId", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("agentId", element)) {
|
||||
v.reset().parameter("agentId").value(this.fromApiJsonHelper.extractLongNamed("agentId", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("subType", element)) {
|
||||
v.reset().parameter("subType").value(this.fromApiJsonHelper.extractStringNamed("subType", element)).notBlank()
|
||||
.notExceedingLengthOf(60);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("partyName", element)) {
|
||||
v.reset().parameter("partyName").value(this.fromApiJsonHelper.extractStringNamed("partyName", element)).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("address1", element)) {
|
||||
v.reset().parameter("address1").value(this.fromApiJsonHelper.extractStringNamed("address1", element)).notBlank()
|
||||
.notExceedingLengthOf(200);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("city", element)) {
|
||||
v.reset().parameter("city").value(this.fromApiJsonHelper.extractStringNamed("city", element)).notBlank()
|
||||
.notExceedingLengthOf(100);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("pincode", element)) {
|
||||
v.reset().parameter("pincode").value(this.fromApiJsonHelper.extractStringNamed("pincode", element)).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("mobileNoSms", element)) {
|
||||
v.reset().parameter("mobileNoSms").value(this.fromApiJsonHelper.extractStringNamed("mobileNoSms", element)).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("operation", element)) {
|
||||
v.reset().parameter("operation").value(this.fromApiJsonHelper.extractStringNamed("operation", element)).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("adultMinor", element)) {
|
||||
v.reset().parameter("adultMinor").value(this.fromApiJsonHelper.extractStringNamed("adultMinor", element)).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("openingDate", element)) {
|
||||
v.reset().parameter("openingDate").value(this.fromApiJsonHelper.extractLocalDateNamed("openingDate", element)).notNull();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("dailyDepositAmount", element)) {
|
||||
v.reset().parameter("dailyDepositAmount")
|
||||
.value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("dailyDepositAmount", element)).notNull()
|
||||
.positiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("days", element)) {
|
||||
v.reset().parameter("days").value(this.fromApiJsonHelper.extractIntegerSansLocaleNamed("days", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
|
||||
validateOptionalFields(element, v);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists("nominees", element)) {
|
||||
validateNominees(element.getAsJsonObject(), dataValidationErrors);
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
public void validateForClose(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, Set.of("closeDate", "dateFormat", "locale"));
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("party");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
v.reset().parameter("closeDate").value(this.fromApiJsonHelper.extractLocalDateNamed("closeDate", element)).notNull();
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void validateOptionalFields(final JsonElement element, final DataValidatorBuilder v) {
|
||||
optionalString(element, v, "lastAccount", 50);
|
||||
optionalString(element, v, "aadharNo", 20);
|
||||
optionalString(element, v, "partyName1", 100);
|
||||
optionalString(element, v, "address2", 200);
|
||||
optionalString(element, v, "address3", 200);
|
||||
optionalString(element, v, "gender", 20);
|
||||
optionalString(element, v, "panCardNo", 20);
|
||||
optionalString(element, v, "telephone", 20);
|
||||
optionalString(element, v, "occupation", 40);
|
||||
optionalString(element, v, "risk", 20);
|
||||
optionalString(element, v, "ckycId", 40);
|
||||
optionalString(element, v, "minAccountOpt", 40);
|
||||
optionalString(element, v, "relation", 40);
|
||||
optionalString(element, v, "ledgerNo", 40);
|
||||
optionalString(element, v, "withdrawType", 20);
|
||||
optionalString(element, v, "spDescription", 500);
|
||||
optionalString(element, v, "freezeCode", 20);
|
||||
optionalString(element, v, "advCode", 40);
|
||||
optionalString(element, v, "creditToBranch", 100);
|
||||
optionalString(element, v, "creditToAccountType", 100);
|
||||
optionalString(element, v, "creditToAccount", 50);
|
||||
optionalString(element, v, "nomineeSplitMode", 20);
|
||||
optionalString(element, v, "savingsAccountNo", 20);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists("guardianClientId", element)) {
|
||||
v.reset().parameter("guardianClientId").value(this.fromApiJsonHelper.extractLongNamed("guardianClientId", element))
|
||||
.ignoreIfNull().integerGreaterThanZero();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("month", element)) {
|
||||
v.reset().parameter("month").value(this.fromApiJsonHelper.extractIntegerSansLocaleNamed("month", element)).ignoreIfNull()
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("depositAmount", element)) {
|
||||
v.reset().parameter("depositAmount").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("depositAmount", element))
|
||||
.ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("paymentAmount", element)) {
|
||||
v.reset().parameter("paymentAmount")
|
||||
.value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("paymentAmount", element)).ignoreIfNull()
|
||||
.zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("openingBalance", element)) {
|
||||
v.reset().parameter("openingBalance")
|
||||
.value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("openingBalance", element)).ignoreIfNull()
|
||||
.zeroOrPositiveAmount();
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists("interestRate", element)) {
|
||||
v.reset().parameter("interestRate").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRate", element))
|
||||
.ignoreIfNull().zeroOrPositiveAmount();
|
||||
}
|
||||
}
|
||||
|
||||
private void optionalString(final JsonElement element, final DataValidatorBuilder v, final String paramName, final int maxLength) {
|
||||
if (this.fromApiJsonHelper.parameterExists(paramName, element)) {
|
||||
v.reset().parameter(paramName).value(this.fromApiJsonHelper.extractStringNamed(paramName, element)).ignoreIfNull()
|
||||
.notExceedingLengthOf(maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNominees(final JsonObject topLevelJson, final List<ApiParameterError> dataValidationErrors) {
|
||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("party.nominee");
|
||||
final var nomineesArray = topLevelJson.getAsJsonArray("nominees");
|
||||
for (int i = 0; i < nomineesArray.size(); i++) {
|
||||
final JsonObject nomineeJson = nomineesArray.get(i).getAsJsonObject();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(nomineeJson, SUPPORTED_NOMINEE_PARAMETERS);
|
||||
|
||||
v.reset().parameterAtIndexArray("nominationDate", i).value(extractString(nomineeJson, "nominationDate")).notBlank();
|
||||
v.reset().parameterAtIndexArray("name", i).value(extractString(nomineeJson, "name")).notBlank().notExceedingLengthOf(100);
|
||||
v.reset().parameterAtIndexArray("relation", i).value(extractString(nomineeJson, "relation")).notBlank()
|
||||
.notExceedingLengthOf(40);
|
||||
v.reset().parameterAtIndexArray("gender", i).value(extractString(nomineeJson, "gender")).notBlank()
|
||||
.notExceedingLengthOf(20);
|
||||
}
|
||||
}
|
||||
|
||||
private String extractString(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.dds.party.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.portfolio.dds.party.data.PartyData;
|
||||
|
||||
public interface PartyReadPlatformService {
|
||||
|
||||
Collection<PartyData> retrieveAll();
|
||||
|
||||
PartyData retrieveOne(Long partyId);
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* 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.dds.party.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.portfolio.dds.party.data.PartyData;
|
||||
import org.apache.fineract.portfolio.dds.party.data.PartyNomineeData;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartyNotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PartyReadPlatformServiceImpl implements PartyReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static final PartyMapper PARTY_MAPPER = new PartyMapper();
|
||||
private static final PartyNomineeMapper NOMINEE_MAPPER = new PartyNomineeMapper();
|
||||
|
||||
private static final class PartyMapper implements RowMapper<PartyData> {
|
||||
|
||||
public String schema() {
|
||||
return " p.id as id, p.client_id as clientId, c.display_name as clientName, p.agent_id as agentId, "
|
||||
+ " a.agent_code as agentCode, a.agent_name as agentName, p.savings_account_id as savingsAccountId, "
|
||||
+ " p.sub_type as subType, p.party_code as partyCode, p.last_account as lastAccount, p.nominal as nominal, "
|
||||
+ " p.aadhar_no as aadharNo, p.party_name as partyName, p.party_name1 as partyName1, p.address1 as address1, "
|
||||
+ " p.address2 as address2, p.address3 as address3, p.city as city, p.pincode as pincode, p.gender as gender, "
|
||||
+ " p.pan_card_no as panCardNo, p.telephone as telephone, p.mobile_no_sms as mobileNoSms, p.operation as operation, "
|
||||
+ " p.birth_date as birthDate, p.occupation as occupation, p.risk as risk, p.ckyc_id as ckycId, "
|
||||
+ " p.adult_minor as adultMinor, p.minor_date as minorDate, p.guardian_client_id as guardianClientId, "
|
||||
+ " p.min_account_opt as minAccountOpt, p.relation as relation, p.opening_date as openingDate, "
|
||||
+ " p.months as month, p.maturity_date as maturityDate, p.reopen_date as reopenDate, "
|
||||
+ " p.deposit_amount as depositAmount, p.daily_deposit_amount as dailyDepositAmount, p.days as days, "
|
||||
+ " p.ledger_no as ledgerNo, p.maturity_amount as maturityAmount, p.interest_rate as interestRate, "
|
||||
+ " p.withdraw_type as withdrawType, p.od1_flag as od1Flag, p.od2_flag as od2Flag, p.lien as lien, "
|
||||
+ " p.pay_date as payDate, p.payment_amount as paymentAmount, p.current_balance as currentBalance, "
|
||||
+ " p.opening_balance as openingBalance, p.is_sms_active as isSmsActive, p.is_email_active as isEmailActive, "
|
||||
+ " p.interest_calc as interestCalc, p.interest_date as interestDate, p.sp_description as spDescription, "
|
||||
+ " p.freeze_code as freezeCode, p.adv_code as advCode, p.credit_to_branch as creditToBranch, "
|
||||
+ " p.credit_to_account_type as creditToAccountType, p.credit_to_account as creditToAccount, "
|
||||
+ " p.nominee_applicable as nomineeApplicable, p.nominee_split_mode as nomineeSplitMode, "
|
||||
+ " p.status as status, p.close_date as closeDate "
|
||||
+ " from m_dds_party p join m_client c on c.id = p.client_id join m_dds_agent a on a.id = p.agent_id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartyData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
return PartyData.builder() //
|
||||
.id(rs.getLong("id")) //
|
||||
.clientId(rs.getLong("clientId")) //
|
||||
.clientName(rs.getString("clientName")) //
|
||||
.agentId(rs.getLong("agentId")) //
|
||||
.agentCode(rs.getString("agentCode")) //
|
||||
.agentName(rs.getString("agentName")) //
|
||||
.savingsAccountId(JdbcSupport.getLong(rs, "savingsAccountId")) //
|
||||
.subType(rs.getString("subType")) //
|
||||
.partyCode(rs.getString("partyCode")) //
|
||||
.lastAccount(rs.getString("lastAccount")) //
|
||||
.nominal(rs.getBoolean("nominal")) //
|
||||
.aadharNo(rs.getString("aadharNo")) //
|
||||
.partyName(rs.getString("partyName")) //
|
||||
.partyName1(rs.getString("partyName1")) //
|
||||
.address1(rs.getString("address1")) //
|
||||
.address2(rs.getString("address2")) //
|
||||
.address3(rs.getString("address3")) //
|
||||
.city(rs.getString("city")) //
|
||||
.pincode(rs.getString("pincode")) //
|
||||
.gender(rs.getString("gender")) //
|
||||
.panCardNo(rs.getString("panCardNo")) //
|
||||
.telephone(rs.getString("telephone")) //
|
||||
.mobileNoSms(rs.getString("mobileNoSms")) //
|
||||
.operation(rs.getString("operation")) //
|
||||
.birthDate(JdbcSupport.getLocalDate(rs, "birthDate")) //
|
||||
.occupation(rs.getString("occupation")) //
|
||||
.risk(rs.getString("risk")) //
|
||||
.ckycId(rs.getString("ckycId")) //
|
||||
.adultMinor(rs.getString("adultMinor")) //
|
||||
.minorDate(JdbcSupport.getLocalDate(rs, "minorDate")) //
|
||||
.guardianClientId(JdbcSupport.getLong(rs, "guardianClientId")) //
|
||||
.minAccountOpt(rs.getString("minAccountOpt")) //
|
||||
.relation(rs.getString("relation")) //
|
||||
.openingDate(JdbcSupport.getLocalDate(rs, "openingDate")) //
|
||||
.month(JdbcSupport.getInteger(rs, "month")) //
|
||||
.maturityDate(JdbcSupport.getLocalDate(rs, "maturityDate")) //
|
||||
.reopenDate(JdbcSupport.getLocalDate(rs, "reopenDate")) //
|
||||
.depositAmount(rs.getBigDecimal("depositAmount")) //
|
||||
.dailyDepositAmount(rs.getBigDecimal("dailyDepositAmount")) //
|
||||
.days(JdbcSupport.getInteger(rs, "days")) //
|
||||
.ledgerNo(rs.getString("ledgerNo")) //
|
||||
.maturityAmount(rs.getBigDecimal("maturityAmount")) //
|
||||
.interestRate(rs.getBigDecimal("interestRate")) //
|
||||
.withdrawType(rs.getString("withdrawType")) //
|
||||
.od1Flag(rs.getBoolean("od1Flag")) //
|
||||
.od2Flag(rs.getBoolean("od2Flag")) //
|
||||
.lien(rs.getBoolean("lien")) //
|
||||
.payDate(JdbcSupport.getLocalDate(rs, "payDate")) //
|
||||
.paymentAmount(rs.getBigDecimal("paymentAmount")) //
|
||||
.currentBalance(rs.getBigDecimal("currentBalance")) //
|
||||
.openingBalance(rs.getBigDecimal("openingBalance")) //
|
||||
.isSmsActive(rs.getBoolean("isSmsActive")) //
|
||||
.isEmailActive(rs.getBoolean("isEmailActive")) //
|
||||
.interestCalc(rs.getBigDecimal("interestCalc")) //
|
||||
.interestDate(JdbcSupport.getLocalDate(rs, "interestDate")) //
|
||||
.spDescription(rs.getString("spDescription")) //
|
||||
.freezeCode(rs.getString("freezeCode")) //
|
||||
.advCode(rs.getString("advCode")) //
|
||||
.creditToBranch(rs.getString("creditToBranch")) //
|
||||
.creditToAccountType(rs.getString("creditToAccountType")) //
|
||||
.creditToAccount(rs.getString("creditToAccount")) //
|
||||
.nomineeApplicable(rs.getBoolean("nomineeApplicable")) //
|
||||
.nomineeSplitMode(rs.getString("nomineeSplitMode")) //
|
||||
.status(rs.getString("status")) //
|
||||
.closeDate(JdbcSupport.getLocalDate(rs, "closeDate")) //
|
||||
.nominees(new ArrayList<>()) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PartyNomineeMapper implements RowMapper<Map.Entry<Long, PartyNomineeData>> {
|
||||
|
||||
public String schema() {
|
||||
return " n.party_id as partyId, n.id as id, n.nomination_date as nominationDate, n.name as name, "
|
||||
+ " n.mobile_no as mobileNo, n.birth_date as birthDate, n.minor as minor, n.relation as relation, "
|
||||
+ " n.gender as gender, n.guardian_name as guardianName from m_dds_party_nominee n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<Long, PartyNomineeData> mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
final Long partyId = rs.getLong("partyId");
|
||||
final PartyNomineeData data = PartyNomineeData.instance(rs.getLong("id"), JdbcSupport.getLocalDate(rs, "nominationDate"),
|
||||
rs.getString("name"), rs.getString("mobileNo"), JdbcSupport.getLocalDate(rs, "birthDate"), rs.getBoolean("minor"),
|
||||
rs.getString("relation"), rs.getString("gender"), rs.getString("guardianName"));
|
||||
return Map.entry(partyId, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PartyData> retrieveAll() {
|
||||
final String sql = "select " + PARTY_MAPPER.schema() + " order by p.id";
|
||||
final List<PartyData> parties = this.jdbcTemplate.query(sql, PARTY_MAPPER); // NOSONAR
|
||||
return attachNominees(parties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartyData retrieveOne(final Long partyId) {
|
||||
try {
|
||||
final String sql = "select " + PARTY_MAPPER.schema() + " where p.id = ?";
|
||||
final PartyData party = this.jdbcTemplate.queryForObject(sql, PARTY_MAPPER, partyId); // NOSONAR
|
||||
return attachNominees(List.of(party)).iterator().next();
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new PartyNotFoundException(partyId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<PartyData> attachNominees(final List<PartyData> parties) {
|
||||
if (parties.isEmpty()) {
|
||||
return parties;
|
||||
}
|
||||
|
||||
final String sql = "select " + NOMINEE_MAPPER.schema() + " order by n.id";
|
||||
final List<Map.Entry<Long, PartyNomineeData>> rows = this.jdbcTemplate.query(sql, NOMINEE_MAPPER); // NOSONAR
|
||||
|
||||
final Map<Long, List<PartyNomineeData>> nomineesByPartyId = new LinkedHashMap<>();
|
||||
for (final Map.Entry<Long, PartyNomineeData> row : rows) {
|
||||
nomineesByPartyId.computeIfAbsent(row.getKey(), k -> new ArrayList<>()).add(row.getValue());
|
||||
}
|
||||
|
||||
final List<PartyData> result = new ArrayList<>(parties.size());
|
||||
for (final PartyData p : parties) {
|
||||
result.add(p.toBuilder().nominees(nomineesByPartyId.getOrDefault(p.getId(), new ArrayList<>())).build());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.dds.party.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface PartyWritePlatformService {
|
||||
|
||||
CommandProcessingResult createParty(JsonCommand command);
|
||||
|
||||
CommandProcessingResult updateParty(Long partyId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult closeParty(Long partyId, JsonCommand command);
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 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.dds.party.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.PartyNominee;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartyAlreadyClosedException;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartyHasBalanceException;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartyNotFoundException;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartySavingsAccountClientMismatchException;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartySavingsAccountNotFoundException;
|
||||
import org.apache.fineract.portfolio.dds.party.serialization.PartyCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PartyWritePlatformServiceJpaRepositoryImpl implements PartyWritePlatformService {
|
||||
|
||||
private final PartyCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final PartyRepository partyRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||
private final SavingsAccountRepository savingsAccountRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createParty(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||
|
||||
final Long clientId = command.longValueOfParameterNamed("clientId");
|
||||
final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
||||
|
||||
final Long agentId = command.longValueOfParameterNamed("agentId");
|
||||
final Agent agent = this.agentRepository.findById(agentId)
|
||||
.orElseThrow(() -> new AgentNotFoundException(agentId));
|
||||
|
||||
final String partyCode = generateNextPartyCode();
|
||||
final Party party = Party.fromJson(client, agent, partyCode, command);
|
||||
|
||||
linkSavingsAccountIfProvided(party, clientId, command);
|
||||
addNomineesFromCommand(party, command);
|
||||
|
||||
this.partyRepository.saveAndFlush(party);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(party.getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleDataIntegrityIssues(throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult updateParty(final Long partyId, final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.fromApiJsonDeserializer.validateForUpdate(command.json());
|
||||
|
||||
final Party partyForUpdate = this.partyRepository.findById(partyId).orElseThrow(() -> new PartyNotFoundException(partyId));
|
||||
final Map<String, Object> changesOnly = partyForUpdate.update(command);
|
||||
|
||||
if (changesOnly.containsKey("clientId")) {
|
||||
final Long newClientId = (Long) changesOnly.get("clientId");
|
||||
final Client newClient = this.clientRepositoryWrapper.findOneWithNotFoundDetection(newClientId);
|
||||
partyForUpdate.changeClient(newClient);
|
||||
}
|
||||
|
||||
if (changesOnly.containsKey("agentId")) {
|
||||
final Long newAgentId = (Long) changesOnly.get("agentId");
|
||||
final Agent newAgent = this.agentRepository.findById(newAgentId)
|
||||
.orElseThrow(() -> new AgentNotFoundException(newAgentId));
|
||||
partyForUpdate.changeAgent(newAgent);
|
||||
}
|
||||
|
||||
if (command.parameterExists("nominees")) {
|
||||
partyForUpdate.resetNominees();
|
||||
addNomineesFromCommand(partyForUpdate, command);
|
||||
changesOnly.put("nominees", command.jsonFragment("nominees"));
|
||||
}
|
||||
|
||||
linkSavingsAccountIfProvided(partyForUpdate, partyForUpdate.clientId(), command);
|
||||
|
||||
this.partyRepository.saveAndFlush(partyForUpdate);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(partyId).with(changesOnly)
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleDataIntegrityIssues(throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult closeParty(final Long partyId, final JsonCommand command) {
|
||||
|
||||
this.fromApiJsonDeserializer.validateForClose(command.json());
|
||||
|
||||
final Party party = this.partyRepository.findById(partyId).orElseThrow(() -> new PartyNotFoundException(partyId));
|
||||
|
||||
if (party.isClosed()) {
|
||||
throw new PartyAlreadyClosedException(party.getPartyCode());
|
||||
}
|
||||
if (party.getCurrentBalance().compareTo(BigDecimal.ZERO) != 0) {
|
||||
throw new PartyHasBalanceException(party.getPartyCode());
|
||||
}
|
||||
|
||||
final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
|
||||
party.close(closeDate);
|
||||
this.partyRepository.saveAndFlush(party);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(partyId).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the caller gave a savingsAccountNo, links this Party to that real Savings Account - resolved
|
||||
* by account number since that's what an officer actually has to hand, not the internal id. Left
|
||||
* unlinked (as before) when the parameter isn't given: a Party doesn't require a real account,
|
||||
* this is opportunistic linking for a client that already happens to have one.
|
||||
*/
|
||||
private void linkSavingsAccountIfProvided(final Party party, final Long expectedClientId, final JsonCommand command) {
|
||||
if (!command.parameterExists("savingsAccountNo")) {
|
||||
return;
|
||||
}
|
||||
final String savingsAccountNo = command.stringValueOfParameterNamed("savingsAccountNo");
|
||||
final SavingsAccount savingsAccount = this.savingsAccountRepository.findNonClosedAccountByAccountNumber(savingsAccountNo);
|
||||
if (savingsAccount == null) {
|
||||
throw new PartySavingsAccountNotFoundException(savingsAccountNo);
|
||||
}
|
||||
if (!expectedClientId.equals(savingsAccount.clientId())) {
|
||||
throw new PartySavingsAccountClientMismatchException(savingsAccountNo);
|
||||
}
|
||||
party.linkSavingsAccount(savingsAccount.getId());
|
||||
}
|
||||
|
||||
private void addNomineesFromCommand(final Party party, final JsonCommand command) {
|
||||
if (!command.parameterExists("nominees")) {
|
||||
return;
|
||||
}
|
||||
final JsonArray nomineesArray = command.arrayOfParameterNamed("nominees");
|
||||
if (nomineesArray == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < nomineesArray.size(); i++) {
|
||||
final JsonObject nomineeJson = nomineesArray.get(i).getAsJsonObject();
|
||||
final LocalDate nominationDate = LocalDate.parse(nomineeJson.get("nominationDate").getAsString());
|
||||
final String name = nomineeJson.get("name").getAsString();
|
||||
final String mobileNo = nomineeJson.has("mobileNo") && !nomineeJson.get("mobileNo").isJsonNull()
|
||||
? nomineeJson.get("mobileNo").getAsString()
|
||||
: null;
|
||||
final LocalDate birthDate = nomineeJson.has("birthDate") && !nomineeJson.get("birthDate").isJsonNull()
|
||||
? LocalDate.parse(nomineeJson.get("birthDate").getAsString())
|
||||
: null;
|
||||
final boolean minor = nomineeJson.has("minor") && !nomineeJson.get("minor").isJsonNull() && nomineeJson.get("minor")
|
||||
.getAsBoolean();
|
||||
final String relation = nomineeJson.get("relation").getAsString();
|
||||
final String gender = nomineeJson.get("gender").getAsString();
|
||||
final String guardianName = nomineeJson.has("guardianName") && !nomineeJson.get("guardianName").isJsonNull()
|
||||
? nomineeJson.get("guardianName").getAsString()
|
||||
: null;
|
||||
|
||||
party.addNominee(new PartyNominee(nominationDate, name, mobileNo, birthDate, minor, relation, gender, guardianName));
|
||||
}
|
||||
}
|
||||
|
||||
private String generateNextPartyCode() {
|
||||
final Long maxSuffix = this.partyRepository.retrieveMaxPartyCodeSuffix();
|
||||
final long nextSuffix = (maxSuffix == null ? 0L : maxSuffix) + 1L;
|
||||
return String.format("PT%04d", nextSuffix);
|
||||
}
|
||||
|
||||
private void handleDataIntegrityIssues(final Throwable realCause, final Exception dve) {
|
||||
if (realCause.getMessage() != null && realCause.getMessage().contains("party_code")) {
|
||||
throw new PlatformDataIntegrityException("error.msg.party.duplicate.partyCode",
|
||||
"A party with the generated code already exists, please retry");
|
||||
}
|
||||
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.party.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.dds.party.starter;
|
||||
|
||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
||||
import org.apache.fineract.portfolio.dds.party.serialization.PartyCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.dds.party.service.PartyReadPlatformService;
|
||||
import org.apache.fineract.portfolio.dds.party.service.PartyReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformService;
|
||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class DdsPartyConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PartyReadPlatformService.class)
|
||||
public PartyReadPlatformService partyReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new PartyReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PartyWritePlatformService.class)
|
||||
public PartyWritePlatformService partyWritePlatformService(PartyCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||
PartyRepository partyRepository, AgentRepository agentRepository, ClientRepositoryWrapper clientRepositoryWrapper,
|
||||
SavingsAccountRepository savingsAccountRepository) {
|
||||
return new PartyWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, partyRepository, agentRepository,
|
||||
clientRepositoryWrapper, savingsAccountRepository);
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.dds.report.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.DefaultValue;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.time.LocalDate;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.dds.report.data.DdsReportData;
|
||||
import org.apache.fineract.portfolio.dds.report.service.DdsReportReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/dds/reports/{reportKey}")
|
||||
@Component
|
||||
@Tag(name = "DDS Reports", description = "The DDS reports suite - one shared query engine behind a generic column/row shape, filtered by date range and agent/party id range.")
|
||||
@RequiredArgsConstructor
|
||||
public class DdsReportsApiResource {
|
||||
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "DDSREPORT";
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final DdsReportReadPlatformService readPlatformService;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Run a DDS Report", description = "reportKey is one of: AGENT_CHECK_LIST, PARTY_CHECK_LIST, PARTY_BALANCE, "
|
||||
+ "AGENT_WISE_BALANCE, AGENT_WISE_MONTHLY_COLLECTION, COMMISSION_REGISTER, COLLECTION_SUMMARY_AGENT_WISE, "
|
||||
+ "AGENT_PARTYWISE_DAILY_COLLECTION, DAILY_COLLECTION_REPORT, PARTY_INTEREST_REGISTER, BALANCE_SUMMARY, "
|
||||
+ "TRANSACTION_REGISTER, MATURE_DATE_WISE, INTEREST_CERTIFICATE, AGENT_COMMISSION_STATEMENT, SUPPLEMENTARY_DDS, "
|
||||
+ "CLOSE_PARTY_AC_REGISTER, MASTER_DATA_EXPORT (the last three always return available=false - no matching data exists "
|
||||
+ "in this system yet). "
|
||||
+ "fromDate/toDate are ISO (yyyy-MM-dd); agentFrom/To and partyFrom/To default to the full id range.")
|
||||
public DdsReportData runReport(@PathParam("reportKey") @Parameter(description = "reportKey") final String reportKey,
|
||||
@QueryParam("fromDate") @Parameter(description = "fromDate") final String fromDate,
|
||||
@QueryParam("toDate") @Parameter(description = "toDate") final String toDate,
|
||||
@DefaultValue("1") @QueryParam("agentFrom") @Parameter(description = "agentFrom") final Long agentFrom,
|
||||
@DefaultValue("9223372036854775807") @QueryParam("agentTo") @Parameter(description = "agentTo") final Long agentTo,
|
||||
@DefaultValue("1") @QueryParam("partyFrom") @Parameter(description = "partyFrom") final Long partyFrom,
|
||||
@DefaultValue("9223372036854775807") @QueryParam("partyTo") @Parameter(description = "partyTo") final Long partyTo) {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
|
||||
final LocalDate from = fromDate != null ? LocalDate.parse(fromDate) : LocalDate.of(1900, 1, 1);
|
||||
final LocalDate to = toDate != null ? LocalDate.parse(toDate) : LocalDate.of(9999, 12, 31);
|
||||
|
||||
return readPlatformService.runReport(reportKey, from, to, agentFrom, agentTo, partyFrom, partyTo);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.dds.report.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.portfolio.dds.report.data.DdsReportData;
|
||||
|
||||
public interface DdsReportReadPlatformService {
|
||||
|
||||
DdsReportData runReport(String reportKey, LocalDate fromDate, LocalDate toDate, Long agentFrom, Long agentTo, Long partyFrom,
|
||||
Long partyTo);
|
||||
}
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* 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.dds.report.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
|
||||
import org.apache.fineract.portfolio.dds.report.data.DdsReportData;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
/**
|
||||
* One switch over every DDS report key. Each branch is a real query against data this system
|
||||
* actually has (Agent/Party/PartyTransaction/CommissionSlab/BatchRun).
|
||||
*
|
||||
* Since SubAgent was folded into Agent (2026-08-24, Agent now does the daily collection work
|
||||
* directly), the queries here dropped a join level wherever they used to go Party/Transaction ->
|
||||
* SubAgent -> Agent - they now join straight to Agent.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class DdsReportReadPlatformServiceImpl implements DdsReportReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public DdsReportData runReport(final String reportKey, final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
||||
|
||||
return switch (reportKey) {
|
||||
case "AGENT_CHECK_LIST" -> agentCheckList();
|
||||
case "PARTY_CHECK_LIST", "PARTY_BALANCE" -> partyList(agentFrom, agentTo, partyFrom, partyTo);
|
||||
case "AGENT_WISE_BALANCE" -> agentWiseBalance(agentFrom, agentTo);
|
||||
case "AGENT_WISE_MONTHLY_COLLECTION" -> agentWiseCollection(fromDate, toDate, agentFrom, agentTo);
|
||||
case "COMMISSION_REGISTER" -> commissionRegister(fromDate, toDate, agentFrom, agentTo);
|
||||
case "COLLECTION_SUMMARY_AGENT_WISE" -> collectionSummaryAgentWise(fromDate, toDate, agentFrom, agentTo);
|
||||
case "AGENT_PARTYWISE_DAILY_COLLECTION" -> agentPartywiseDailyCollection(fromDate, toDate, agentFrom, agentTo, partyFrom,
|
||||
partyTo);
|
||||
case "DAILY_COLLECTION_REPORT" -> dailyCollectionReport(fromDate, toDate, agentFrom, agentTo);
|
||||
case "PARTY_INTEREST_REGISTER" -> partyInterestRegister(fromDate, toDate, partyFrom, partyTo);
|
||||
case "BALANCE_SUMMARY" -> balanceSummary(agentFrom, agentTo, partyFrom, partyTo);
|
||||
case "TRANSACTION_REGISTER" -> transactionRegister(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
||||
case "MATURE_DATE_WISE" -> matureDateWise(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
||||
case "INTEREST_CERTIFICATE" -> interestCertificate(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
||||
case "AGENT_COMMISSION_STATEMENT" -> agentCommissionStatement(fromDate, toDate);
|
||||
case "SUPPLEMENTARY_DDS" -> supplementaryDds(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
||||
case "CLOSE_PARTY_AC_REGISTER" -> closePartyAcRegister(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
||||
case "MASTER_DATA_EXPORT" -> masterDataExport(agentFrom, agentTo, partyFrom, partyTo);
|
||||
default -> throw new UnrecognizedQueryParamException("reportKey", reportKey);
|
||||
};
|
||||
}
|
||||
|
||||
private DdsReportData agentCheckList() {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Agent Name\", a.agent_city as \"City\", "
|
||||
+ "a.agent_current_balance as \"Current Balance\" from m_dds_agent a order by a.agent_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql);
|
||||
return available("AGENT_CHECK_LIST", List.of("Agent Code", "Agent Name", "City", "Current Balance"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData partyList(final Long agentFrom, final Long agentTo, final Long partyFrom, final Long partyTo) {
|
||||
final String sql = "select p.party_code as \"Party Code\", p.party_name as \"Party Name\", "
|
||||
+ "a.agent_code as \"Agent Code\", p.current_balance as \"Current Balance\", "
|
||||
+ "p.opening_date as \"Opening Date\" from m_dds_party p join m_dds_agent a on a.id = p.agent_id "
|
||||
+ "where a.id between ? and ? and p.id between ? and ? order by p.party_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, agentFrom, agentTo, partyFrom, partyTo);
|
||||
return available("PARTY_CHECK_LIST", List.of("Party Code", "Party Name", "Agent Code", "Current Balance", "Opening Date"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData agentWiseBalance(final Long agentFrom, final Long agentTo) {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Name\", "
|
||||
+ "a.agent_current_balance as \"Current Balance\" from m_dds_agent a where a.id between ? and ? "
|
||||
+ "order by a.agent_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, agentFrom, agentTo);
|
||||
return available("AGENT_WISE_BALANCE", List.of("Agent Code", "Name", "Current Balance"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData agentWiseCollection(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo) {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Name\", "
|
||||
+ "coalesce(sum(t.amount), 0) as \"Total Collection\" from m_dds_agent a "
|
||||
+ "left join m_dds_party_transaction t on t.agent_id = a.id and t.transaction_type = 'PARTY_TRAN' "
|
||||
+ "and t.direction = 'CREDIT' and t.transaction_date between ? and ? " + "where a.id between ? and ? "
|
||||
+ "group by a.id, a.agent_code, a.agent_name order by a.agent_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo);
|
||||
return available("AGENT_WISE_MONTHLY_COLLECTION", List.of("Agent Code", "Name", "Total Collection"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData commissionRegister(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo) {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Name\", b.job_date as \"Job Date\", "
|
||||
+ "b.posted_on_date as \"Posted On\", r.amount as \"Commission Amount\" from m_dds_batch_run_row r "
|
||||
+ "join m_dds_batch_run b on b.id = r.batch_run_id join m_dds_agent a on a.id = r.agent_id "
|
||||
+ "where b.job_type = 'COMMISSION_CALCULATION' and b.status = 'POSTED' and b.posted_on_date between ? and ? "
|
||||
+ "and a.id between ? and ? order by b.posted_on_date, a.agent_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo);
|
||||
return available("COMMISSION_REGISTER", List.of("Agent Code", "Name", "Job Date", "Posted On", "Commission Amount"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData collectionSummaryAgentWise(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo) {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Agent Name\", "
|
||||
+ "coalesce(sum(t.amount), 0) as \"Total Collection\" from m_dds_agent a "
|
||||
+ "left join m_dds_party_transaction t on t.agent_id = a.id and t.transaction_type = 'PARTY_TRAN' "
|
||||
+ "and t.direction = 'CREDIT' and t.transaction_date between ? and ? " + "where a.id between ? and ? "
|
||||
+ "group by a.id, a.agent_code, a.agent_name order by a.agent_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo);
|
||||
return available("COLLECTION_SUMMARY_AGENT_WISE", List.of("Agent Code", "Agent Name", "Total Collection"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData agentPartywiseDailyCollection(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", p.party_code as \"Party Code\", "
|
||||
+ "t.transaction_date as \"Transaction Date\", t.amount as \"Amount\", "
|
||||
+ "t.balance_after_transaction as \"Balance After\" from m_dds_party_transaction t "
|
||||
+ "join m_dds_party p on p.id = t.party_id join m_dds_agent a on a.id = t.agent_id "
|
||||
+ "where t.transaction_type = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transaction_date between ? and ? "
|
||||
+ "and a.id between ? and ? and p.id between ? and ? order by t.transaction_date, a.agent_code, p.party_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
||||
partyTo);
|
||||
return available("AGENT_PARTYWISE_DAILY_COLLECTION",
|
||||
List.of("Agent Code", "Party Code", "Transaction Date", "Amount", "Balance After"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData dailyCollectionReport(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo) {
|
||||
final String sql = "select t.transaction_date as \"Transaction Date\", a.agent_code as \"Agent Code\", "
|
||||
+ "coalesce(sum(t.amount), 0) as \"Total Collection\", count(*) as \"Transaction Count\" "
|
||||
+ "from m_dds_party_transaction t join m_dds_agent a on a.id = t.agent_id "
|
||||
+ "where t.transaction_type = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transaction_date between ? and ? "
|
||||
+ "and a.id between ? and ? group by t.transaction_date, a.id, a.agent_code "
|
||||
+ "order by t.transaction_date, a.agent_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo);
|
||||
return available("DAILY_COLLECTION_REPORT",
|
||||
List.of("Transaction Date", "Agent Code", "Total Collection", "Transaction Count"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData partyInterestRegister(final LocalDate fromDate, final LocalDate toDate, final Long partyFrom,
|
||||
final Long partyTo) {
|
||||
final String sql = "select p.party_code as \"Party Code\", t.transaction_date as \"Transaction Date\", "
|
||||
+ "t.transaction_type as \"Transaction Type\", t.amount as \"Amount\", "
|
||||
+ "t.balance_after_transaction as \"Balance After\" from m_dds_party_transaction t "
|
||||
+ "join m_dds_party p on p.id = t.party_id "
|
||||
+ "where t.transaction_type in ('INTEREST_POST', 'OD_INTEREST_POST') and t.transaction_date between ? and ? "
|
||||
+ "and p.id between ? and ? order by t.transaction_date, p.party_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, partyFrom, partyTo);
|
||||
return available("PARTY_INTEREST_REGISTER",
|
||||
List.of("Party Code", "Transaction Date", "Transaction Type", "Amount", "Balance After"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData balanceSummary(final Long agentFrom, final Long agentTo, final Long partyFrom, final Long partyTo) {
|
||||
final Integer totalAgents = this.jdbcTemplate.queryForObject("select count(*) from m_dds_agent where id between ? and ?",
|
||||
Integer.class, agentFrom, agentTo);
|
||||
final Integer totalParties = this.jdbcTemplate.queryForObject(
|
||||
"select count(*) from m_dds_party where agent_id between ? and ? and id between ? and ?", Integer.class, agentFrom,
|
||||
agentTo, partyFrom, partyTo);
|
||||
final BigDecimal totalBalance = this.jdbcTemplate.queryForObject(
|
||||
"select coalesce(sum(current_balance), 0) from m_dds_party where agent_id between ? and ? and id between ? and ?",
|
||||
BigDecimal.class, agentFrom, agentTo, partyFrom, partyTo);
|
||||
|
||||
final Map<String, Object> row = Map.of("Total Agents", totalAgents, "Total Parties", totalParties, "Total Party Balance",
|
||||
totalBalance);
|
||||
return available("BALANCE_SUMMARY", List.of("Total Agents", "Total Parties", "Total Party Balance"), List.of(row));
|
||||
}
|
||||
|
||||
private DdsReportData transactionRegister(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
||||
final String sql = "select p.party_code as \"Party Code\", a.agent_code as \"Agent Code\", "
|
||||
+ "t.transaction_date as \"Transaction Date\", t.transaction_type as \"Transaction Type\", "
|
||||
+ "t.direction as \"Direction\", t.amount as \"Amount\", t.description as \"Description\" "
|
||||
+ "from m_dds_party_transaction t join m_dds_party p on p.id = t.party_id "
|
||||
+ "join m_dds_agent a on a.id = t.agent_id "
|
||||
+ "where t.transaction_date between ? and ? and a.id between ? and ? and p.id between ? and ? "
|
||||
+ "order by t.transaction_date, t.id";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
||||
partyTo);
|
||||
return available("TRANSACTION_REGISTER",
|
||||
List.of("Party Code", "Agent Code", "Transaction Date", "Transaction Type", "Direction", "Amount", "Description"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData matureDateWise(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom, final Long agentTo,
|
||||
final Long partyFrom, final Long partyTo) {
|
||||
final String sql = "select p.party_code as \"Party Code\", p.maturity_date as \"Maturity Date\", "
|
||||
+ "p.current_balance as \"Current Balance\" from m_dds_party p "
|
||||
+ "where p.maturity_date is not null and p.maturity_date between ? and ? and p.agent_id between ? and ? "
|
||||
+ "and p.id between ? and ? order by p.maturity_date";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
||||
partyTo);
|
||||
return available("MATURE_DATE_WISE", List.of("Party Code", "Maturity Date", "Current Balance"), rows);
|
||||
}
|
||||
|
||||
private DdsReportData interestCertificate(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
||||
final String sql = "select p.party_code as \"Party Code\", p.party_name as \"Party Name\", "
|
||||
+ "p.opening_date as \"Opening Date\", p.daily_deposit_amount as \"Daily Deposit Amount\", "
|
||||
+ "p.current_balance as \"Current Balance\", coalesce((select sum(t.amount) from m_dds_party_transaction t "
|
||||
+ "where t.party_id = p.id and t.transaction_type in ('INTEREST_POST', 'OD_INTEREST_POST') "
|
||||
+ "and t.transaction_date between ? and ?), 0) as \"Interest Earned\" from m_dds_party p "
|
||||
+ "where p.agent_id between ? and ? and p.id between ? and ? order by p.party_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
||||
partyTo);
|
||||
return available("INTEREST_CERTIFICATE",
|
||||
List.of("Party Code", "Party Name", "Opening Date", "Daily Deposit Amount", "Current Balance", "Interest Earned"), rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-agent settlement statement for a date range: collection volume, commission actually
|
||||
* posted in that window (via the same BatchRunRow join as COMMISSION_REGISTER), and the
|
||||
* deposit/advance/deduction fields tracked directly on the agent. "Deduct Commission" and
|
||||
* "PC Diff" have no corresponding data anywhere in this schema yet, so they report as 0 rather
|
||||
* than a guessed figure - matching the legacy sample where most of these columns were 0.00 too.
|
||||
* No agent id range filter is applied here: the legacy screenshot for this report only ever
|
||||
* showed a date range, no agent-range inputs.
|
||||
*/
|
||||
private DdsReportData agentCommissionStatement(final LocalDate fromDate, final LocalDate toDate) {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Agent Name\", "
|
||||
+ "a.agent_saving_account_no as \"A/c No\", coalesce(collection.total, 0) as \"Total Collection\", "
|
||||
+ "coalesce(commission.total, 0) as \"Commission\", 0 as \"Deduct Commission\", "
|
||||
+ "a.agent_deposit as \"Agent Deposit\", a.festival_advance as \"Festival Advance\", "
|
||||
+ "a.other_deduction as \"Other Deduction\", "
|
||||
+ "(coalesce(commission.total, 0) - a.agent_deposit - a.festival_advance - a.other_deduction) as \"Total\", "
|
||||
+ "0 as \"PC Diff\" " + "from m_dds_agent a "
|
||||
+ "left join (select t.agent_id as agent_id, sum(t.amount) as total from m_dds_party_transaction t "
|
||||
+ "where t.transaction_type = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transaction_date between ? and ? "
|
||||
+ "group by t.agent_id) collection on collection.agent_id = a.id "
|
||||
+ "left join (select r.agent_id as agent_id, sum(r.amount) as total from m_dds_batch_run_row r "
|
||||
+ "join m_dds_batch_run b on b.id = r.batch_run_id "
|
||||
+ "where b.job_type = 'COMMISSION_CALCULATION' and b.status = 'POSTED' and b.posted_on_date between ? and ? "
|
||||
+ "group by r.agent_id) commission on commission.agent_id = a.id " + "order by a.agent_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, fromDate, toDate);
|
||||
return available("AGENT_COMMISSION_STATEMENT",
|
||||
List.of("Agent Code", "Agent Name", "A/c No", "Total Collection", "Commission", "Deduct Commission", "Agent Deposit",
|
||||
"Festival Advance", "Other Deduction", "Total", "PC Diff"),
|
||||
rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* The legacy Supplementary-DDS (As On) report: every regular collection (PARTY_TRAN, CREDIT)
|
||||
* posted in the given date range - clarified with the business as "the regular transactions
|
||||
* going on" (2026-08-24), same shape as the other collection-listing reports.
|
||||
*/
|
||||
private DdsReportData supplementaryDds(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom, final Long agentTo,
|
||||
final Long partyFrom, final Long partyTo) {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", p.party_code as \"Party Code\", p.party_name as \"Party Name\", "
|
||||
+ "t.transaction_date as \"Transaction Date\", t.transaction_mode as \"Mode\", t.amount as \"Amount\", "
|
||||
+ "t.balance_after_transaction as \"Balance After\" from m_dds_party_transaction t "
|
||||
+ "join m_dds_party p on p.id = t.party_id join m_dds_agent a on a.id = t.agent_id "
|
||||
+ "where t.transaction_type = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transaction_date between ? and ? "
|
||||
+ "and a.id between ? and ? and p.id between ? and ? order by t.transaction_date, a.agent_code, p.party_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
||||
partyTo);
|
||||
return available("SUPPLEMENTARY_DDS",
|
||||
List.of("Agent Code", "Party Code", "Party Name", "Transaction Date", "Mode", "Amount", "Balance After"), rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* The legacy Close Party Ac Register report: Parties closed (via the Close Party command) in the
|
||||
* given date range, with their close date and final (zero) balance.
|
||||
*/
|
||||
private DdsReportData closePartyAcRegister(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
||||
final String sql = "select p.party_code as \"Party Code\", p.party_name as \"Party Name\", a.agent_code as \"Agent Code\", "
|
||||
+ "p.opening_date as \"Opening Date\", p.close_date as \"Close Date\", p.current_balance as \"Final Balance\" "
|
||||
+ "from m_dds_party p join m_dds_agent a on a.id = p.agent_id "
|
||||
+ "where p.status = 'CLOSED' and p.close_date between ? and ? and a.id between ? and ? and p.id between ? and ? "
|
||||
+ "order by p.close_date, p.party_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
||||
partyTo);
|
||||
return available("CLOSE_PARTY_AC_REGISTER",
|
||||
List.of("Party Code", "Party Name", "Agent Code", "Opening Date", "Close Date", "Final Balance"), rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* The legacy Master Data Export report: clarified with the business (2026-08-24) as "select an
|
||||
* agent, show the full detail of every party under that agent" - filtered by the same agent
|
||||
* range as every other report here (a single agent is just agentFrom == agentTo).
|
||||
*/
|
||||
private DdsReportData masterDataExport(final Long agentFrom, final Long agentTo, final Long partyFrom, final Long partyTo) {
|
||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Agent Name\", "
|
||||
+ "p.party_code as \"Party Code\", p.party_name as \"Party Name\", p.address1 as \"Address\", "
|
||||
+ "p.city as \"City\", p.pincode as \"Pincode\", p.mobile_no_sms as \"Mobile No\", "
|
||||
+ "p.aadhar_no as \"Aadhar No\", p.pan_card_no as \"PAN No\", p.opening_date as \"Opening Date\", "
|
||||
+ "p.maturity_date as \"Maturity Date\", p.daily_deposit_amount as \"Daily Deposit Amount\", "
|
||||
+ "p.interest_rate as \"Interest Rate\", p.current_balance as \"Current Balance\" "
|
||||
+ "from m_dds_party p join m_dds_agent a on a.id = p.agent_id "
|
||||
+ "where a.id between ? and ? and p.id between ? and ? order by a.agent_code, p.party_code";
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, agentFrom, agentTo, partyFrom, partyTo);
|
||||
return available("MASTER_DATA_EXPORT",
|
||||
List.of("Agent Code", "Agent Name", "Party Code", "Party Name", "Address", "City", "Pincode", "Mobile No", "Aadhar No",
|
||||
"PAN No", "Opening Date", "Maturity Date", "Daily Deposit Amount", "Interest Rate", "Current Balance"),
|
||||
rows);
|
||||
}
|
||||
|
||||
private DdsReportData available(final String reportKey, final List<String> columns, final List<Map<String, Object>> rows) {
|
||||
return DdsReportData.builder().reportKey(reportKey).available(true).columns(columns).rows(rows).build();
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.dds.report.starter;
|
||||
|
||||
import org.apache.fineract.portfolio.dds.report.service.DdsReportReadPlatformService;
|
||||
import org.apache.fineract.portfolio.dds.report.service.DdsReportReadPlatformServiceImpl;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class DdsReportConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(DdsReportReadPlatformService.class)
|
||||
public DdsReportReadPlatformService ddsReportReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new DdsReportReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.dds.transaction.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.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.dds.transaction.data.PartyTransactionData;
|
||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/dds/partytransactions")
|
||||
@Component
|
||||
@Tag(name = "DDS Party Transactions", description = "The Daily Transaction Entry ledger: a day's PARTY_TRAN collection rows, the two legs of a WITHDRAW_DEPOSITE transfer between two parties, or an AGENT_TRAN settlement of an Agent's collected cash to its branch. Immutable once posted - reversed with an opposing entry, never edited.")
|
||||
@RequiredArgsConstructor
|
||||
public class PartyTransactionsApiResource {
|
||||
|
||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "PARTYTRANSACTION";
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final PartyTransactionReadPlatformService readPlatformService;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve DDS Party Transactions", description = "Returns the list of posted DDS party transactions, most recent first. Optionally filter by agentId and/or partyId.")
|
||||
public Collection<PartyTransactionData> retrieveAll(@QueryParam("agentId") @Parameter(description = "agentId") final Long agentId,
|
||||
@QueryParam("partyId") @Parameter(description = "partyId") final Long partyId) {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveAll(agentId, partyId);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{transactionId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Retrieve a DDS Party Transaction", description = "Returns the details of a single posted DDS party transaction.")
|
||||
public PartyTransactionData retrieveOne(@PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId) {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return readPlatformService.retrieveOne(transactionId);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Post DDS Party Transactions", description = "Mandatory fields: agentId, transactionType (PARTY_TRAN, "
|
||||
+ "WITHDRAW_DEPOSITE or AGENT_TRAN), transactionDate\n" + "\n"
|
||||
+ "For PARTY_TRAN: collections (array of {partyCode, transactionMode, accountType, accountNo, amount, chequeNo, tokenNo, "
|
||||
+ "description})\n" + "\n" + "For WITHDRAW_DEPOSITE: creditorPartyCode, debitPartyCode, withdrawAmount, withdrawDescription\n"
|
||||
+ "\n" + "For AGENT_TRAN: amount, transactionMode, accountType, accountNo, description")
|
||||
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.createPartyTransaction() //
|
||||
.withJson(apiRequestBodyAsJson) //
|
||||
.build();
|
||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.dds.transaction.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface PartyTransactionRepository extends JpaRepository<PartyTransaction, Long> {
|
||||
|
||||
@Query("select coalesce(sum(t.amount), 0) from PartyTransaction t where t.agent.id = :agentId "
|
||||
+ "and t.transactionType = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transactionDate between :fromDate and :toDate")
|
||||
BigDecimal sumPartyTranCredits(@Param("agentId") Long agentId, @Param("fromDate") LocalDate fromDate,
|
||||
@Param("toDate") LocalDate toDate);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.dds.transaction.handler;
|
||||
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "PARTYTRANSACTION", action = "CREATE")
|
||||
public class CreatePartyTransactionCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final PartyTransactionWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreatePartyTransactionCommandHandler(final PartyTransactionWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.createPartyTransaction(command);
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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.dds.transaction.serialization;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* PARTY_TRAN (batch collection rows), WITHDRAW_DEPOSITE (a transfer between two parties) and
|
||||
* AGENT_TRAN (an Agent settling its collected cash to a branch, no party involved) are
|
||||
* validated/processed - the three transaction types the Daily Transaction Entry screen actually
|
||||
* collects fields for today. PARTY_PAYMENT / OD_ENTRY / OD_PAYMENT are listed on the frontend as
|
||||
* not-yet-implemented placeholders, so they are rejected here rather than silently accepted with no
|
||||
* real effect.
|
||||
*/
|
||||
@Component
|
||||
public final class PartyTransactionCommandFromApiJsonDeserializer {
|
||||
|
||||
private static final Set<String> SUPPORTED_TRANSACTION_TYPES = new HashSet<>(
|
||||
Arrays.asList("PARTY_TRAN", "WITHDRAW_DEPOSITE", "AGENT_TRAN"));
|
||||
|
||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("agentId", "transactionType",
|
||||
"transactionDate", "collections", "creditorPartyCode", "debitPartyCode", "withdrawAmount", "withdrawDescription",
|
||||
"amount", "transactionMode", "accountType", "accountNo", "description", "dateFormat", "locale"));
|
||||
|
||||
private static final Set<String> SUPPORTED_COLLECTION_PARAMETERS = new HashSet<>(Arrays.asList("partyCode", "transactionMode",
|
||||
"accountType", "accountNo", "amount", "chequeNo", "tokenNo", "description"));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public PartyTransactionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForCreate(final String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
throw new InvalidJsonException();
|
||||
}
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
||||
|
||||
}.getType();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("partytransaction");
|
||||
|
||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||
|
||||
v.reset().parameter("agentId").value(this.fromApiJsonHelper.extractLongNamed("agentId", element)).notNull()
|
||||
.integerGreaterThanZero();
|
||||
|
||||
final String transactionType = this.fromApiJsonHelper.extractStringNamed("transactionType", element);
|
||||
v.reset().parameter("transactionType").value(transactionType).notBlank().isOneOfTheseStringValues(
|
||||
SUPPORTED_TRANSACTION_TYPES.toArray());
|
||||
|
||||
v.reset().parameter("transactionDate").value(this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element)).notNull();
|
||||
|
||||
if ("PARTY_TRAN".equals(transactionType)) {
|
||||
if (!this.fromApiJsonHelper.parameterExists("collections", element)) {
|
||||
v.reset().parameter("collections").value(null).notNull();
|
||||
} else {
|
||||
final JsonObject topLevel = element.getAsJsonObject();
|
||||
final var collectionsArray = topLevel.getAsJsonArray("collections");
|
||||
v.reset().parameter("collections").value(collectionsArray).jsonArrayNotEmpty();
|
||||
for (int i = 0; i < collectionsArray.size(); i++) {
|
||||
final JsonObject row = collectionsArray.get(i).getAsJsonObject();
|
||||
this.fromApiJsonHelper.checkForUnsupportedParameters(row, SUPPORTED_COLLECTION_PARAMETERS);
|
||||
|
||||
v.reset().parameterAtIndexArray("partyCode", i).value(extractString(row, "partyCode")).notBlank();
|
||||
v.reset().parameterAtIndexArray("accountNo", i).value(extractString(row, "accountNo")).notBlank();
|
||||
v.reset().parameterAtIndexArray("amount", i).value(extractBigDecimal(row, "amount")).notNull().positiveAmount();
|
||||
}
|
||||
}
|
||||
} else if ("WITHDRAW_DEPOSITE".equals(transactionType)) {
|
||||
v.reset().parameter("creditorPartyCode").value(this.fromApiJsonHelper.extractStringNamed("creditorPartyCode", element))
|
||||
.notBlank();
|
||||
v.reset().parameter("debitPartyCode").value(this.fromApiJsonHelper.extractStringNamed("debitPartyCode", element))
|
||||
.notBlank();
|
||||
v.reset().parameter("withdrawAmount").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("withdrawAmount", element))
|
||||
.notNull().positiveAmount();
|
||||
} else if ("AGENT_TRAN".equals(transactionType)) {
|
||||
v.reset().parameter("amount").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element)).notNull()
|
||||
.positiveAmount();
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private String extractString(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
||||
}
|
||||
|
||||
private java.math.BigDecimal extractBigDecimal(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsBigDecimal() : null;
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.dds.transaction.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.portfolio.dds.transaction.data.PartyTransactionData;
|
||||
|
||||
public interface PartyTransactionReadPlatformService {
|
||||
|
||||
Collection<PartyTransactionData> retrieveAll(Long agentId, Long partyId);
|
||||
|
||||
PartyTransactionData retrieveOne(Long transactionId);
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.dds.transaction.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
||||
import org.apache.fineract.infrastructure.security.utils.SQLBuilder;
|
||||
import org.apache.fineract.portfolio.dds.transaction.data.PartyTransactionData;
|
||||
import org.apache.fineract.portfolio.dds.transaction.exception.PartyTransactionNotFoundException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PartyTransactionReadPlatformServiceImpl implements PartyTransactionReadPlatformService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static final PartyTransactionMapper MAPPER = new PartyTransactionMapper();
|
||||
|
||||
private static final class PartyTransactionMapper implements RowMapper<PartyTransactionData> {
|
||||
|
||||
public String schema() {
|
||||
return " t.id as id, t.party_id as partyId, p.party_code as partyCode, p.party_name as partyName, "
|
||||
+ " t.agent_id as agentId, a.agent_code as agentCode, t.transaction_date as transactionDate, "
|
||||
+ " t.transaction_type as transactionType, t.direction as direction, t.transaction_mode as transactionMode, "
|
||||
+ " t.account_type as accountType, t.account_no as accountNo, t.amount as amount, t.cheque_no as chequeNo, "
|
||||
+ " t.token_no as tokenNo, t.description as description, t.balance_after_transaction as balanceAfterTransaction "
|
||||
+ " from m_dds_party_transaction t left join m_dds_party p on p.id = t.party_id "
|
||||
+ " join m_dds_agent a on a.id = t.agent_id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartyTransactionData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||
return PartyTransactionData.builder() //
|
||||
.id(rs.getLong("id")) //
|
||||
.partyId(JdbcSupport.getLongDefaultToNullIfZero(rs, "partyId")) //
|
||||
.partyCode(rs.getString("partyCode")) //
|
||||
.partyName(rs.getString("partyName")) //
|
||||
.agentId(rs.getLong("agentId")) //
|
||||
.agentCode(rs.getString("agentCode")) //
|
||||
.transactionDate(JdbcSupport.getLocalDate(rs, "transactionDate")) //
|
||||
.transactionType(rs.getString("transactionType")) //
|
||||
.direction(rs.getString("direction")) //
|
||||
.transactionMode(rs.getString("transactionMode")) //
|
||||
.accountType(rs.getString("accountType")) //
|
||||
.accountNo(rs.getString("accountNo")) //
|
||||
.amount(rs.getBigDecimal("amount")) //
|
||||
.chequeNo(rs.getString("chequeNo")) //
|
||||
.tokenNo(rs.getString("tokenNo")) //
|
||||
.description(rs.getString("description")) //
|
||||
.balanceAfterTransaction(rs.getBigDecimal("balanceAfterTransaction")) //
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PartyTransactionData> retrieveAll(final Long agentId, final Long partyId) {
|
||||
final SQLBuilder extraCriteria = new SQLBuilder();
|
||||
extraCriteria.addNonNullCriteria(" t.agent_id = ", agentId);
|
||||
extraCriteria.addNonNullCriteria(" t.party_id = ", partyId);
|
||||
|
||||
String sql = "select " + MAPPER.schema() + " " + extraCriteria.getSQLTemplate();
|
||||
sql = sql + " order by t.id desc";
|
||||
|
||||
return this.jdbcTemplate.query(sql, MAPPER, extraCriteria.getArguments()); // NOSONAR
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartyTransactionData retrieveOne(final Long transactionId) {
|
||||
try {
|
||||
final String sql = "select " + MAPPER.schema() + " where t.id = ?";
|
||||
return this.jdbcTemplate.queryForObject(sql, MAPPER, transactionId); // NOSONAR
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw new PartyTransactionNotFoundException(transactionId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.dds.transaction.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
public interface PartyTransactionWritePlatformService {
|
||||
|
||||
CommandProcessingResult createPartyTransaction(JsonCommand command);
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* 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.dds.transaction.service;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartyAlreadyClosedException;
|
||||
import org.apache.fineract.portfolio.dds.party.exception.PartyNotFoundException;
|
||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransaction;
|
||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransactionRepository;
|
||||
import org.apache.fineract.portfolio.dds.transaction.exception.InsufficientAgentBalanceException;
|
||||
import org.apache.fineract.portfolio.dds.transaction.exception.InsufficientPartyBalanceException;
|
||||
import org.apache.fineract.portfolio.dds.transaction.serialization.PartyTransactionCommandFromApiJsonDeserializer;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PartyTransactionWritePlatformServiceJpaRepositoryImpl implements PartyTransactionWritePlatformService {
|
||||
|
||||
private final PartyTransactionCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final PartyTransactionRepository partyTransactionRepository;
|
||||
private final PartyRepository partyRepository;
|
||||
private final AgentRepository agentRepository;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult createPartyTransaction(final JsonCommand command) {
|
||||
|
||||
try {
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
||||
|
||||
final Long agentId = command.longValueOfParameterNamed("agentId");
|
||||
final Agent agent = this.agentRepository.findById(agentId)
|
||||
.orElseThrow(() -> new AgentNotFoundException(agentId));
|
||||
|
||||
final String transactionType = command.stringValueOfParameterNamed("transactionType");
|
||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
|
||||
final List<PartyTransaction> posted = new ArrayList<>();
|
||||
if ("PARTY_TRAN".equals(transactionType)) {
|
||||
posted.addAll(postCollections(agent, transactionDate, command));
|
||||
} else if ("AGENT_TRAN".equals(transactionType)) {
|
||||
posted.add(postAgentSettlement(agent, transactionDate, command));
|
||||
} else {
|
||||
posted.addAll(postWithdrawDeposite(agent, transactionDate, command));
|
||||
}
|
||||
|
||||
this.partyTransactionRepository.saveAllAndFlush(posted);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
.withEntityId(posted.isEmpty() ? null : posted.get(0).getId()) //
|
||||
.build();
|
||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
||||
handleDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
||||
return CommandProcessingResult.empty();
|
||||
} catch (final PersistenceException dve) {
|
||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
||||
handleDataIntegrityIssues(throwable, dve);
|
||||
return CommandProcessingResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private List<PartyTransaction> postCollections(final Agent agent, final LocalDate transactionDate, final JsonCommand command) {
|
||||
final List<PartyTransaction> transactions = new ArrayList<>();
|
||||
final JsonArray collectionsArray = command.arrayOfParameterNamed("collections");
|
||||
for (int i = 0; i < collectionsArray.size(); i++) {
|
||||
final JsonObject row = collectionsArray.get(i).getAsJsonObject();
|
||||
final String partyCode = row.get("partyCode").getAsString();
|
||||
final Party party = this.partyRepository.findByPartyCode(partyCode)
|
||||
.orElseThrow(() -> new PartyNotFoundException(partyCode));
|
||||
if (party.isClosed()) {
|
||||
throw new PartyAlreadyClosedException(partyCode);
|
||||
}
|
||||
final BigDecimal amount = row.get("amount").getAsBigDecimal();
|
||||
final BigDecimal newBalance = party.credit(amount);
|
||||
agent.credit(amount);
|
||||
|
||||
transactions.add(new PartyTransaction(party, agent, transactionDate, "PARTY_TRAN", "CREDIT",
|
||||
stringOrNull(row, "transactionMode"), stringOrNull(row, "accountType"), stringOrNull(row, "accountNo"), amount,
|
||||
stringOrNull(row, "chequeNo"), stringOrNull(row, "tokenNo"), stringOrNull(row, "description"), newBalance));
|
||||
}
|
||||
return transactions;
|
||||
}
|
||||
|
||||
private PartyTransaction postAgentSettlement(final Agent agent, final LocalDate transactionDate, final JsonCommand command) {
|
||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
|
||||
if (agent.getAgentCurrentBalance().compareTo(amount) < 0) {
|
||||
throw new InsufficientAgentBalanceException(agent.getAgentCode());
|
||||
}
|
||||
final BigDecimal newBalance = agent.settle(amount);
|
||||
|
||||
return new PartyTransaction(null, agent, transactionDate, "AGENT_TRAN", "DEBIT",
|
||||
command.stringValueOfParameterNamedAllowingNull("transactionMode"),
|
||||
command.stringValueOfParameterNamedAllowingNull("accountType"),
|
||||
command.stringValueOfParameterNamedAllowingNull("accountNo"), amount, null, null,
|
||||
command.stringValueOfParameterNamedAllowingNull("description"), newBalance);
|
||||
}
|
||||
|
||||
private List<PartyTransaction> postWithdrawDeposite(final Agent agent, final LocalDate transactionDate,
|
||||
final JsonCommand command) {
|
||||
final String creditorPartyCode = command.stringValueOfParameterNamed("creditorPartyCode");
|
||||
final String debitPartyCode = command.stringValueOfParameterNamed("debitPartyCode");
|
||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("withdrawAmount");
|
||||
final String description = command.stringValueOfParameterNamedAllowingNull("withdrawDescription");
|
||||
|
||||
final Party creditorParty = this.partyRepository.findByPartyCode(creditorPartyCode)
|
||||
.orElseThrow(() -> new PartyNotFoundException(creditorPartyCode));
|
||||
final Party debitParty = this.partyRepository.findByPartyCode(debitPartyCode)
|
||||
.orElseThrow(() -> new PartyNotFoundException(debitPartyCode));
|
||||
|
||||
if (debitParty.getCurrentBalance().compareTo(amount) < 0) {
|
||||
throw new InsufficientPartyBalanceException(debitPartyCode);
|
||||
}
|
||||
|
||||
final BigDecimal debtorNewBalance = debitParty.debit(amount);
|
||||
final BigDecimal creditorNewBalance = creditorParty.credit(amount);
|
||||
|
||||
final List<PartyTransaction> transactions = new ArrayList<>();
|
||||
transactions.add(new PartyTransaction(debitParty, agent, transactionDate, "WITHDRAW_DEPOSITE", "DEBIT", null, null, null,
|
||||
amount, null, null, description, debtorNewBalance));
|
||||
transactions.add(new PartyTransaction(creditorParty, agent, transactionDate, "WITHDRAW_DEPOSITE", "CREDIT", null, null, null,
|
||||
amount, null, null, description, creditorNewBalance));
|
||||
return transactions;
|
||||
}
|
||||
|
||||
private String stringOrNull(final JsonObject json, final String paramName) {
|
||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
||||
}
|
||||
|
||||
private void handleDataIntegrityIssues(final Throwable realCause, final Exception dve) {
|
||||
if (realCause.getMessage() != null && realCause.getMessage().contains("party_code")) {
|
||||
throw new PlatformDataIntegrityException("error.msg.partytransaction.invalid.party",
|
||||
"One of the party codes in this transaction does not exist");
|
||||
}
|
||||
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, "error.msg.partytransaction.unknown.data.integrity.issue",
|
||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.dds.transaction.starter;
|
||||
|
||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransactionRepository;
|
||||
import org.apache.fineract.portfolio.dds.transaction.serialization.PartyTransactionCommandFromApiJsonDeserializer;
|
||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionReadPlatformService;
|
||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionWritePlatformService;
|
||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
@Configuration
|
||||
public class DdsPartyTransactionConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PartyTransactionReadPlatformService.class)
|
||||
public PartyTransactionReadPlatformService partyTransactionReadPlatformService(JdbcTemplate jdbcTemplate) {
|
||||
return new PartyTransactionReadPlatformServiceImpl(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PartyTransactionWritePlatformService.class)
|
||||
public PartyTransactionWritePlatformService partyTransactionWritePlatformService(
|
||||
PartyTransactionCommandFromApiJsonDeserializer fromApiJsonDeserializer, PartyTransactionRepository partyTransactionRepository,
|
||||
PartyRepository partyRepository, AgentRepository agentRepository) {
|
||||
return new PartyTransactionWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, partyTransactionRepository,
|
||||
partyRepository, agentRepository);
|
||||
}
|
||||
}
|
||||
+5
@@ -324,6 +324,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sp.product_category as productCategory, ");
|
||||
sqlBuilder.append("sp.auto_debit_facility as autoDebitFacility, sp.loan_facility as loanFacility, ");
|
||||
sqlBuilder.append("sp.premature_withdrawal_option as prematureWithdrawalOption, sp.tenure_flexibility as tenureFlexibility, ");
|
||||
sqlBuilder.append("sp.lien_marked as lienMarked, sp.maturity_instructions as maturityInstructions, ");
|
||||
@@ -412,6 +413,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
result.setEnableLockinPeriod(rs.getBoolean("enableLockinPeriod"));
|
||||
result.setBalanceFrequency(rs.getString("balanceFrequency"));
|
||||
result.setAccountClosedZeroBalanceInDayClosing(rs.getBoolean("accountClosedZeroBalanceInDayClosing"));
|
||||
result.setProductCategory(rs.getString("productCategory"));
|
||||
result.setAutoDebitFacility(rs.getBoolean("autoDebitFacility"));
|
||||
result.setLoanFacility(rs.getBoolean("loanFacility"));
|
||||
result.setPrematureWithdrawalOption(rs.getBoolean("prematureWithdrawalOption"));
|
||||
@@ -463,6 +465,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sp.product_category as productCategory, ");
|
||||
sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, ");
|
||||
sqlBuilder.append("sp.auto_debit_facility as autoDebitFacility, sp.loan_facility as loanFacility, ");
|
||||
sqlBuilder.append("sp.premature_withdrawal_option as prematureWithdrawalOption, sp.tenure_flexibility as tenureFlexibility, ");
|
||||
@@ -539,6 +542,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
final boolean enableLockinPeriod = rs.getBoolean("enableLockinPeriod");
|
||||
final String balanceFrequency = rs.getString("balanceFrequency");
|
||||
final boolean accountClosedZeroBalanceInDayClosing = rs.getBoolean("accountClosedZeroBalanceInDayClosing");
|
||||
final String productCategory = rs.getString("productCategory");
|
||||
final boolean balanceSwapOption = rs.getBoolean("balanceSwapOption");
|
||||
final boolean msme = rs.getBoolean("msme");
|
||||
final boolean autoDebitFacility = rs.getBoolean("autoDebitFacility");
|
||||
@@ -581,6 +585,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
result.setEnableLockinPeriod(enableLockinPeriod);
|
||||
result.setBalanceFrequency(balanceFrequency);
|
||||
result.setAccountClosedZeroBalanceInDayClosing(accountClosedZeroBalanceInDayClosing);
|
||||
result.setProductCategory(productCategory);
|
||||
result.setBalanceSwapOption(balanceSwapOption);
|
||||
result.setMsme(msme);
|
||||
result.setAutoDebitFacility(autoDebitFacility);
|
||||
|
||||
+2
@@ -288,6 +288,7 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
|
||||
sqlBuilder.append("sp.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sp.product_category as productCategory, ");
|
||||
sqlBuilder.append("exists(select 1 from m_saving_product_temp spt where spt.product_id = sp.id "
|
||||
+ "and spt.product_type = 'SAVINGS' and spt.status = 'PENDING') as isPendingApproval ");
|
||||
|
||||
@@ -412,6 +413,7 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
|
||||
productData.setEnableLockinPeriod(rs.getBoolean("enableLockinPeriod"));
|
||||
productData.setBalanceFrequency(rs.getString("balanceFrequency"));
|
||||
productData.setAccountClosedZeroBalanceInDayClosing(rs.getBoolean("accountClosedZeroBalanceInDayClosing"));
|
||||
productData.setProductCategory(rs.getString("productCategory"));
|
||||
productData.setIsPendingApproval(rs.getBoolean("isPendingApproval"));
|
||||
|
||||
return productData;
|
||||
|
||||
@@ -537,4 +537,11 @@
|
||||
<include file="parts/0467_add_welcome_kit_return_tracking.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0468_add_address_id_to_welcome_kit_dispatch.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0469_create_database_export_request.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0470_add_product_category_to_savings_product.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0471_create_dds_agent.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0473_create_dds_party.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0474_create_dds_party_transaction.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0475_create_dds_commission_slab.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0476_create_dds_batch_run.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0477_add_dds_report_permission.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
<!-- Tags a savings product with a business category (e.g. "DDS") so product types built as
|
||||
Recurring Deposit variants can be distinguished from plain Recurring Deposit products in
|
||||
listings, without duplicating the underlying product/account engine. Nullable and unused
|
||||
by existing products. -->
|
||||
<changeSet id="0470_add_product_category_to_savings_product" author="CBS" runOnChange="false">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<columnExists tableName="m_savings_product" columnName="product_category"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_savings_product">
|
||||
<column name="product_category" type="VARCHAR(50)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
DDS (Daily Deposit Scheme) Phase 2 - Agent Master. An Agent is a DDS collection agent - either
|
||||
a person or, at the top of the hierarchy, the bank itself - with its own settlement savings
|
||||
account and one or more guarantors. This is new feature work (not a reused RD/savings table),
|
||||
per the DDS architecture decision to keep the agent/commission/reporting half of DDS separate.
|
||||
-->
|
||||
<changeSet id="0471_create_dds_agent" author="CBS">
|
||||
|
||||
<createTable tableName="m_dds_agent">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="client_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_code" type="VARCHAR(20)">
|
||||
<constraints nullable="false" unique="true" uniqueConstraintName="uq_dds_agent_code"/>
|
||||
</column>
|
||||
<column name="agent_name" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_address1" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_address2" type="VARCHAR(200)"/>
|
||||
<column name="agent_city" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_pincode" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_telephone" type="VARCHAR(20)"/>
|
||||
<column name="agent_saving_account_type" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_saving_account_no" type="VARCHAR(50)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_opening_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_birth_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_current_balance" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_opening_balance" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<!-- Merged in from SubAgent (2026-08-24) - Agent now does the daily collection work
|
||||
directly, SubAgent no longer exists as a separate tier. -->
|
||||
<column name="home_branch" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="saving_branch" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="tds_applicable" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="tds_percent" type="DECIMAL(19,6)"/>
|
||||
<column name="current_tds" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="tds_amount" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="transfer_branch" type="VARCHAR(100)"/>
|
||||
<column name="transfer_amount_account_type" type="VARCHAR(100)"/>
|
||||
<column name="transfer_account_no" type="VARCHAR(50)"/>
|
||||
<column name="collector_commission_calculation" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="collector_limit_amount" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="machine_rent" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_security_branch" type="VARCHAR(100)"/>
|
||||
<column name="agent_security_account_type" type="VARCHAR(100)"/>
|
||||
<column name="agent_security_account_no" type="VARCHAR(50)"/>
|
||||
<column name="commission" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="other_commission" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_deposit" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="festival_advance" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="other_deduction" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="last_commission_date" type="DATE"/>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_agent" baseColumnNames="client_id" constraintName="fk_dds_agent_client"
|
||||
referencedTableName="m_client" referencedColumnNames="id"/>
|
||||
<createIndex tableName="m_dds_agent" indexName="idx_dds_agent_client_id">
|
||||
<column name="client_id"/>
|
||||
</createIndex>
|
||||
|
||||
<createTable tableName="m_dds_agent_guarantor">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="client_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="guarantor_name" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="address1" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="address2" type="VARCHAR(200)"/>
|
||||
<column name="city" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="pin_code" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="telephone" type="VARCHAR(20)"/>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_agent_guarantor" baseColumnNames="agent_id"
|
||||
constraintName="fk_dds_agent_guarantor_agent" referencedTableName="m_dds_agent"
|
||||
referencedColumnNames="id" onDelete="CASCADE"/>
|
||||
<createIndex tableName="m_dds_agent_guarantor" indexName="idx_dds_agent_guarantor_agent_id">
|
||||
<column name="agent_id"/>
|
||||
</createIndex>
|
||||
|
||||
<createTable tableName="m_dds_agent_limit">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="from_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="to_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="limit_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_agent_limit" baseColumnNames="agent_id"
|
||||
constraintName="fk_dds_agent_limit_agent" referencedTableName="m_dds_agent"
|
||||
referencedColumnNames="id" onDelete="CASCADE"/>
|
||||
<createIndex tableName="m_dds_agent_limit" indexName="idx_dds_agent_limit_agent_id">
|
||||
<column name="agent_id"/>
|
||||
</createIndex>
|
||||
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="READ_AGENT"/>
|
||||
<column name="entity_name" value="AGENT"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="CREATE_AGENT"/>
|
||||
<column name="entity_name" value="AGENT"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="CREATE_AGENT_CHECKER"/>
|
||||
<column name="entity_name" value="AGENT"/>
|
||||
<column name="action_name" value="CREATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="UPDATE_AGENT"/>
|
||||
<column name="entity_name" value="AGENT"/>
|
||||
<column name="action_name" value="UPDATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="UPDATE_AGENT_CHECKER"/>
|
||||
<column name="entity_name" value="AGENT"/>
|
||||
<column name="action_name" value="UPDATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
DDS Phase 4 - Party Master. A Party is the customer's daily-deposit scheme account, linked to
|
||||
the Agent who collects for it (m_dds_agent). Carries the scheme's KYC, terms, two-stage
|
||||
overdue flags and a nominee list.
|
||||
|
||||
savings_account_id is a nullable placeholder, not yet wired up: the DDS architecture decision
|
||||
is to eventually reuse the existing Recurring Deposit account engine for the deposit half
|
||||
(interest, maturity, posting), but the Party Master screen this table backs collects several
|
||||
mandatory RD-account fields (interest/compounding/posting periods, chart selection, etc.) that
|
||||
this screen does not capture. Rather than guess defaults for those, the scheme/balance fields
|
||||
below (depositAmount, maturityDate, interestRate, currentBalance, ...) are this record's own
|
||||
for now; wiring a real m_savings_account row is a follow-up, not attempted here.
|
||||
-->
|
||||
<changeSet id="0473_create_dds_party" author="CBS">
|
||||
|
||||
<createTable tableName="m_dds_party">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="client_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="savings_account_id" type="BIGINT"/>
|
||||
<column name="sub_type" type="VARCHAR(60)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="party_code" type="VARCHAR(20)">
|
||||
<constraints nullable="false" unique="true" uniqueConstraintName="uq_dds_party_code"/>
|
||||
</column>
|
||||
<column name="last_account" type="VARCHAR(50)"/>
|
||||
<column name="nominal" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="aadhar_no" type="VARCHAR(20)"/>
|
||||
<column name="party_name" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="party_name1" type="VARCHAR(100)"/>
|
||||
<column name="address1" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="address2" type="VARCHAR(200)"/>
|
||||
<column name="address3" type="VARCHAR(200)"/>
|
||||
<column name="city" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="pincode" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="gender" type="VARCHAR(20)"/>
|
||||
<column name="pan_card_no" type="VARCHAR(20)"/>
|
||||
<column name="telephone" type="VARCHAR(20)"/>
|
||||
<column name="mobile_no_sms" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="operation" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="birth_date" type="DATE"/>
|
||||
<column name="occupation" type="VARCHAR(40)"/>
|
||||
<column name="risk" type="VARCHAR(20)"/>
|
||||
<column name="ckyc_id" type="VARCHAR(40)"/>
|
||||
<column name="adult_minor" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="minor_date" type="DATE"/>
|
||||
<column name="guardian_client_id" type="BIGINT"/>
|
||||
<column name="min_account_opt" type="VARCHAR(40)"/>
|
||||
<column name="relation" type="VARCHAR(40)"/>
|
||||
<column name="opening_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="months" type="INT"/>
|
||||
<column name="maturity_date" type="DATE"/>
|
||||
<column name="reopen_date" type="DATE"/>
|
||||
<column name="deposit_amount" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="daily_deposit_amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="days" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ledger_no" type="VARCHAR(40)"/>
|
||||
<column name="maturity_amount" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="interest_rate" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="withdraw_type" type="VARCHAR(20)"/>
|
||||
<column name="od1_flag" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="od2_flag" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="lien" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="pay_date" type="DATE"/>
|
||||
<column name="payment_amount" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="current_balance" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="opening_balance" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="is_sms_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="is_email_active" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="interest_calc" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="interest_date" type="DATE"/>
|
||||
<column name="sp_description" type="VARCHAR(500)"/>
|
||||
<column name="freeze_code" type="VARCHAR(20)" defaultValue="Normal">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="adv_code" type="VARCHAR(40)" defaultValue="None">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="credit_to_branch" type="VARCHAR(100)"/>
|
||||
<column name="credit_to_account_type" type="VARCHAR(100)"/>
|
||||
<column name="credit_to_account" type="VARCHAR(50)"/>
|
||||
<column name="nominee_applicable" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="nominee_split_mode" type="VARCHAR(20)" defaultValue="Percentage"/>
|
||||
<column name="status" type="VARCHAR(20)" defaultValue="ACTIVE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="close_date" type="DATE"/>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_party" baseColumnNames="client_id" constraintName="fk_dds_party_client"
|
||||
referencedTableName="m_client" referencedColumnNames="id"/>
|
||||
<createIndex tableName="m_dds_party" indexName="idx_dds_party_client_id">
|
||||
<column name="client_id"/>
|
||||
</createIndex>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_party" baseColumnNames="agent_id" constraintName="fk_dds_party_agent"
|
||||
referencedTableName="m_dds_agent" referencedColumnNames="id"/>
|
||||
<createIndex tableName="m_dds_party" indexName="idx_dds_party_agent_id">
|
||||
<column name="agent_id"/>
|
||||
</createIndex>
|
||||
|
||||
<createTable tableName="m_dds_party_nominee">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="party_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="nomination_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="mobile_no" type="VARCHAR(20)"/>
|
||||
<column name="birth_date" type="DATE"/>
|
||||
<column name="minor" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="relation" type="VARCHAR(40)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="gender" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="guardian_name" type="VARCHAR(100)"/>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_party_nominee" baseColumnNames="party_id" constraintName="fk_dds_party_nominee_party"
|
||||
referencedTableName="m_dds_party" referencedColumnNames="id" onDelete="CASCADE"/>
|
||||
<createIndex tableName="m_dds_party_nominee" indexName="idx_dds_party_nominee_party_id">
|
||||
<column name="party_id"/>
|
||||
</createIndex>
|
||||
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="READ_PARTY"/>
|
||||
<column name="entity_name" value="PARTY"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CREATE_PARTY"/>
|
||||
<column name="entity_name" value="PARTY"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CREATE_PARTY_CHECKER"/>
|
||||
<column name="entity_name" value="PARTY"/>
|
||||
<column name="action_name" value="CREATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="UPDATE_PARTY"/>
|
||||
<column name="entity_name" value="PARTY"/>
|
||||
<column name="action_name" value="UPDATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="UPDATE_PARTY_CHECKER"/>
|
||||
<column name="entity_name" value="PARTY"/>
|
||||
<column name="action_name" value="UPDATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CLOSE_PARTY"/>
|
||||
<column name="entity_name" value="PARTY"/>
|
||||
<column name="action_name" value="CLOSE"/>
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CLOSE_PARTY_CHECKER"/>
|
||||
<column name="entity_name" value="PARTY"/>
|
||||
<column name="action_name" value="CLOSE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
DDS Phase 5 - Daily Transaction Entry. Posted ledger entries: a Party's day's collection rows
|
||||
(PARTY_TRAN, direction CREDIT), the two legs of a Withdraw Deposite transfer between two
|
||||
parties (creditor leg CREDIT, debtor leg DEBIT), or an Agent settling collected cash to its
|
||||
branch (AGENT_TRAN, direction DEBIT - party_id is null for this type, there is no party
|
||||
involved). No update permission is seeded - transactions are reversed with an opposing entry,
|
||||
not edited, matching normal accounting practice.
|
||||
-->
|
||||
<changeSet id="0474_create_dds_party_transaction" author="CBS">
|
||||
|
||||
<createTable tableName="m_dds_party_transaction">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="party_id" type="BIGINT"/>
|
||||
<column name="agent_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="transaction_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="transaction_type" type="VARCHAR(30)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="direction" type="VARCHAR(10)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="transaction_mode" type="VARCHAR(20)"/>
|
||||
<column name="account_type" type="VARCHAR(100)"/>
|
||||
<column name="account_no" type="VARCHAR(50)"/>
|
||||
<column name="amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="cheque_no" type="VARCHAR(40)"/>
|
||||
<column name="token_no" type="VARCHAR(40)"/>
|
||||
<column name="description" type="VARCHAR(500)"/>
|
||||
<column name="balance_after_transaction" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_party_transaction" baseColumnNames="party_id"
|
||||
constraintName="fk_dds_party_transaction_party" referencedTableName="m_dds_party"
|
||||
referencedColumnNames="id"/>
|
||||
<createIndex tableName="m_dds_party_transaction" indexName="idx_dds_party_transaction_party_id">
|
||||
<column name="party_id"/>
|
||||
</createIndex>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_party_transaction" baseColumnNames="agent_id"
|
||||
constraintName="fk_dds_party_transaction_agent" referencedTableName="m_dds_agent"
|
||||
referencedColumnNames="id"/>
|
||||
<createIndex tableName="m_dds_party_transaction" indexName="idx_dds_party_transaction_agent_id">
|
||||
<column name="agent_id"/>
|
||||
</createIndex>
|
||||
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="READ_PARTYTRANSACTION"/>
|
||||
<column name="entity_name" value="PARTYTRANSACTION"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CREATE_PARTYTRANSACTION"/>
|
||||
<column name="entity_name" value="PARTYTRANSACTION"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="CREATE_PARTYTRANSACTION_CHECKER"/>
|
||||
<column name="entity_name" value="PARTYTRANSACTION"/>
|
||||
<column name="action_name" value="CREATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
DDS Phase 6 - Commission Slab Master: the agent commission chart (ACCOUNT slabs and
|
||||
COMMISSION override slabs). Append-only reference data - no update/delete permission is
|
||||
seeded, matching the legacy screen's behaviour.
|
||||
-->
|
||||
<changeSet id="0475_create_dds_commission_slab" author="CBS">
|
||||
|
||||
<createTable tableName="m_dds_commission_slab">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="slab_type" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="slab_from" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="slab_to" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="commission_percent" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<createIndex tableName="m_dds_commission_slab" indexName="idx_dds_commission_slab_type">
|
||||
<column name="slab_type"/>
|
||||
</createIndex>
|
||||
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="READ_COMMISSIONSLAB"/>
|
||||
<column name="entity_name" value="COMMISSIONSLAB"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="CREATE_COMMISSIONSLAB"/>
|
||||
<column name="entity_name" value="COMMISSIONSLAB"/>
|
||||
<column name="action_name" value="CREATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="CREATE_COMMISSIONSLAB_CHECKER"/>
|
||||
<column name="entity_name" value="COMMISSIONSLAB"/>
|
||||
<column name="action_name" value="CREATE_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
DDS Phase 7 - Interest/Commission Batch. The Calculate-then-Post control from the legacy
|
||||
Interest Part menu: Generate previews a run's rows (persisted, but nothing else changes);
|
||||
Post applies exactly those previewed rows and can only happen once per run.
|
||||
|
||||
last_commission_date on m_dds_agent (added directly in Phase 2's create table, 2026-08-24)
|
||||
tracks how far an agent's commission has already been calculated through, so each Commission
|
||||
Calculation run only covers collections since the last one.
|
||||
-->
|
||||
<changeSet id="0476_create_dds_batch_run" author="CBS">
|
||||
|
||||
<createTable tableName="m_dds_batch_run">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="job_type" type="VARCHAR(40)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="branch_scope" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="job_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_from" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="agent_to" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="party_from" type="BIGINT"/>
|
||||
<column name="party_to" type="BIGINT"/>
|
||||
<column name="status" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_rows" type="INT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="total_amount" type="DECIMAL(19,6)" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="generated_on_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="posted_on_date" type="DATE"/>
|
||||
</createTable>
|
||||
|
||||
<createTable tableName="m_dds_batch_run_row">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="batch_run_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="party_id" type="BIGINT"/>
|
||||
<column name="agent_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="amount" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseTableName="m_dds_batch_run_row" baseColumnNames="batch_run_id"
|
||||
constraintName="fk_dds_batch_run_row_batch_run" referencedTableName="m_dds_batch_run"
|
||||
referencedColumnNames="id" onDelete="CASCADE"/>
|
||||
<createIndex tableName="m_dds_batch_run_row" indexName="idx_dds_batch_run_row_batch_run_id">
|
||||
<column name="batch_run_id"/>
|
||||
</createIndex>
|
||||
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="READ_DDSBATCH"/>
|
||||
<column name="entity_name" value="DDSBATCH"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="GENERATE_DDSBATCH"/>
|
||||
<column name="entity_name" value="DDSBATCH"/>
|
||||
<column name="action_name" value="GENERATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="POST_DDSBATCH"/>
|
||||
<column name="entity_name" value="DDSBATCH"/>
|
||||
<column name="action_name" value="POST"/>
|
||||
<column name="can_maker_checker" valueBoolean="true"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="POST_DDSBATCH_CHECKER"/>
|
||||
<column name="entity_name" value="DDSBATCH"/>
|
||||
<column name="action_name" value="POST_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
DDS Phase 8 - Reports. Pure read-only queries over the existing DDS tables, so no new tables
|
||||
are needed here - just the permission gating access to the report endpoint.
|
||||
-->
|
||||
<changeSet id="0477_add_dds_report_permission" author="CBS">
|
||||
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="READ_DDSREPORT"/>
|
||||
<column name="entity_name" value="DDSREPORT"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user