diff --git a/build/build-config.yml b/build/build-config.yml
index bd81babfa..98545f953 100644
--- a/build/build-config.yml
+++ b/build/build-config.yml
@@ -118,3 +118,10 @@ config:
dockerfile: "build/maven/Dockerfile"
- work-dir: "municipal-services/property-services/src/main/resources/db"
image-name: "property-services-db"
+ - name: "builds/mGramSeva/business-services/egov-apportion-service"
+ build:
+ - work-dir: "business-services/egov-apportion-service"
+ image-name: "egov-apportion-service"
+ dockerfile: "build/maven/Dockerfile"
+ - work-dir: "business-services/egov-apportion-service/src/main/resources/db"
+ image-name: "egov-apportion-service-db"
diff --git a/business-services/egov-apportion-service/Apportion.postman_collection.json b/business-services/egov-apportion-service/Apportion.postman_collection.json
new file mode 100644
index 000000000..8ae77190d
--- /dev/null
+++ b/business-services/egov-apportion-service/Apportion.postman_collection.json
@@ -0,0 +1,40 @@
+{
+ "info": {
+ "_postman_id": "199db000-e1ea-41e0-a559-2fca1e4d5d9d",
+ "name": "Apportion",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "apportion",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"RequestInfo\": {\n \"apiId\": \"Rainmaker\",\n \"action\": \"\",\n \"did\": 1,\n \"key\": \"\",\n \"msgId\": \"20170310130900|en_IN\",\n \"requesterId\": \"\",\n \"ts\": \"\",\n \"ver\": \".01\",\n \"userInfo\": {\n \n \"id\": 104,\n \"uuid\": \"1dc1db2c-6f26-4803-a3a7-028eb4abff6e\",\n \"userName\": \"9999999999\",\n \"name\": \"dfghj\",\n \"gender\": null,\n \"mobileNumber\": \"9999999999\",\n \"type\": \"CITIZEN\",\n \"roles\": [\n {\n \"id\": null,\n \"name\": \"Citizen\",\n \"code\": \"CITIZEN\"\n }\n ],\n \"tenantId\": \"pb\"\n },\n \"authToken\": \"7f7d617b-f7b4-4e2c-907d-dfd0256f2331\"\n },\n \"tenantId\":\"pb.amritsar\",\n \"Bills\":[\n {\n \"id\": \"10090\",\n \"payeeName\": \"NAMEchanged\",\n \"payeeAddress\": \"ADDRESS\",\n \"mobileNumber\": \"9999999999\",\n \"payeeEmail\": null,\n \"isActive\": true,\n \"isCancelled\": false,\n \"collectionMap\": {\n \"PT\": \"213409\"\n },\n \"billDetails\": [\n {\n \"id\": \"10166\",\n \"bill\": \"10090\",\n \"billDate\": 1551249289654,\n \"billDescription\": \"PT Consumer Code: PT-107-016526:AS-2019-02-27-016985\",\n \"billNumber\": \"10166\",\n \"consumerCode\": \"PT-107-016526:AS-2019-02-27-016985\",\n \"consumerType\": \"BUILTUP\",\n \"minimumAmount\": 100,\n \"totalAmount\": 213409,\n \"collectedAmount\": 0,\n \"collectionModesNotAllowed\": [\n \"\"\n ],\n \"tenantId\": \"pb.amritsar\",\n \"businessService\": \"PT\",\n \"displayMessage\": \"PT Consumer Code: PT-107-016526:AS-2019-02-27-016985\",\n \"callBackForApportioning\": false,\n \"receiptNumber\": null,\n \"receiptDate\": null,\n \"receiptType\": null,\n \"channel\": null,\n \"voucherHeader\": null,\n \"boundary\": null,\n \"reasonForCancellation\": null,\n \"amountPaid\": null,\n \"cancellationRemarks\": null,\n \"status\": null,\n \"billAccountDetails\": [\n {\n \"id\": \"37770\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1100101\",\n \"order\": 2,\n \"accountDescription\": \"PT_CANCER_CESS-1522540800000-1554076799000\",\n \"amount\": 8750,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"CURRENT_AMOUNT\"\n },\n {\n \"id\": \"37771\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1405019\",\n \"order\": 99,\n \"accountDescription\": \"PT_DECIMAL_CEILING_CREDIT-1522540800000-1554076799000\",\n \"amount\": 0.1,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"CURRENT_AMOUNT\"\n },\n {\n \"id\": \"37772\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1405013\",\n \"order\": 2,\n \"accountDescription\": \"PT_FIRE_CESS-1522540800000-1554076799000\",\n \"amount\": 8750,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"CURRENT_AMOUNT\"\n },\n {\n \"id\": \"37773\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1405015\",\n \"order\": 1,\n \"accountDescription\": \"PT_OWNER_EXEMPTION-1522540800000-1554076799000\",\n \"amount\": -5000,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"EXEMPTION\"\n },\n {\n \"id\": \"37774\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1405013\",\n \"order\": 3,\n \"accountDescription\": \"PT_TAX-1522540800000-1554076799000\",\n \"amount\": 180000,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"CURRENT_AMOUNT\"\n },\n {\n \"id\": \"37775\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1405019\",\n \"order\": 10,\n \"accountDescription\": \"PT_TIME_INTEREST-1522540800000-1554076799000\",\n \"amount\": 3408.9,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"OTHERS\"\n },\n {\n \"id\": \"37776\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1405013\",\n \"order\": 1,\n \"accountDescription\": \"PT_TIME_PENALTY-1522540800000-1554076799000\",\n \"amount\": 17500,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"OTHERS\"\n },\n {\n \"id\": \"37777\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1405016\",\n \"order\": 1,\n \"accountDescription\": \"PT_TIME_REBATE-1522540800000-1554076799000\",\n \"amount\": 0,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"REBATE\"\n },\n {\n \"id\": \"37778\",\n \"tenantId\": \"pb.amritsar\",\n \"billDetail\": \"10166\",\n \"glcode\": \"1405019\",\n \"order\": 100,\n \"accountDescription\": \"PT_UNIT_USAGE_EXEMPTION-1522540800000-1554076799000\",\n \"amount\": 0,\n \"adjustedAmount\": 0,\n \"isActualDemand\": true,\n \"purpose\": \"EXEMPTION\"\n }\n ],\n \"manualReceiptNumber\": null,\n \"stateId\": null\n } \n ],\n \"tenantId\": \"pb.amritsar\",\n \"auditDetails\": null\n }\n \t]\n\n}"
+ },
+ "url": {
+ "raw": "https://egov-micro-dev.egovernments.org/apportion-service/v1/_apportion",
+ "protocol": "https",
+ "host": [
+ "egov-micro-dev",
+ "egovernments",
+ "org"
+ ],
+ "path": [
+ "apportion-service",
+ "v1",
+ "_apportion"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/business-services/egov-apportion-service/CHANGELOG.md b/business-services/egov-apportion-service/CHANGELOG.md
new file mode 100644
index 000000000..46f281803
--- /dev/null
+++ b/business-services/egov-apportion-service/CHANGELOG.md
@@ -0,0 +1,37 @@
+
+
+# Changelog
+All notable changes to this module will be documented in this file.
+## 1.1.5 - 2023-02-02
+
+- Transition from 1.1.5-beta version to 1.1.5 version
+
+## 1.1.5-beta - 2022-01-13
+- Updated to log4j2 version 2.17.1
+
+## 1.1.4 2021-02-26
+
+- Updated domain name in application.properties
+
+## 1.1.3 2021-01-12
+
+- Apportion and backupdate to demands enabled for zero payments, which was blocked till this point of time
+
+## 1.1.3
+
+## 1.1.2 2020-08-24
+- Fixed Bug RAIN-1432: Added audit for demand apportion
+
+## 1.1.1 - 2020-06-17
+
+- Fixed Bug WOR-415: Regarding adjustedAmount during multiple advance adjustment
+
+## 1.1.0 - 2020-06-25
+- Added typescript definition generation plugin
+- Upgraded to `tracer:2.0.0-SNAPSHOT`
+- Upgraded to spring boot `2.2.6-RELEASE`
+- Deleted `Dockerfile` and `start.sh` as it is no longer in use
+
+## 1.0.0
+
+- Base version
diff --git a/business-services/egov-apportion-service/LOCALSETUP.md b/business-services/egov-apportion-service/LOCALSETUP.md
new file mode 100644
index 000000000..e696a7854
--- /dev/null
+++ b/business-services/egov-apportion-service/LOCALSETUP.md
@@ -0,0 +1,37 @@
+# Local Setup
+
+To setup the egov-apportion-service service in your local system, clone the [Business Service repository](https://github.com/egovernments/business-services).
+
+## Dependencies
+
+### Infra Dependency
+
+- [x] Postgres DB
+- [ ] Redis
+- [ ] Elasticsearch
+- [x] Kafka
+ - [ ] Consumer
+ - [x] Producer
+
+## Running Locally
+
+To run the egov-apportion-service in local system, you need to port forward below services.
+
+```bash
+ kubectl port-forward -n egov {egov-mdms} 8088:8080
+```
+
+Update below listed properties in `application.properties` before running the project:
+
+```ini
+
+-spring.datasource.url=jdbc:postgresql://localhost:5432/{local postgres db name}
+
+-spring.flyway.url=jdbc:postgresql://localhost:5432/{local postgres db name}
+
+-egov.mdms.host={mdms hostname}
+
+```
+
+Optionally egov=persister should be ran locally if auditing is required and following config path should be added in it:
+ (https://raw.githubusercontent.com/egovernments/configs/master/egov-persister/apportion-persister.yml)
\ No newline at end of file
diff --git a/business-services/egov-apportion-service/README.md b/business-services/egov-apportion-service/README.md
new file mode 100644
index 000000000..f8d6cda41
--- /dev/null
+++ b/business-services/egov-apportion-service/README.md
@@ -0,0 +1,73 @@
+
+
+# eGov ApportionService
+
+
+Module is used to distribute the paid amount among the taxHeads.
+
+### Apportion
+The Apportioning service is used to distribute the paid amount among the respective taxHead.
+There are two API's first is /apportion-service/bill/_apportion endpoint which is used to apportion the bill. In this case each bill is processed seperately.
+The billDetails in each bill are grouped by businessService. For each group the apportionPaidAmount() function is called to apportion the paid amount.
+The second endpoint is /apportion-service/demand/_apportion which is used to apportion advance amount in demands. Demands for the same consumer code should be sent for the apportion.
+The interface Apportion is to be implemented for custom logic. Default implementation is provided in class called OrderByPriorityApportion,
+Default Implementation:
+ 1. Apportions the paid amount based on the order in each taxhead (ascending wise priority)
+ 2. If multiple billDetails are present they are sorted by fromPeriod and the oldest one is apportioned first
+ 3. Validation is placed to enforce that all taxHeads containing negative amounts should have order less than the one with positive amounts
+ 4. Any advance amount is added to the latest billDetail as new billAccountDetail
+For custom implementation the methods getBusinessService() and apportionPaidAmount() has to be implemented. The first method returns key of the implementation while the second method contains the apportion logic for the particular key
+The apportion request and respose are stored for audit using persister
+
+
+
+### Service Dependencies
+- egov-mdms
+- egov-persister
+
+
+### Project Structure
+*Packages*
+ - config - Contains all the configuration properties related to module
+ - service - Consists of all services containing the business logic.
+ - util - Contains utility functions and constants.
+ - repository - Fetch data from dependent micro services
+ - web/controllers - Controllers for the app.
+ - web/models - POJO for the module.
+ - producer - Contains kafka producer
+
+
+### Resources
+- Granular details about the API's can be found in the [swagger api definition](https://raw.githubusercontent.com/egovernments/docs/collections/contracts/apportion/egov-apportion-service.yml)
+- Postman collection for all the API's can be found in the [postman collection](https://raw.githubusercontent.com/egovernments/egov-services/core/egov-apportion-service/Apportion.postman_collection.json)
+
+
+## Build & Run
+
+
+ mvn clean install
+ java -jar target/egov-apportion-service-1.1.1-SNAPSHOT.jar
+
+
+
+### API Details
+
+`BasePath` /apportion-service/v2/[API endpoint]
+
+##### Method
+**a) Apportion Bill `POST /bill/_apportion` :** API (Bulk API) Apportions the paid amount in the field collectedAmount of billDetails in the bill
+**b) Apportion demands `POST /demand/_apportion` :** API Apportions the advance amount from previous billing cycles in the latest demands
+
+
+### Kafka Consumers
+
+- NA
+
+
+### Kafka Producers
+
+- Following are the Producer topic.
+ - **save-apportion-bill-request** :- This topic is used to save the bill apportion request for audit.
+ - **save-apportion-bill-response** :- This topic is used to save the bill apportion response for audit.
+ - **save-apportion-demand-request** :- This topic is used to save the demand apportion request for audit.
+ - **save-apportion-demand-response** :- This topic is used to save the demand apportion response for audit.
diff --git a/business-services/egov-apportion-service/pom.xml b/business-services/egov-apportion-service/pom.xml
new file mode 100644
index 000000000..0a33e1589
--- /dev/null
+++ b/business-services/egov-apportion-service/pom.xml
@@ -0,0 +1,176 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.6.RELEASE
+
+ org.egov
+ egov-apportion-service
+ 1.1.5-SNAPSHOT
+ egov-apportion-service
+
+ 2.17.1
+ 1.8
+ ${java.version}
+ 1.18.8
+ ${java.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework
+ spring-beans
+ 5.2.20.RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.swagger
+ swagger-core
+ 1.5.18
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.egov.services
+ services-common
+ 1.0.0-RELEASE
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+ org.postgresql
+ postgresql
+ 42.2.2.jre7
+
+
+ javax.validation
+ validation-api
+
+
+ org.egov.services
+ tracer
+ 2.0.0-SNAPSHOT
+ compile
+
+
+ org.egov
+ mdms-client
+ 0.0.2-SNAPSHOT
+ compile
+
+
+
+
+ repo.egovernments.org
+ eGov ERP Releases Repository
+ https://nexus-repo.egovernments.org/nexus/content/repositories/releases/
+
+
+ repo.egovernments.org.snapshots
+ eGov ERP Releases Repository
+ https://nexus-repo.egovernments.org/nexus/content/repositories/snapshots/
+
+
+ repo.egovernments.org.public
+ eGov Public Repository Group
+ https://nexus-repo.egovernments.org/nexus/content/groups/public/
+
+
+
+ src/main/java
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+ org.springframework.boot
+ spring-boot-devtools
+
+
+
+
+
+ cz.habarta.typescript-generator
+ typescript-generator-maven-plugin
+ 2.22.595
+
+
+ generate
+
+ generate
+
+ process-classes
+
+
+
+ jackson2
+
+ org.egov.web.models.ApportionDemandResponse
+ org.egov.web.models.ApportionRequest
+ org.egov.web.models.ApportionResponse
+ org.egov.web.models.ApportionRequestV2
+ org.egov.web.models.Bill
+ org.egov.web.models.BillAccountDetail
+ org.egov.web.models.BillDetail
+ org.egov.web.models.Demand
+ org.egov.web.models.DemandDetail
+ org.egov.web.models.Role
+ org.egov.web.models.TaxHeadMaster
+ org.egov.web.models.AuditDetails
+ org.egov.web.models.enums.Category
+ org.egov.web.models.enums.DemandApportionRequest
+ org.egov.web.models.enums.Purpose
+ org.egov.web.models.enums.ReceiptType
+
+
+ org.egov.web.models.Demand$StatusEnum:DemandStatusEnum
+
+
+ org.egov.common.contract.request.User:User
+ org.egov.common.contract.request.RequestInfo:RequestInfo
+ org.egov.common.contract.response.ResponseInfo:ResponseInfo
+
+ Digit
+ true
+ module
+
+
+
+
+
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/ApportionApp.java b/business-services/egov-apportion-service/src/main/java/org/egov/ApportionApp.java
new file mode 100644
index 000000000..cd931b839
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/ApportionApp.java
@@ -0,0 +1,37 @@
+package org.egov;
+
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.egov.tracer.config.TracerConfiguration;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Import;
+
+import java.util.TimeZone;
+
+@SpringBootApplication
+@ComponentScan(basePackages = { "org.egov", "org.egov.web.controllers" , "org.egov.config"})
+@Import({ TracerConfiguration.class })
+public class ApportionApp {
+
+ @Value("${app.timezone}")
+ private String timeZone;
+
+ @Bean
+ public ObjectMapper objectMapper(){
+ return new ObjectMapper()
+ .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
+ .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
+ .setTimeZone(TimeZone.getTimeZone(timeZone));
+ }
+
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(ApportionApp.class, args);
+ }
+
+}
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/config/ApportionConfig.java b/business-services/egov-apportion-service/src/main/java/org/egov/config/ApportionConfig.java
new file mode 100644
index 000000000..0a0484063
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/config/ApportionConfig.java
@@ -0,0 +1,69 @@
+package org.egov.config;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.TimeZone;
+
+@Data
+@Component
+public class ApportionConfig {
+
+ @Value("${app.timezone}")
+ private String timeZone;
+
+ @PostConstruct
+ public void initialize() {
+ TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
+ }
+
+ /*@Bean
+ public ObjectMapper objectMapper(){
+ return new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).setTimeZone(TimeZone.getTimeZone(timeZone));
+ }*/
+
+ @Bean
+ @Autowired
+ public MappingJackson2HttpMessageConverter jacksonConverter(ObjectMapper objectMapper) {
+ MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
+ converter.setObjectMapper(objectMapper);
+ return converter;
+ }
+
+
+
+ //Persister Config
+ @Value("${persister.save.bill.apportion.request.topic}")
+ private String billRequestTopic;
+
+ @Value("${persister.save.bill.apportion.response.topic}")
+ private String billResponseTopic;
+
+ @Value("${persister.save.demand.apportion.request.topic}")
+ private String demandRequestTopic;
+
+ @Value("${persister.save.demand.apportion.response.topic}")
+ private String demandResponseTopic;
+
+ //MDMS
+ @Value("${egov.mdms.host}")
+ private String mdmsHost;
+
+ @Value("${egov.mdms.search.endpoint}")
+ private String mdmsEndPoint;
+
+ //Default implementation switch
+ @Value("${egov.apportion.default.value.order}")
+ private Boolean apportionByValueAndOrder;
+
+
+
+
+}
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/producer/Producer.java b/business-services/egov-apportion-service/src/main/java/org/egov/producer/Producer.java
new file mode 100644
index 000000000..118934431
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/producer/Producer.java
@@ -0,0 +1,18 @@
+package org.egov.producer;
+
+import lombok.extern.slf4j.Slf4j;
+import org.egov.tracer.kafka.CustomKafkaTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class Producer {
+
+ @Autowired
+ private CustomKafkaTemplate kafkaTemplate;
+
+ public void push(String topic, Object value) {
+ kafkaTemplate.send(topic, value);
+ }
+}
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/repository/ServiceRequestRepository.java b/business-services/egov-apportion-service/src/main/java/org/egov/repository/ServiceRequestRepository.java
new file mode 100644
index 000000000..6bf3c5172
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/repository/ServiceRequestRepository.java
@@ -0,0 +1,46 @@
+package org.egov.repository;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import lombok.extern.slf4j.Slf4j;
+import org.egov.tracer.model.ServiceCallException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+
+@Repository
+@Slf4j
+public class ServiceRequestRepository {
+
+ private ObjectMapper mapper;
+
+ private RestTemplate restTemplate;
+
+
+ @Autowired
+ public ServiceRequestRepository(ObjectMapper mapper, RestTemplate restTemplate) {
+ this.mapper = mapper;
+ this.restTemplate = restTemplate;
+ }
+
+
+ public Object fetchResult(StringBuilder uri, Object request) {
+ mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ Object response = null;
+ log.info("URI: "+uri.toString());
+ try {
+ log.info("Request: "+mapper.writeValueAsString(request));
+ response = restTemplate.postForObject(uri.toString(), request, Map.class);
+ }catch(HttpClientErrorException e) {
+ log.error("External Service threw an Exception: ",e);
+ throw new ServiceCallException(e.getResponseBodyAsString());
+ }catch(Exception e) {
+ log.error("Exception while fetching from searcher: ",e);
+ }
+
+ return response;
+ }
+}
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/service/Apportion.java b/business-services/egov-apportion-service/src/main/java/org/egov/service/Apportion.java
new file mode 100644
index 000000000..bcb2d4a8b
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/service/Apportion.java
@@ -0,0 +1,29 @@
+package org.egov.service;
+
+import org.egov.web.models.Bill;
+import org.egov.web.models.BillDetail;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+public interface Apportion {
+
+
+
+ /**
+ * Should return the code of the BusinessService for which the interface is implemented
+ * @return Code of the BusinessService
+ */
+ String getBusinessService();
+
+
+ /**
+ * Distibutes the paid amount among the Bill account details
+ * @param bill The bill to be apportioned
+ * @return Apportioned BillDetails
+ */
+ List apportionPaidAmount(Bill bill, Object masterData);
+
+
+
+}
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionService.java b/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionService.java
new file mode 100644
index 000000000..c550a7a7f
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionService.java
@@ -0,0 +1,125 @@
+package org.egov.service;
+
+import static org.egov.util.ApportionConstants.DEFAULT;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+import org.egov.config.ApportionConfig;
+import org.egov.producer.Producer;
+import org.egov.web.models.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+
+@Service
+public class ApportionService {
+
+ private final List apportions;
+ private Map APPORTION_MAP = new HashMap<>();
+
+ private Producer producer;
+ private ApportionConfig config;
+ private MDMSService mdmsService;
+
+
+ @Autowired
+ public ApportionService(List apportions, Producer producer,
+ ApportionConfig config, MDMSService mdmsService) {
+ this.apportions = Collections.unmodifiableList(apportions);
+ this.producer = producer;
+ this.config = config;
+ this.mdmsService = mdmsService;
+ initialize();
+ }
+
+ private void initialize() {
+ if (Objects.isNull(apportions))
+ throw new IllegalStateException("No Apportion found, spring initialization failed.");
+
+ if (APPORTION_MAP.isEmpty() && !apportions.isEmpty()) {
+ apportions.forEach(apportion -> {
+ APPORTION_MAP.put(apportion.getBusinessService(), apportion);
+ });
+ }
+ APPORTION_MAP = Collections.unmodifiableMap(APPORTION_MAP);
+ }
+
+
+ /**
+ * Apportions the paid amount for the given list of bills
+ *
+ * @param request The apportion request
+ * @return Apportioned Bills
+ */
+ public List apportionBills(ApportionRequest request) {
+ List bills = request.getBills();
+ Apportion apportion;
+
+ //Save the request through persister
+ producer.push(config.getBillRequestTopic(), request);
+
+ //Fetch the required MDMS data
+ Object masterData = mdmsService.mDMSCall(request.getRequestInfo(), request.getTenantId());
+
+ for (Bill bill : bills) {
+
+ // Create a map of businessService to list of billDetails belonging to that businessService
+ // Map> businessServiceToBillDetails = util.groupByBusinessService(billInfo.getBillDetails());
+
+ bill.getBillDetails().sort(Comparator.comparing(BillDetail::getFromPeriod));
+
+
+ String businessKey = bill.getBusinessService();
+ BigDecimal amountPaid = bill.getAmountPaid();
+
+ List billDetails = bill.getBillDetails();
+
+ if (CollectionUtils.isEmpty(billDetails))
+ continue;
+
+ // Get the appropriate implementation of Apportion
+ if (isApportionPresent(businessKey))
+ apportion = getApportion(businessKey);
+ else
+ apportion = getApportion(DEFAULT);
+
+ /*
+ * Apportion the paid amount among the given list of billDetail
+ */
+ apportion.apportionPaidAmount(bill, masterData);
+ }
+
+
+
+
+ //Save the response through persister
+ producer.push(config.getBillResponseTopic(), request);
+ return bills;
+ }
+
+
+ /**
+ * Retrives the apportion for the given businessService
+ *
+ * @param businessService The businessService of the billDetails
+ * @return Apportion object for the given businessService
+ */
+ private Apportion getApportion(String businessService) {
+ return APPORTION_MAP.get(businessService);
+ }
+
+
+ /**
+ * Checks if the apportion is present for the given businessService
+ *
+ * @param businessService The businessService of the billDetails
+ * @return True if the apportion is present else false
+ */
+ private Boolean isApportionPresent(String businessService) {
+ return APPORTION_MAP.containsKey(businessService);
+ }
+
+
+}
\ No newline at end of file
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionServiceV2.java b/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionServiceV2.java
new file mode 100644
index 000000000..9a133c11b
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionServiceV2.java
@@ -0,0 +1,274 @@
+package org.egov.service;
+
+import static org.egov.util.ApportionConstants.DEFAULT;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+import org.egov.config.ApportionConfig;
+import org.egov.producer.Producer;
+import org.egov.web.models.*;
+import org.egov.web.models.enums.DemandApportionRequest;
+import org.egov.web.models.enums.Purpose;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+
+@Service
+public class ApportionServiceV2 {
+
+ private final List apportions;
+ private Map APPORTION_MAP = new HashMap<>();
+
+ private Producer producer;
+ private ApportionConfig config;
+ private MDMSService mdmsService;
+ private TranslationService translationService;
+
+
+ @Autowired
+ public ApportionServiceV2(List apportions, Producer producer,
+ ApportionConfig config, MDMSService mdmsService,
+ TranslationService translationService) {
+ this.apportions = Collections.unmodifiableList(apportions);
+ this.producer = producer;
+ this.config = config;
+ this.mdmsService = mdmsService;
+ this.translationService = translationService;
+ initialize();
+ }
+
+ private void initialize() {
+ if (Objects.isNull(apportions))
+ throw new IllegalStateException("No Apportion found, spring initialization failed.");
+
+ if (APPORTION_MAP.isEmpty() && !apportions.isEmpty()) {
+ apportions.forEach(apportion -> {
+ APPORTION_MAP.put(apportion.getBusinessService(), apportion);
+ });
+ }
+ APPORTION_MAP = Collections.unmodifiableMap(APPORTION_MAP);
+ }
+
+
+ /**
+ * Apportions the paid amount for the given list of bills
+ *
+ * @param request The apportion request
+ * @return Apportioned Bills
+ */
+ public List apportionBills(ApportionRequest request) {
+ List bills = request.getBills();
+ ApportionV2 apportion;
+
+ //Save the request through persister
+ producer.push(config.getBillRequestTopic(), request);
+
+ //Fetch the required MDMS data
+ Object masterData = mdmsService.mDMSCall(request.getRequestInfo(), request.getTenantId());
+
+ for (Bill bill : bills) {
+
+ // Create a map of businessService to list of billDetails belonging to that businessService
+ // Map> businessServiceToBillDetails = util.groupByBusinessService(billInfo.getBillDetails());
+
+ bill.getBillDetails().sort(Comparator.comparing(BillDetail::getFromPeriod));
+
+
+ String businessKey = bill.getBusinessService();
+ BigDecimal amountPaid = bill.getAmountPaid();
+
+ List billDetails = bill.getBillDetails();
+
+ if (CollectionUtils.isEmpty(billDetails))
+ continue;
+
+ // Get the appropriate implementation of Apportion
+ if (isApportionPresent(businessKey))
+ apportion = getApportion(businessKey);
+ else
+ apportion = getApportion(DEFAULT);
+
+ /*
+ * Apportion the paid amount among the given list of billDetail
+ */
+
+ ApportionRequestV2 apportionRequestV2 = translationService.translate(bill);
+ List taxDetails = apportion.apportionPaidAmount(apportionRequestV2, masterData, true);
+ updateAdjustedAmountInBills(bill,taxDetails);
+ addAdvanceIfExistForBill(billDetails,taxDetails);
+ }
+
+ //Save the response through persister
+ producer.push(config.getBillResponseTopic(), request);
+ return bills;
+ }
+
+
+ /**
+ * Retrives the apportion for the given businessService
+ *
+ * @param businessService The businessService of the billDetails
+ * @return Apportion object for the given businessService
+ */
+ private ApportionV2 getApportion(String businessService) {
+ return APPORTION_MAP.get(businessService);
+ }
+
+
+ /**
+ * Checks if the apportion is present for the given businessService
+ *
+ * @param businessService The businessService of the billDetails
+ * @return True if the apportion is present else false
+ */
+ private Boolean isApportionPresent(String businessService) {
+ return APPORTION_MAP.containsKey(businessService);
+ }
+
+
+
+ /**
+ * Apportions the paid amount for the given list of demands
+ *
+ * @param request The apportion request
+ * @return Apportioned Bills
+ */
+ public List apportionDemands(DemandApportionRequest request) {
+ List demands = request.getDemands();
+ ApportionV2 apportion;
+
+ //Save the request through persister
+ producer.push(config.getDemandRequestTopic(), request);
+
+ //Fetch the required MDMS data
+ Object masterData = mdmsService.mDMSCall(request.getRequestInfo(), request.getTenantId());
+
+ demands.sort(Comparator.comparing(Demand::getTaxPeriodFrom));
+
+ ApportionRequestV2 apportionRequestV2 = translationService.translate(demands,masterData);
+
+
+ /*
+ * Need to validate that all demands that come for apportioning
+ * has same businessService and consumerCode
+ * */
+ String businessKey = demands.get(0).getBusinessService();
+
+ if (isApportionPresent(businessKey))
+ apportion = getApportion(businessKey);
+ else
+ apportion = getApportion(DEFAULT);
+
+ List taxDetails = apportion.apportionPaidAmount(apportionRequestV2, masterData, false);
+ updateAdjustedAmountInDemands(demands,taxDetails);
+ addAdvanceIfExistForDemand(demands,taxDetails);
+
+
+
+ //Save the response through persister
+ producer.push(config.getDemandResponseTopic(), request);
+ return demands;
+ }
+
+
+ /**
+ * Updates adjusted amount in demand from mao returned after apportion
+ * @param demands
+ * @param taxDetails
+ */
+ private void updateAdjustedAmountInDemands(List demands,List taxDetails){
+
+ Map idToAdjustedAmount = new HashMap<>();
+ taxDetails.forEach(taxDetail -> {
+ taxDetail.getBuckets().forEach(bucket -> {
+ idToAdjustedAmount.put(bucket.getEntityId(),bucket.getAdjustedAmount());
+ });
+ });
+
+ demands.forEach(demand -> {
+ demand.getDemandDetails().forEach(demandDetail -> {
+ demandDetail.setCollectionAmount(idToAdjustedAmount.get(demandDetail.getId()));
+ });
+ });
+
+ }
+
+ /**
+ * Updates adjusted amount in bill from mao returned after apportion
+ * @param bill
+ * @param taxDetails
+ */
+ private void updateAdjustedAmountInBills(Bill bill,List taxDetails){
+
+ Map idToBucket = new HashMap<>();
+ Map idToAmountPaid = new HashMap<>();
+
+ taxDetails.forEach(taxDetail -> {
+ idToAmountPaid.put(taxDetail.getEntityId(),taxDetail.getAmountPaid());
+ taxDetail.getBuckets().forEach(bucket -> {
+ idToBucket.put(bucket.getEntityId(),bucket);
+ });
+ });
+
+ bill.getBillDetails().forEach(billDetail -> {
+ billDetail.setAmountPaid(idToAmountPaid.get(billDetail.getId()));
+ billDetail.getBillAccountDetails().forEach(billAccountDetail -> {
+ billAccountDetail.setAdjustedAmount(idToBucket.get(billAccountDetail.getId()).getAdjustedAmount());
+ if(billAccountDetail.getTaxHeadCode().contains("ADVANCE")){
+ billAccountDetail.setAmount(idToBucket.get(billAccountDetail.getId()).getAmount());
+ }
+
+ });
+ });
+ }
+
+
+ private void addAdvanceIfExistForDemand(List demands,List taxDetails){
+
+ Bucket advanceBucket = null;
+ TaxDetail taxDetail = taxDetails.get(taxDetails.size()-1);
+
+ for(Bucket bucket : taxDetail.getBuckets()){
+ if(bucket.getEntityId()==null && bucket.getPurpose().equals(Purpose.ADVANCE_AMOUNT)){
+ advanceBucket = bucket;
+ break;
+ }
+ }
+
+ if(advanceBucket != null){
+ DemandDetail demandDetailForAdvance = new DemandDetail();
+ demandDetailForAdvance.setTaxAmount(advanceBucket.getAmount());
+ demandDetailForAdvance.setTaxHeadMasterCode(advanceBucket.getTaxHeadCode());
+ demands.get(demands.size()-1).getDemandDetails().add(demandDetailForAdvance);
+ }
+
+ }
+
+
+ private void addAdvanceIfExistForBill(List billDetails,List taxDetails){
+
+ Bucket advanceBucket = null;
+ TaxDetail taxDetail = taxDetails.get(taxDetails.size()-1);
+
+ for(Bucket bucket : taxDetail.getBuckets()){
+ if(bucket.getEntityId()==null && bucket.getPurpose().equals(Purpose.ADVANCE_AMOUNT)){
+ advanceBucket = bucket;
+ break;
+ }
+ }
+
+ if(advanceBucket != null){
+ BillAccountDetail billAccountDetailForAdvance = new BillAccountDetail();
+ billAccountDetailForAdvance.setAmount(advanceBucket.getAmount());
+ billAccountDetailForAdvance.setPurpose(Purpose.ADVANCE_AMOUNT);
+ billAccountDetailForAdvance.setTaxHeadCode(advanceBucket.getTaxHeadCode());
+ billDetails.get(billDetails.size()-1).getBillAccountDetails().add(billAccountDetailForAdvance);
+ }
+
+ }
+
+
+
+}
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionV2.java b/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionV2.java
new file mode 100644
index 000000000..6d5e37b0a
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/service/ApportionV2.java
@@ -0,0 +1,22 @@
+package org.egov.service;
+
+import org.egov.web.models.ApportionRequestV2;
+import org.egov.web.models.Bill;
+import org.egov.web.models.BillDetail;
+import org.egov.web.models.TaxDetail;
+
+import java.util.List;
+
+public interface ApportionV2 {
+
+ /**
+ * Should return the code of the BusinessService for which the interface is implemented
+ * @return Code of the BusinessService
+ */
+ String getBusinessService();
+
+
+
+ List apportionPaidAmount(ApportionRequestV2 apportionRequestV2, Object masterData, Boolean isBillApportion);
+
+}
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/service/MDMSService.java b/business-services/egov-apportion-service/src/main/java/org/egov/service/MDMSService.java
new file mode 100644
index 000000000..f23081164
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/service/MDMSService.java
@@ -0,0 +1,139 @@
+package org.egov.service;
+
+import org.egov.common.contract.request.RequestInfo;
+import org.egov.config.ApportionConfig;
+import org.egov.mdms.model.MasterDetail;
+import org.egov.mdms.model.MdmsCriteria;
+import org.egov.mdms.model.MdmsCriteriaReq;
+import org.egov.mdms.model.ModuleDetail;
+import org.egov.repository.ServiceRequestRepository;
+import org.egov.web.models.ApportionRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.egov.util.ApportionConstants.*;
+
+@Service
+public class MDMSService {
+
+
+ private ServiceRequestRepository serviceRequestRepository;
+
+ private ApportionConfig config;
+
+ @Autowired
+ public MDMSService(ServiceRequestRepository serviceRequestRepository,ApportionConfig config) {
+ this.serviceRequestRepository = serviceRequestRepository;
+ this.config = config;
+ }
+
+ /**
+ * Fetches MDMS data based on the apportion request
+ * @param requestInfo The apportion request for which master data is required
+ * @return MasterData from MDMS
+ */
+ public Object mDMSCall(RequestInfo requestInfo,String tenantId){
+ MdmsCriteriaReq mdmsCriteriaReq = getMDMSRequest(requestInfo,tenantId);
+ Object result = serviceRequestRepository.fetchResult(getMdmsSearchUrl(), mdmsCriteriaReq);
+ return result;
+ }
+
+ /**
+ * Creates MDMS search criteria object MdmsCriteriaReq
+ * @param requestInfo The requestInfo of the apportion request
+ * @param tenantId TenantId of the request
+ * @return MDMS search criteria
+ */
+ private MdmsCriteriaReq getMDMSRequest(RequestInfo requestInfo, String tenantId){
+ ModuleDetail moduleDetail = getBillingModuleDetail();
+
+ List moduleDetails = new LinkedList<>();
+ moduleDetails.add(moduleDetail);
+
+ MdmsCriteria mdmsCriteria = MdmsCriteria.builder().moduleDetails(moduleDetails).tenantId(tenantId)
+ .build();
+
+ MdmsCriteriaReq mdmsCriteriaReq = MdmsCriteriaReq.builder().mdmsCriteria(mdmsCriteria)
+ .requestInfo(requestInfo).build();
+ return mdmsCriteriaReq;
+ }
+
+
+
+ /**
+ * Returns the url for mdms search endpoint
+ *
+ * @return url for mdms search endpoint
+ */
+ public StringBuilder getMdmsSearchUrl() {
+ return new StringBuilder().append(config.getMdmsHost()).append(config.getMdmsEndPoint());
+ }
+
+
+ /**
+ * Creates request to search taxhead in mdms
+ * @return MDMS request for taxhead
+ */
+ private ModuleDetail getTaxHeadMasterRequest() {
+ // filter to only get code field from master data
+
+ // final String filterCodeForUom = "$.[?(@.active==true)]";
+
+ ModuleDetail tlModuleDtls = ModuleDetail.builder()
+ .masterDetails(Collections.singletonList(MasterDetail.builder().name(MDMS_TAXHEAD).build()))
+ .moduleName(MDMS_BILLING_SERVICE).build();
+
+ return tlModuleDtls;
+ }
+
+
+ /**
+ * Creates request to search businessService in mdms
+ * @return MDMS request for businessService
+ */
+ private ModuleDetail getBusinessServiceRequest() {
+ // filter to only get code field from master data
+
+ // final String filterCodeForUom = "$.[?(@.active==true)]";
+
+ ModuleDetail tlModuleDtls = ModuleDetail.builder()
+ .masterDetails(Collections.singletonList(MasterDetail.builder().name(MDMS_BUSINESSSERVICE).build()))
+ .moduleName(MDMS_BILLING_SERVICE).build();
+
+ return tlModuleDtls;
+ }
+
+
+ /**
+ * Creates request to search businessService in mdms
+ * @return MDMS request for businessService
+ */
+ private ModuleDetail getBillingModuleDetail() {
+
+ List masterDetails = new ArrayList<>();
+
+ MasterDetail businessServiceMasterDetail = MasterDetail.builder().name(MDMS_BUSINESSSERVICE).build();
+ MasterDetail taxHeadMasterDetail = MasterDetail.builder().name(MDMS_TAXHEAD).build();
+
+ masterDetails.add(businessServiceMasterDetail);
+ masterDetails.add(taxHeadMasterDetail);
+
+ ModuleDetail tlModuleDtls = ModuleDetail.builder()
+ .masterDetails(masterDetails)
+ .moduleName(MDMS_BILLING_SERVICE).build();
+
+ return tlModuleDtls;
+ }
+
+
+
+
+
+
+
+}
diff --git a/business-services/egov-apportion-service/src/main/java/org/egov/service/TaxHeadMasterService.java b/business-services/egov-apportion-service/src/main/java/org/egov/service/TaxHeadMasterService.java
new file mode 100644
index 000000000..0af052bd3
--- /dev/null
+++ b/business-services/egov-apportion-service/src/main/java/org/egov/service/TaxHeadMasterService.java
@@ -0,0 +1,87 @@
+package org.egov.service;
+
+import com.jayway.jsonpath.JsonPath;
+import org.egov.tracer.model.CustomException;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.egov.util.ApportionConstants.*;
+
+@Service
+public class TaxHeadMasterService {
+
+
+ /**
+ * Fetches the advance amount taxHead for the given businessService from MDMSData
+ * @param businessService The businessService for which taxhead is to be fetched
+ * @param mdmsData The master data received from MDMS Service
+ * @return The code of the TaxHead
+ */
+ public String getAdvanceTaxHead(String businessService,Object mdmsData){
+
+ String jsonpath = ADVANCE_TAXHEAD_JSONPATH_CODE;
+ jsonpath = jsonpath.replace("{}",businessService);
+
+
+ List taxHeads = JsonPath.read(mdmsData,jsonpath);
+
+ if(CollectionUtils.isEmpty(taxHeads))
+ throw new CustomException("NO TAXHEAD FOUND","No Advance taxHead found for businessService: "+businessService);
+
+ return taxHeads.get(0);
+ }
+
+
+ /**
+ * Creates a map of taxHeadCode to priority for taxHeads of given businessService
+ * @param businessService
+ * @param mdmsData
+ * @return
+ */
+ public Map getCodeToOrderMap(String businessService,Object mdmsData){
+
+ String jsonpath = TAXHEAD_JSONPATH_CODE;
+ jsonpath = jsonpath.replace("{}",businessService);
+
+ List