FINERACT-1744 - Idempotency support
- [x] Idempotency write move to filters - [x] Idempotency integration test with success and failure - [x] Added response status code to database - [x] SynchronousCommandProcessingService not generate the command, just marked to the filter - [x] IdempotencyStoreFilter store the command request if available. - [x] Removed JpaExceptionHandler.java
This commit is contained in:
committed by
Arnold Gálovics
parent
25c0b6a08d
commit
5210bf7412
@@ -1447,7 +1447,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
||||||
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
||||||
@@ -1455,7 +1455,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
@@ -1762,7 +1762,7 @@ CREATE TABLE `r_enum_value` (
|
|||||||
|
|
||||||
LOCK TABLES `r_enum_value` WRITE;
|
LOCK TABLES `r_enum_value` WRITE;
|
||||||
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
||||||
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('status',0,'invalid','Invalid'),('status',1,'processed','Processed'),('status',2,'awaiting.approval','Awaiting Approval'),('status',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
||||||
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
|||||||
@@ -1448,7 +1448,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
||||||
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
||||||
@@ -1456,7 +1456,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
@@ -1763,7 +1763,7 @@ CREATE TABLE `r_enum_value` (
|
|||||||
|
|
||||||
LOCK TABLES `r_enum_value` WRITE;
|
LOCK TABLES `r_enum_value` WRITE;
|
||||||
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
||||||
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('status',0,'invalid','Invalid'),('status',1,'processed','Processed'),('status',2,'awaiting.approval','Awaiting Approval'),('status',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
||||||
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
|||||||
@@ -1373,7 +1373,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
||||||
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
||||||
@@ -1381,7 +1381,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
@@ -1701,7 +1701,7 @@ CREATE TABLE `r_enum_value` (
|
|||||||
|
|
||||||
LOCK TABLES `r_enum_value` WRITE;
|
LOCK TABLES `r_enum_value` WRITE;
|
||||||
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
||||||
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('status',0,'invalid','Invalid'),('status',1,'processed','Processed'),('status',2,'awaiting.approval','Awaiting Approval'),('status',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
||||||
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
|||||||
@@ -1373,7 +1373,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
||||||
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
||||||
@@ -1381,7 +1381,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
@@ -1701,7 +1701,7 @@ CREATE TABLE `r_enum_value` (
|
|||||||
|
|
||||||
LOCK TABLES `r_enum_value` WRITE;
|
LOCK TABLES `r_enum_value` WRITE;
|
||||||
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
||||||
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('status',0,'invalid','Invalid'),('status',1,'processed','Processed'),('status',2,'awaiting.approval','Awaiting Approval'),('status',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
||||||
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
|||||||
@@ -1448,7 +1448,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
||||||
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
||||||
@@ -1456,7 +1456,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
@@ -1763,7 +1763,7 @@ CREATE TABLE `r_enum_value` (
|
|||||||
|
|
||||||
LOCK TABLES `r_enum_value` WRITE;
|
LOCK TABLES `r_enum_value` WRITE;
|
||||||
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
||||||
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',601,'Written-Off','Written-Off'),('loan_status_id',602,'Rescheduled','Rescheduled'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('status',0,'invalid','Invalid'),('status',1,'processed','Processed'),('status',2,'awaiting.approval','Awaiting Approval'),('status',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years'),('transaction_type_enum',1,'Disbursement','Disbursement'),('transaction_type_enum',2,'Repayment','Repayment'),('transaction_type_enum',3,'Contra','Contra'),('transaction_type_enum',4,'Waive Interest','Waive Interest'),('transaction_type_enum',5,'Repayment At Disbursement','Repayment At Disbursement'),('transaction_type_enum',6,'Write-Off','Write-Off'),('transaction_type_enum',7,'Marked for Rescheduling','Marked for Rescheduling'),('transaction_type_enum',8,'Recovery Repayment','Recovery Repayment'),('transaction_type_enum',9,'Waive Charges','Waive Charges'),('transaction_type_enum',10,'Apply Charges','Apply Charges'),('transaction_type_enum',11,'Apply Interest','Apply Interest');
|
||||||
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
|||||||
@@ -1232,7 +1232,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
||||||
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
||||||
@@ -1240,7 +1240,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
@@ -1679,7 +1679,7 @@ CREATE TABLE `r_enum_value` (
|
|||||||
|
|
||||||
LOCK TABLES `r_enum_value` WRITE;
|
LOCK TABLES `r_enum_value` WRITE;
|
||||||
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` DISABLE KEYS */;
|
||||||
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('processing_result_enum',0,'invalid','Invalid'),('processing_result_enum',1,'processed','Processed'),('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),('processing_result_enum',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years');
|
INSERT INTO `r_enum_value` VALUES ('amortization_method_enum',0,'Equal principle payments','Equal principle payments'),('amortization_method_enum',1,'Equal installments','Equal installments'),('interest_calculated_in_period_enum',0,'Daily','Daily'),('interest_calculated_in_period_enum',1,'Same as repayment period','Same as repayment period'),('interest_method_enum',0,'Declining Balance','Declining Balance'),('interest_method_enum',1,'Flat','Flat'),('interest_period_frequency_enum',2,'Per month','Per month'),('interest_period_frequency_enum',3,'Per year','Per year'),('loan_status_id',100,'Submitted and awaiting approval','Submitted and awaiting approval'),('loan_status_id',200,'Approved','Approved'),('loan_status_id',300,'Active','Active'),('loan_status_id',400,'Withdrawn by client','Withdrawn by client'),('loan_status_id',500,'Rejected','Rejected'),('loan_status_id',600,'Closed','Closed'),('loan_status_id',700,'Overpaid','Overpaid'),('loan_transaction_strategy_id',1,'mifos-standard-strategy','Mifos style'),('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),('status',0,'invalid','Invalid'),('status',1,'processed','Processed'),('status',2,'awaiting.approval','Awaiting Approval'),('status',3,'rejected','Rejected'),('repayment_period_frequency_enum',0,'Days','Days'),('repayment_period_frequency_enum',1,'Weeks','Weeks'),('repayment_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',0,'Days','Days'),('term_period_frequency_enum',1,'Weeks','Weeks'),('term_period_frequency_enum',2,'Months','Months'),('term_period_frequency_enum',3,'Years','Years');
|
||||||
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `r_enum_value` ENABLE KEYS */;
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
KEY `FK_m_maker_m_appuser` (`maker_id`),
|
||||||
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
KEY `FK_m_checker_m_appuser` (`checker_id`),
|
||||||
@@ -312,7 +312,7 @@ CREATE TABLE `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
|
|||||||
@@ -47,10 +47,12 @@ VALUES
|
|||||||
('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),
|
('loan_transaction_strategy_id',2,'heavensfamily-strategy','Heavensfamily'),
|
||||||
('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),
|
('loan_transaction_strategy_id',3,'creocore-strategy','Creocore'),
|
||||||
('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),
|
('loan_transaction_strategy_id',4,'rbi-india-strategy','RBI (India)'),
|
||||||
('processing_result_enum',0,'invalid','Invalid'),
|
('status',0,'invalid','Invalid'),
|
||||||
('processing_result_enum',1,'processed','Processed'),
|
('status',1,'processed','Processed'),
|
||||||
('processing_result_enum',2,'awaiting.approval','Awaiting Approval'),
|
('status',2,'awaiting.approval','Awaiting Approval'),
|
||||||
('processing_result_enum',3,'rejected','Rejected'),
|
('status',3,'rejected','Rejected'),
|
||||||
|
('status',4,'underProcessing','Under Processing'),
|
||||||
|
('status',5,'error','Error'),
|
||||||
('repayment_period_frequency_enum',0,'Days','Days'),
|
('repayment_period_frequency_enum',0,'Days','Days'),
|
||||||
('repayment_period_frequency_enum',1,'Weeks','Weeks'),
|
('repayment_period_frequency_enum',1,'Weeks','Weeks'),
|
||||||
('repayment_period_frequency_enum',2,'Months','Months'),
|
('repayment_period_frequency_enum',2,'Months','Months'),
|
||||||
|
|||||||
+4
-8
@@ -37,22 +37,18 @@ import org.springframework.batch.core.StepContribution;
|
|||||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||||
import org.springframework.batch.repeat.RepeatStatus;
|
import org.springframework.batch.repeat.RepeatStatus;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class InlineLoanCOBBuildExecutionContextTasklet implements Tasklet, InitializingBean {
|
@Component
|
||||||
|
public class InlineLoanCOBBuildExecutionContextTasklet implements Tasklet {
|
||||||
|
|
||||||
private final GoogleGsonSerializerHelper gsonFactory;
|
private final GoogleGsonSerializerHelper gsonFactory;
|
||||||
private final COBBusinessStepService cobBusinessStepService;
|
private final COBBusinessStepService cobBusinessStepService;
|
||||||
private final CustomJobParameterRepository customJobParameterRepository;
|
private final CustomJobParameterRepository customJobParameterRepository;
|
||||||
|
|
||||||
private Gson gson;
|
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterPropertiesSet() throws Exception {
|
|
||||||
this.gson = gsonFactory.createSimpleGson();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||||
|
|||||||
+2
-8
@@ -55,7 +55,6 @@ import org.springframework.batch.core.configuration.JobLocator;
|
|||||||
import org.springframework.batch.core.explore.JobExplorer;
|
import org.springframework.batch.core.explore.JobExplorer;
|
||||||
import org.springframework.batch.core.launch.JobLauncher;
|
import org.springframework.batch.core.launch.JobLauncher;
|
||||||
import org.springframework.batch.core.launch.NoSuchJobException;
|
import org.springframework.batch.core.launch.NoSuchJobException;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.TransactionStatus;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
@@ -66,7 +65,7 @@ import org.springframework.transaction.support.TransactionTemplate;
|
|||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<Long>, InitializingBean {
|
public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<Long> {
|
||||||
|
|
||||||
private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: ";
|
private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: ";
|
||||||
|
|
||||||
@@ -80,12 +79,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<L
|
|||||||
private final CustomJobParameterRepository customJobParameterRepository;
|
private final CustomJobParameterRepository customJobParameterRepository;
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
|
|
||||||
private Gson gson;
|
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterPropertiesSet() throws Exception {
|
|
||||||
this.gson = gsonFactory.createSimpleGson();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
|
|||||||
+8
-8
@@ -68,7 +68,7 @@ public class AuditsApiResource {
|
|||||||
"subresourceId", "maker", "madeOnDate", "checker", "checkedOnDate", "processingResult", "commandAsJson", "officeName",
|
"subresourceId", "maker", "madeOnDate", "checker", "checkedOnDate", "processingResult", "commandAsJson", "officeName",
|
||||||
"groupLevelName", "groupName", "clientName", "loanAccountNo", "savingsAccountNo", "clientId", "loanId", "url"));
|
"groupLevelName", "groupName", "clientName", "loanAccountNo", "savingsAccountNo", "clientId", "loanId", "url"));
|
||||||
|
|
||||||
private final String resourceNameForPermissions = "AUDIT";
|
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "AUDIT";
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final AuditReadPlatformService auditReadPlatformService;
|
private final AuditReadPlatformService auditReadPlatformService;
|
||||||
@@ -107,7 +107,7 @@ public class AuditsApiResource {
|
|||||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder);
|
final PaginationParameters parameters = PaginationParameters.instance(paged, offset, limit, orderBy, sortOrder);
|
||||||
final SQLBuilder extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo,
|
final SQLBuilder extraCriteria = getExtraCriteria(actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo,
|
||||||
checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult, officeId, groupId, clientId, loanId, savingsAccountId);
|
checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult, officeId, groupId, clientId, loanId, savingsAccountId);
|
||||||
@@ -137,7 +137,7 @@ public class AuditsApiResource {
|
|||||||
public String retrieveAuditEntry(@PathParam("auditId") @Parameter(description = "auditId") final Long auditId,
|
public String retrieveAuditEntry(@PathParam("auditId") @Parameter(description = "auditId") final Long auditId,
|
||||||
@Context final UriInfo uriInfo) {
|
@Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final AuditData auditEntry = this.auditReadPlatformService.retrieveAuditEntry(auditId);
|
final AuditData auditEntry = this.auditReadPlatformService.retrieveAuditEntry(auditId);
|
||||||
|
|
||||||
@@ -155,22 +155,22 @@ public class AuditsApiResource {
|
|||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = MakercheckersApiResourceSwagger.GetMakerCheckersSearchTemplateResponse.class))) })
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = MakercheckersApiResourceSwagger.GetMakerCheckersSearchTemplateResponse.class))) })
|
||||||
public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) {
|
public String retrieveAuditSearchTemplate(@Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
this.context.authenticatedUser().validateHasReadPermission(this.RESOURCE_NAME_FOR_PERMISSIONS);
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
|
|
||||||
final AuditSearchData auditSearchData = this.auditReadPlatformService.retrieveSearchTemplate("audit");
|
final AuditSearchData auditSearchData = this.auditReadPlatformService.retrieveSearchTemplate("audit");
|
||||||
|
|
||||||
final Set<String> RESPONSE_DATA_PARAMETERS_SEARCH_TEMPLATE = new HashSet<>(
|
final Set<String> RESPONSE_DATA_PARAMETERS_SEARCH_TEMPLATE = new HashSet<>(
|
||||||
Arrays.asList("appUsers", "actionNames", "entityNames", "processingResults"));
|
Arrays.asList("appUsers", "actionNames", "entityNames", "status"));
|
||||||
|
|
||||||
return this.toApiJsonSerializerSearchTemplate.serialize(settings, auditSearchData, RESPONSE_DATA_PARAMETERS_SEARCH_TEMPLATE);
|
return this.toApiJsonSerializerSearchTemplate.serialize(settings, auditSearchData, RESPONSE_DATA_PARAMETERS_SEARCH_TEMPLATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SQLBuilder getExtraCriteria(final String actionName, final String entityName, final Long resourceId, final Long makerId,
|
private SQLBuilder getExtraCriteria(final String actionName, final String entityName, final Long resourceId, final Long makerId,
|
||||||
final String makerDateTimeFrom, final String makerDateTimeTo, final Long checkerId, final String checkerDateTimeFrom,
|
final String makerDateTimeFrom, final String makerDateTimeTo, final Long checkerId, final String checkerDateTimeFrom,
|
||||||
final String checkerDateTimeTo, final Integer processingResult, final Integer officeId, final Integer groupId,
|
final String checkerDateTimeTo, final Integer status, final Integer officeId, final Integer groupId, final Integer clientId,
|
||||||
final Integer clientId, final Integer loanId, final Integer savingsAccountId) {
|
final Integer loanId, final Integer savingsAccountId) {
|
||||||
|
|
||||||
SQLBuilder extraCriteria = new SQLBuilder();
|
SQLBuilder extraCriteria = new SQLBuilder();
|
||||||
extraCriteria.addNonNullCriteria("aud.action_name = ", actionName);
|
extraCriteria.addNonNullCriteria("aud.action_name = ", actionName);
|
||||||
@@ -184,7 +184,7 @@ public class AuditsApiResource {
|
|||||||
extraCriteria.addNonNullCriteria("aud.made_on_date <= ", makerDateTimeTo);
|
extraCriteria.addNonNullCriteria("aud.made_on_date <= ", makerDateTimeTo);
|
||||||
extraCriteria.addNonNullCriteria("aud.checked_on_date >= ", checkerDateTimeFrom);
|
extraCriteria.addNonNullCriteria("aud.checked_on_date >= ", checkerDateTimeFrom);
|
||||||
extraCriteria.addNonNullCriteria("aud.checked_on_date <= ", checkerDateTimeTo);
|
extraCriteria.addNonNullCriteria("aud.checked_on_date <= ", checkerDateTimeTo);
|
||||||
extraCriteria.addNonNullCriteria("aud.processing_result_enum = ", processingResult);
|
extraCriteria.addNonNullCriteria("aud.status = ", status);
|
||||||
extraCriteria.addNonNullCriteria("aud.office_id = ", officeId);
|
extraCriteria.addNonNullCriteria("aud.office_id = ", officeId);
|
||||||
extraCriteria.addNonNullCriteria("aud.group_id = ", groupId);
|
extraCriteria.addNonNullCriteria("aud.group_id = ", groupId);
|
||||||
extraCriteria.addNonNullCriteria("aud.client_id = ", clientId);
|
extraCriteria.addNonNullCriteria("aud.client_id = ", clientId);
|
||||||
|
|||||||
+1
-1
@@ -34,5 +34,5 @@ public final class AuditSearchData {
|
|||||||
private final Collection<AppUserData> appUsers;
|
private final Collection<AppUserData> appUsers;
|
||||||
private final List<String> actionNames;
|
private final List<String> actionNames;
|
||||||
private final List<String> entityNames;
|
private final List<String> entityNames;
|
||||||
private final Collection<ProcessingResultLookup> processingResults;
|
private final Collection<ProcessingResultLookup> statuses;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -28,7 +28,9 @@ public enum CommandProcessingResultType {
|
|||||||
INVALID(0, "commandProcessingResultType.invalid"), //
|
INVALID(0, "commandProcessingResultType.invalid"), //
|
||||||
PROCESSED(1, "commandProcessingResultType.processed"), //
|
PROCESSED(1, "commandProcessingResultType.processed"), //
|
||||||
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
|
AWAITING_APPROVAL(2, "commandProcessingResultType.awaiting.approval"), //
|
||||||
REJECTED(3, "commandProcessingResultType.rejected");
|
REJECTED(3, "commandProcessingResultType.rejected"), //
|
||||||
|
UNDER_PROCESSING(4, "commandProcessingResultType.underProcessing"), //
|
||||||
|
ERROR(5, "commandProcessingResultType.error");
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
|
|||||||
+46
-24
@@ -24,7 +24,6 @@ import javax.persistence.Entity;
|
|||||||
import javax.persistence.JoinColumn;
|
import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
@@ -91,8 +90,8 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
@JoinColumn(name = "checker_id", nullable = true)
|
@JoinColumn(name = "checker_id", nullable = true)
|
||||||
private AppUser checker;
|
private AppUser checker;
|
||||||
|
|
||||||
@Column(name = "processing_result_enum", nullable = false)
|
@Column(name = "status", nullable = false)
|
||||||
private Integer processingResult;
|
private Integer status;
|
||||||
|
|
||||||
@Column(name = "product_id")
|
@Column(name = "product_id")
|
||||||
private Long productId;
|
private Long productId;
|
||||||
@@ -118,10 +117,16 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
@Column(name = "subresource_external_id")
|
@Column(name = "subresource_external_id")
|
||||||
private ExternalId subResourceExternalId;
|
private ExternalId subResourceExternalId;
|
||||||
|
|
||||||
|
@Column(name = "result")
|
||||||
|
private String result;
|
||||||
|
|
||||||
|
@Column(name = "result_status_code")
|
||||||
|
private Integer resultStatusCode;
|
||||||
|
|
||||||
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker,
|
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker,
|
||||||
String idempotencyKey) {
|
String idempotencyKey, Integer status) {
|
||||||
return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(),
|
return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(),
|
||||||
command.json(), maker, idempotencyKey);
|
command.json(), maker, idempotencyKey, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CommandSource() {
|
protected CommandSource() {
|
||||||
@@ -129,7 +134,8 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId,
|
private CommandSource(final String actionName, final String entityName, final String href, final Long resourceId,
|
||||||
final Long subResourceId, final String commandSerializedAsJson, final AppUser maker, final String idempotencyKey) {
|
final Long subResourceId, final String commandSerializedAsJson, final AppUser maker, final String idempotencyKey,
|
||||||
|
final Integer status) {
|
||||||
this.actionName = actionName;
|
this.actionName = actionName;
|
||||||
this.entityName = entityName;
|
this.entityName = entityName;
|
||||||
this.resourceGetUrl = href;
|
this.resourceGetUrl = href;
|
||||||
@@ -138,7 +144,7 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
this.commandAsJson = commandSerializedAsJson;
|
this.commandAsJson = commandSerializedAsJson;
|
||||||
this.maker = maker;
|
this.maker = maker;
|
||||||
this.madeOnDate = DateUtils.getOffsetDateTimeOfTenant();
|
this.madeOnDate = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
|
this.status = status;
|
||||||
this.idempotencyKey = idempotencyKey;
|
this.idempotencyKey = idempotencyKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,13 +171,13 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
public void markAsChecked(final AppUser checker) {
|
public void markAsChecked(final AppUser checker) {
|
||||||
this.checker = checker;
|
this.checker = checker;
|
||||||
this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenant();
|
this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
this.processingResult = CommandProcessingResultType.PROCESSED.getValue();
|
this.status = CommandProcessingResultType.PROCESSED.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markAsRejected(final AppUser checker) {
|
public void markAsRejected(final AppUser checker) {
|
||||||
this.checker = checker;
|
this.checker = checker;
|
||||||
this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenant();
|
this.checkedOnDate = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
this.processingResult = CommandProcessingResultType.REJECTED.getValue();
|
this.status = CommandProcessingResultType.REJECTED.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateResourceId(final Long resourceId) {
|
public void updateResourceId(final Long resourceId) {
|
||||||
@@ -182,7 +188,11 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
this.subResourceId = subResourceId;
|
this.subResourceId = subResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateJsonTo(final String json) {
|
public String getCommandJson() {
|
||||||
|
return this.commandAsJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommandJson(final String json) {
|
||||||
this.commandAsJson = json;
|
this.commandAsJson = json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,14 +204,6 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
return this.subResourceId;
|
return this.subResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasJson() {
|
|
||||||
return StringUtils.isNotBlank(this.commandAsJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String json() {
|
|
||||||
return this.commandAsJson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getActionName() {
|
public String getActionName() {
|
||||||
return this.actionName;
|
return this.actionName;
|
||||||
}
|
}
|
||||||
@@ -223,15 +225,11 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void markAsAwaitingApproval() {
|
public void markAsAwaitingApproval() {
|
||||||
this.processingResult = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
|
this.status = CommandProcessingResultType.AWAITING_APPROVAL.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMarkedAsAwaitingApproval() {
|
public boolean isMarkedAsAwaitingApproval() {
|
||||||
if (this.processingResult.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue())) {
|
return this.status.equals(CommandProcessingResultType.AWAITING_APPROVAL.getValue());
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateForAudit(final CommandProcessingResult result) {
|
public void updateForAudit(final CommandProcessingResult result) {
|
||||||
@@ -309,4 +307,28 @@ public class CommandSource extends AbstractPersistableCustom {
|
|||||||
public void setIdempotencyKey(String idempotencyKey) {
|
public void setIdempotencyKey(String idempotencyKey) {
|
||||||
this.idempotencyKey = idempotencyKey;
|
this.idempotencyKey = idempotencyKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResult(String result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Integer status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getResultStatusCode() {
|
||||||
|
return resultStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResultStatusCode(Integer resultStatusCode) {
|
||||||
|
this.resultStatusCode = resultStatusCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -22,5 +22,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
|
||||||
public interface CommandSourceRepository extends JpaRepository<CommandSource, Long>, JpaSpecificationExecutor<CommandSource> {
|
public interface CommandSourceRepository extends JpaRepository<CommandSource, Long>, JpaSpecificationExecutor<CommandSource> {
|
||||||
// no added behaviour
|
|
||||||
|
CommandSource findByActionNameAndEntityNameAndIdempotencyKey(String actionName, String entityName, String idempotencyKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -20,8 +20,12 @@ package org.apache.fineract.commands.handler;
|
|||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
public interface NewCommandSourceHandler {
|
public interface NewCommandSourceHandler {
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||||
CommandProcessingResult processCommand(JsonCommand command);
|
CommandProcessingResult processCommand(JsonCommand command);
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -115,7 +115,7 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
|
|||||||
+ " left join m_office o on o.id = aud.office_id" + " left join m_group g on g.id = aud.group_id"
|
+ " left join m_office o on o.id = aud.office_id" + " left join m_group g on g.id = aud.group_id"
|
||||||
+ " left join m_group_level gl on gl.id = g.level_id" + " left join m_client c on c.id = aud.client_id"
|
+ " left join m_group_level gl on gl.id = g.level_id" + " left join m_client c on c.id = aud.client_id"
|
||||||
+ " left join m_loan l on l.id = aud.loan_id" + " left join m_savings_account s on s.id = aud.savings_account_id"
|
+ " left join m_loan l on l.id = aud.loan_id" + " left join m_savings_account s on s.id = aud.savings_account_id"
|
||||||
+ " left join r_enum_value ev on ev.enum_name = 'processing_result_enum' and ev.enum_id = aud.processing_result_enum";
|
+ " left join r_enum_value ev on ev.enum_name = 'status' and ev.enum_id = aud.status";
|
||||||
|
|
||||||
// data scoping: head office (hierarchy = ".") can see all audit
|
// data scoping: head office (hierarchy = ".") can see all audit
|
||||||
// entries
|
// entries
|
||||||
@@ -206,7 +206,7 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<AuditData> retrieveAllEntriesToBeChecked(final SQLBuilder extraCriteria, final boolean includeJson) {
|
public Collection<AuditData> retrieveAllEntriesToBeChecked(final SQLBuilder extraCriteria, final boolean includeJson) {
|
||||||
extraCriteria.addCriteria("aud.processing_result_enum = ", 2);
|
extraCriteria.addCriteria("aud.status = ", 2);
|
||||||
return retrieveEntries("makerchecker", extraCriteria, " order by aud.id, mk.username", includeJson);
|
return retrieveEntries("makerchecker", extraCriteria, " order by aud.id, mk.username", includeJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,13 +491,13 @@ public class AuditReadPlatformServiceImpl implements AuditReadPlatformService {
|
|||||||
@Override
|
@Override
|
||||||
public ProcessingResultLookup mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
public ProcessingResultLookup mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||||
final Long id = JdbcSupport.getLong(rs, "id");
|
final Long id = JdbcSupport.getLong(rs, "id");
|
||||||
final String processingResult = rs.getString("processingResult");
|
final String status = rs.getString("status");
|
||||||
|
|
||||||
return new ProcessingResultLookup(id, processingResult);
|
return new ProcessingResultLookup(id, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String schema() {
|
public String schema() {
|
||||||
return " select enum_id as id, enum_message_property as processingResult from r_enum_value where enum_name = 'processing_result_enum' "
|
return " select enum_id as id, enum_message_property as status from r_enum_value where enum_name = 'status' "
|
||||||
+ " order by enum_id";
|
+ " order by enum_id";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* 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.commands.service;
|
||||||
|
|
||||||
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
|
||||||
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.batch.exception.ErrorHandler;
|
||||||
|
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSource;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
import org.apache.fineract.commands.exception.CommandNotFoundException;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Two phase transactional command processing: save initial...work...finish/failed to handle idempotent requests. As the
|
||||||
|
* default isolation level for MYSQL is REPEATABLE_READ and a lower value READ_COMMITED for postgres, we can force to
|
||||||
|
* use the same for both database backends to be consistent.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CommandSourceService {
|
||||||
|
|
||||||
|
private final CommandSourceRepository commandSourceRepository;
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||||
|
public CommandSource saveInitial(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||||
|
CommandSource initialCommandSource = getInitialCommandSource(wrapper, jsonCommand, maker, idempotencyKey);
|
||||||
|
|
||||||
|
if (initialCommandSource.getCommandJson() == null) {
|
||||||
|
initialCommandSource.setCommandJson("{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandSourceRepository.saveAndFlush(initialCommandSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||||
|
public void saveFailed(CommandSource commandSource) {
|
||||||
|
commandSource.setStatus(ERROR.getValue());
|
||||||
|
commandSourceRepository.saveAndFlush(commandSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
|
||||||
|
public CommandSource saveResult(CommandSource commandSource) {
|
||||||
|
return commandSourceRepository.saveAndFlush(commandSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorInfo generateErrorException(Throwable t) {
|
||||||
|
if (t instanceof final RuntimeException e) {
|
||||||
|
return ErrorHandler.handler(e);
|
||||||
|
} else {
|
||||||
|
return new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString() + "}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
|
||||||
|
public CommandSource findCommandSource(CommandWrapper wrapper, String idempotencyKey) {
|
||||||
|
return commandSourceRepository.findByActionNameAndEntityNameAndIdempotencyKey(wrapper.actionName(), wrapper.entityName(),
|
||||||
|
idempotencyKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandSource getInitialCommandSource(CommandWrapper wrapper, JsonCommand jsonCommand, AppUser maker, String idempotencyKey) {
|
||||||
|
CommandSource commandSourceResult;
|
||||||
|
if (jsonCommand.commandId() != null) {
|
||||||
|
commandSourceResult = commandSourceRepository.findById(jsonCommand.commandId())
|
||||||
|
.orElseThrow(() -> new CommandNotFoundException(jsonCommand.commandId()));
|
||||||
|
commandSourceResult.markAsChecked(maker);
|
||||||
|
} else {
|
||||||
|
commandSourceResult = CommandSource.fullEntryFrom(wrapper, jsonCommand, maker, idempotencyKey, UNDER_PROCESSING.getValue());
|
||||||
|
}
|
||||||
|
return commandSourceResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* 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.commands.service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IdempotencyKeyResolver {
|
||||||
|
|
||||||
|
private final IdempotencyKeyGenerator idempotencyKeyGenerator;
|
||||||
|
|
||||||
|
private final FineractProperties fineractProperties;
|
||||||
|
|
||||||
|
public String resolve(CommandWrapper wrapper) {
|
||||||
|
return Optional.ofNullable(wrapper.getIdempotencyKey())
|
||||||
|
.orElseGet(() -> getHeaderAttribute().orElseGet(idempotencyKeyGenerator::create));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String> getHeaderAttribute() {
|
||||||
|
return Optional.ofNullable(RequestContextHolder.getRequestAttributes()) //
|
||||||
|
.filter(ServletRequestAttributes.class::isInstance) //
|
||||||
|
.map(ServletRequestAttributes.class::cast) //
|
||||||
|
.map(ServletRequestAttributes::getRequest) //
|
||||||
|
.map(request -> request.getHeader(fineractProperties.getIdempotencyKeyHeaderName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-4
@@ -87,16 +87,15 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
|
|||||||
commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(),
|
commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(),
|
||||||
commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(),
|
commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getCreditBureauId(),
|
||||||
commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getIdempotencyKey());
|
commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getIdempotencyKey());
|
||||||
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json());
|
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.getCommandJson());
|
||||||
final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand,
|
final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.getCommandJson(), parsedCommand,
|
||||||
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
|
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
|
||||||
commandSourceInput.subResourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(),
|
commandSourceInput.subResourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(),
|
||||||
commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(),
|
commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(),
|
||||||
commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getCreditBureauId(),
|
commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getCreditBureauId(),
|
||||||
commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName());
|
commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName());
|
||||||
|
|
||||||
final boolean makerCheckerApproval = true;
|
return this.processAndLogCommandService.executeCommand(wrapper, command, true);
|
||||||
return this.processAndLogCommandService.executeCommand(wrapper, command, makerCheckerApproval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|||||||
+83
-64
@@ -18,6 +18,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.commands.service;
|
package org.apache.fineract.commands.service;
|
||||||
|
|
||||||
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.ERROR;
|
||||||
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.PROCESSED;
|
||||||
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import io.github.resilience4j.retry.annotation.Retry;
|
import io.github.resilience4j.retry.annotation.Retry;
|
||||||
@@ -25,23 +29,27 @@ import java.lang.reflect.Type;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.batch.exception.ErrorHandler;
|
|
||||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||||
|
import org.apache.fineract.commands.domain.CommandProcessingResultType;
|
||||||
import org.apache.fineract.commands.domain.CommandSource;
|
import org.apache.fineract.commands.domain.CommandSource;
|
||||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.commands.exception.CommandNotFoundException;
|
|
||||||
import org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
|
import org.apache.fineract.commands.exception.RollbackTransactionAsCommandIsNotApprovedByCheckerException;
|
||||||
import org.apache.fineract.commands.exception.UnsupportedCommandException;
|
import org.apache.fineract.commands.exception.UnsupportedCommandException;
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
import org.apache.fineract.commands.provider.CommandHandlerProvider;
|
import org.apache.fineract.commands.provider.CommandHandlerProvider;
|
||||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.infrastructure.hooks.event.HookEvent;
|
import org.apache.fineract.infrastructure.hooks.event.HookEvent;
|
||||||
@@ -53,80 +61,64 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.context.request.RequestAttributes;
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class SynchronousCommandProcessingService implements CommandProcessingService {
|
public class SynchronousCommandProcessingService implements CommandProcessingService {
|
||||||
|
|
||||||
|
public static final String IDEMPOTENCY_KEY_STORE_FLAG = "idempotencyKeyStoreFlag";
|
||||||
|
public static final String COMMAND_SOURCE_ID = "commandSourceId";
|
||||||
private final PlatformSecurityContext context;
|
private final PlatformSecurityContext context;
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
private final ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
|
private final ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
|
||||||
private final ToApiJsonSerializer<CommandProcessingResult> toApiResultJsonSerializer;
|
private final ToApiJsonSerializer<CommandProcessingResult> toApiResultJsonSerializer;
|
||||||
private final CommandSourceRepository commandSourceRepository;
|
|
||||||
private final ConfigurationDomainService configurationDomainService;
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
private final CommandHandlerProvider commandHandlerProvider;
|
private final CommandHandlerProvider commandHandlerProvider;
|
||||||
|
private final IdempotencyKeyResolver idempotencyKeyResolver;
|
||||||
private final IdempotencyKeyGenerator idempotencyKeyGenerator;
|
private final IdempotencyKeyGenerator idempotencyKeyGenerator;
|
||||||
private final FineractProperties fineractProperties;
|
private final CommandSourceService commandSourceService;
|
||||||
|
private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
@Retry(name = "executeCommand", fallbackMethod = "fallbackExecuteCommand")
|
@Retry(name = "executeCommand", fallbackMethod = "fallbackExecuteCommand")
|
||||||
public CommandProcessingResult executeCommand(final CommandWrapper wrapper, final JsonCommand command,
|
public CommandProcessingResult executeCommand(final CommandWrapper wrapper, final JsonCommand command,
|
||||||
final boolean isApprovedByChecker) {
|
final boolean isApprovedByChecker) {
|
||||||
|
// Do not store the idempotency key because of the exception handling
|
||||||
|
setIdempotencyKeyStoreFlag(false);
|
||||||
|
|
||||||
final boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
|
final boolean rollbackTransaction = configurationDomainService.isMakerCheckerEnabledForTask(wrapper.taskPermissionName());
|
||||||
|
String idempotencyKey = idempotencyKeyResolver.resolve(wrapper);
|
||||||
|
exceptionWhenTheRequestAlreadyProcessed(wrapper, idempotencyKey);
|
||||||
|
|
||||||
final NewCommandSourceHandler handler = findCommandHandler(wrapper);
|
// Store idempotency key to the request attribute
|
||||||
|
|
||||||
|
CommandSource savedCommandSource = commandSourceService.saveInitial(wrapper, command, context.authenticatedUser(wrapper),
|
||||||
|
idempotencyKey);
|
||||||
|
storeCommandToIdempotentFilter(savedCommandSource);
|
||||||
|
setIdempotencyKeyStoreFlag(true);
|
||||||
|
|
||||||
final CommandProcessingResult result;
|
final CommandProcessingResult result;
|
||||||
try {
|
try {
|
||||||
result = handler.processCommand(command);
|
result = findCommandHandler(wrapper).processCommand(command);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) { // NOSONAR
|
||||||
|
commandSourceService.saveFailed(commandSourceService.findCommandSource(wrapper, idempotencyKey));
|
||||||
publishHookErrorEvent(wrapper, command, t);
|
publishHookErrorEvent(wrapper, command, t);
|
||||||
throw t;
|
throw t;
|
||||||
}
|
}
|
||||||
|
|
||||||
final AppUser maker = context.authenticatedUser(wrapper);
|
CommandSource initialCommandSource = commandSourceService.findCommandSource(wrapper, idempotencyKey);
|
||||||
|
initialCommandSource.setResult(toApiJsonSerializer.serializeResult(result));
|
||||||
|
initialCommandSource.updateResourceId(result.getResourceId());
|
||||||
|
initialCommandSource.updateForAudit(result);
|
||||||
|
|
||||||
CommandSource commandSourceResult;
|
|
||||||
if (command.commandId() != null) {
|
|
||||||
commandSourceResult = commandSourceRepository.findById(command.commandId())
|
|
||||||
.orElseThrow(() -> new CommandNotFoundException(command.commandId()));
|
|
||||||
commandSourceResult.markAsChecked(maker);
|
|
||||||
} else {
|
|
||||||
String requestIdempotencyKey = null;
|
|
||||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
|
||||||
if (requestAttributes != null) {
|
|
||||||
if (requestAttributes instanceof ServletRequestAttributes) {
|
|
||||||
requestIdempotencyKey = ((ServletRequestAttributes) requestAttributes).getRequest()
|
|
||||||
.getHeader(fineractProperties.getIdempotencyKeyHeaderName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commandSourceResult = CommandSource.fullEntryFrom(wrapper, command, maker,
|
|
||||||
wrapper.getIdempotencyKey() == null
|
|
||||||
? (requestIdempotencyKey == null ? idempotencyKeyGenerator.create() : requestIdempotencyKey)
|
|
||||||
: wrapper.getIdempotencyKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
commandSourceResult.updateForAudit(result);
|
|
||||||
|
|
||||||
String changesOnlyJson;
|
|
||||||
boolean rollBack = (rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker;
|
boolean rollBack = (rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker;
|
||||||
if (result.hasChanges() && !rollBack) {
|
if (result.hasChanges() && !rollBack) {
|
||||||
changesOnlyJson = toApiJsonSerializer.serializeResult(result.getChanges());
|
initialCommandSource.setCommandJson(toApiJsonSerializer.serializeResult(result.getChanges()));
|
||||||
commandSourceResult.updateJsonTo(changesOnlyJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.hasChanges() && wrapper.isUpdateOperation() && !wrapper.isUpdateDatatable()) {
|
initialCommandSource.setStatus(CommandProcessingResultType.PROCESSED.getValue());
|
||||||
commandSourceResult.updateJsonTo(null);
|
commandSourceService.saveResult(initialCommandSource);
|
||||||
}
|
|
||||||
|
|
||||||
if (commandSourceResult.hasJson()) {
|
|
||||||
commandSourceRepository.save(commandSourceResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker) {
|
if ((rollbackTransaction || result.isRollbackTransaction()) && !isApprovedByChecker) {
|
||||||
/*
|
/*
|
||||||
@@ -134,12 +126,12 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
* transactionId, because as there are no entries are created with new transactionId, will throw an error
|
* transactionId, because as there are no entries are created with new transactionId, will throw an error
|
||||||
* when checker approves the transaction
|
* when checker approves the transaction
|
||||||
*/
|
*/
|
||||||
commandSourceResult.updateTransaction(command.getTransactionId());
|
initialCommandSource.updateTransaction(command.getTransactionId());
|
||||||
/*
|
/*
|
||||||
* Update CommandSource json data with JsonCommand json data, line 77 and 81 may update the json data
|
* Update CommandSource json data with JsonCommand json data, line 77 and 81 may update the json data
|
||||||
*/
|
*/
|
||||||
commandSourceResult.updateJsonTo(command.json());
|
initialCommandSource.setCommandJson(command.json());
|
||||||
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(commandSourceResult);
|
throw new RollbackTransactionAsCommandIsNotApprovedByCheckerException(initialCommandSource);
|
||||||
}
|
}
|
||||||
result.setRollbackTransaction(null);
|
result.setRollbackTransaction(null);
|
||||||
|
|
||||||
@@ -148,15 +140,55 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void storeCommandToIdempotentFilter(CommandSource savedCommandSource) {
|
||||||
|
if (savedCommandSource.getId() == null) {
|
||||||
|
throw new IllegalStateException("Command source not saved");
|
||||||
|
}
|
||||||
|
saveCommandToRequest(savedCommandSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void saveCommandToRequest(CommandSource savedCommandSource) {
|
||||||
|
Optional.ofNullable(RequestContextHolder.getRequestAttributes()).ifPresent(requestAttributes -> requestAttributes
|
||||||
|
.setAttribute(COMMAND_SOURCE_ID, savedCommandSource.getId(), RequestAttributes.SCOPE_REQUEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishHookErrorEvent(CommandWrapper wrapper, JsonCommand command, Throwable t) {
|
||||||
|
ErrorInfo ex = commandSourceService.generateErrorException(t);
|
||||||
|
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, gson.toJson(ex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exceptionWhenTheRequestAlreadyProcessed(CommandWrapper wrapper, String idempotencyKey) {
|
||||||
|
CommandSource existingCommand = commandSourceService.findCommandSource(wrapper, idempotencyKey);
|
||||||
|
if (existingCommand != null) {
|
||||||
|
idempotentExceptionByStatus(UNDER_PROCESSING, existingCommand,
|
||||||
|
command -> new IdempotentCommandProcessUnderProcessingException(wrapper));
|
||||||
|
idempotentExceptionByStatus(ERROR, existingCommand, command -> new IdempotentCommandProcessFailedException(wrapper, command));
|
||||||
|
idempotentExceptionByStatus(PROCESSED, existingCommand,
|
||||||
|
command -> new IdempotentCommandProcessSucceedException(wrapper, command.getResult()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void idempotentExceptionByStatus(CommandProcessingResultType status, CommandSource command,
|
||||||
|
Function<CommandSource, AbstractIdempotentCommandException> exceptionMapper) {
|
||||||
|
if (status.getValue().equals(command.getStatus())) {
|
||||||
|
throw exceptionMapper.apply(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setIdempotencyKeyStoreFlag(boolean flag) {
|
||||||
|
Optional.ofNullable(RequestContextHolder.getRequestAttributes()).ifPresent(
|
||||||
|
requestAttributes -> requestAttributes.setAttribute(IDEMPOTENCY_KEY_STORE_FLAG, flag, RequestAttributes.SCOPE_REQUEST));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult logCommand(CommandSource commandSourceResult) {
|
public CommandProcessingResult logCommand(CommandSource commandSourceResult) {
|
||||||
|
|
||||||
commandSourceResult.markAsAwaitingApproval();
|
commandSourceResult.markAsAwaitingApproval();
|
||||||
if (commandSourceResult.getIdempotencyKey() == null) {
|
if (commandSourceResult.getIdempotencyKey() == null) {
|
||||||
commandSourceResult.setIdempotencyKey(idempotencyKeyGenerator.create());
|
commandSourceResult.setIdempotencyKey(idempotencyKeyGenerator.create());
|
||||||
}
|
}
|
||||||
commandSourceResult = commandSourceRepository.saveAndFlush(commandSourceResult);
|
commandSourceResult = commandSourceService.saveResult(commandSourceResult);
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
|
return new CommandProcessingResultBuilder().withCommandId(commandSourceResult.getId())
|
||||||
.withEntityId(commandSourceResult.getResourceId()).build();
|
.withEntityId(commandSourceResult.getResourceId()).build();
|
||||||
@@ -236,20 +268,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
return rollbackTransaction;
|
return rollbackTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publishHookErrorEvent(CommandWrapper wrapper, JsonCommand command, Throwable t) {
|
|
||||||
|
|
||||||
ErrorInfo ex;
|
|
||||||
if (t instanceof final RuntimeException e) {
|
|
||||||
ex = ErrorHandler.handler(e);
|
|
||||||
} else {
|
|
||||||
ex = new ErrorInfo(500, 9999, "{\"Exception\": " + t.toString() + "}");
|
|
||||||
}
|
|
||||||
|
|
||||||
publishHookEvent(wrapper.entityName(), wrapper.actionName(), command, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publishHookEvent(final String entityName, final String actionName, JsonCommand command, final Object result) {
|
private void publishHookEvent(final String entityName, final String actionName, JsonCommand command, final Object result) {
|
||||||
Gson gson = new Gson();
|
|
||||||
try {
|
try {
|
||||||
final AppUser appUser = context.authenticatedUser(CommandWrapper.wrap(actionName, entityName, null, null));
|
final AppUser appUser = context.authenticatedUser(CommandWrapper.wrap(actionName, entityName, null, null));
|
||||||
|
|
||||||
@@ -301,5 +321,4 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
log.error("Error", e);
|
log.error("Error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package org.apache.fineract.infrastructure.core.config;
|
package org.apache.fineract.infrastructure.core.config;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreFilter;
|
||||||
import org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter;
|
import org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter;
|
||||||
import org.apache.fineract.infrastructure.jobs.filter.LoanCOBApiFilter;
|
import org.apache.fineract.infrastructure.jobs.filter.LoanCOBApiFilter;
|
||||||
import org.apache.fineract.infrastructure.security.filter.InsecureTwoFactorAuthenticationFilter;
|
import org.apache.fineract.infrastructure.security.filter.InsecureTwoFactorAuthenticationFilter;
|
||||||
@@ -41,6 +42,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
|||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||||
@@ -67,6 +69,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ServerProperties serverProperties;
|
private ServerProperties serverProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IdempotencyStoreFilter idempotencyStoreFilter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
http //
|
http //
|
||||||
@@ -91,7 +96,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
.addFilterAfter(fineractInstanceModeApiFilter, SecurityContextPersistenceFilter.class) //
|
.addFilterAfter(fineractInstanceModeApiFilter, SecurityContextPersistenceFilter.class) //
|
||||||
.addFilterAfter(tenantAwareBasicAuthenticationFilter(), FineractInstanceModeApiFilter.class) //
|
.addFilterAfter(tenantAwareBasicAuthenticationFilter(), FineractInstanceModeApiFilter.class) //
|
||||||
.addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class) //
|
.addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class) //
|
||||||
.addFilterAfter(loanCOBApiFilter, InsecureTwoFactorAuthenticationFilter.class);
|
.addFilterAfter(loanCOBApiFilter, InsecureTwoFactorAuthenticationFilter.class)
|
||||||
|
.addFilterBefore(idempotencyStoreFilter, ExceptionTranslationFilter.class);
|
||||||
|
|
||||||
if (serverProperties.getSsl().isEnabled()) {
|
if (serverProperties.getSsl().isEnabled()) {
|
||||||
http.requiresChannel(channel -> channel.antMatchers("/api/**").requiresSecure());
|
http.requiresChannel(channel -> channel.antMatchers("/api/**").requiresSecure());
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.core.exception;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public abstract class AbstractIdempotentCommandException extends AbstractPlatformException {
|
||||||
|
|
||||||
|
public static final String IDEMPOTENT_CACHE_HEADER = "x-served-from-cache";
|
||||||
|
@Getter
|
||||||
|
private final String action;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String entity;
|
||||||
|
@Getter
|
||||||
|
private final String idempotencyKey;
|
||||||
|
@Getter
|
||||||
|
private final String response;
|
||||||
|
|
||||||
|
protected AbstractIdempotentCommandException(String action, String entity, String idempotencyKey, String response) {
|
||||||
|
super(null, null);
|
||||||
|
this.action = action;
|
||||||
|
this.entity = entity;
|
||||||
|
this.idempotencyKey = idempotencyKey;
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.core.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.commands.domain.CommandSource;
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when command is sent with same action, entity and idempotency key
|
||||||
|
*/
|
||||||
|
public class IdempotentCommandProcessFailedException extends AbstractIdempotentCommandException {
|
||||||
|
|
||||||
|
private final Integer statusCode;
|
||||||
|
|
||||||
|
public IdempotentCommandProcessFailedException(CommandWrapper wrapper, CommandSource commandSource) {
|
||||||
|
super(wrapper.actionName(), wrapper.entityName(), wrapper.getIdempotencyKey(), commandSource.getResult());
|
||||||
|
this.statusCode = commandSource.getResultStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatusCode() {
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.infrastructure.core.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when command is sent with same action, entity and idempotency key
|
||||||
|
*/
|
||||||
|
public class IdempotentCommandProcessSucceedException extends AbstractIdempotentCommandException {
|
||||||
|
|
||||||
|
public IdempotentCommandProcessSucceedException(CommandWrapper wrapper, String response) {
|
||||||
|
super(wrapper.actionName(), wrapper.entityName(), wrapper.getIdempotencyKey(), response);
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.infrastructure.core.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when command is sent with same action, entity and idempotency key
|
||||||
|
*/
|
||||||
|
public class IdempotentCommandProcessUnderProcessingException extends AbstractIdempotentCommandException {
|
||||||
|
|
||||||
|
public IdempotentCommandProcessUnderProcessingException(CommandWrapper wrapper) {
|
||||||
|
super(wrapper.actionName(), wrapper.entityName(), wrapper.getIdempotencyKey(), wrapper.getJson());
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.core.exceptionmapper;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessFailedException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class IdempotentCommandProcessFailedExceptionMapper implements ExceptionMapper<IdempotentCommandProcessFailedException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response toResponse(final IdempotentCommandProcessFailedException exception) {
|
||||||
|
log.debug("Idempotent processing failed request: {}", exception.getMessage());
|
||||||
|
Status statusCode = Status.fromStatusCode(exception.getStatusCode());
|
||||||
|
return Response.status(statusCode).entity(exception.getResponse())
|
||||||
|
.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER, "true").type(MediaType.APPLICATION_JSON).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.infrastructure.core.exceptionmapper;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessSucceedException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class IdempotentCommandProcessSucceedExceptionMapper implements ExceptionMapper<IdempotentCommandProcessSucceedException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response toResponse(final IdempotentCommandProcessSucceedException exception) {
|
||||||
|
log.debug("Idempotent processing success request: {}", exception.getMessage());
|
||||||
|
return Response.status(Status.OK).entity(exception.getResponse())
|
||||||
|
.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER, "true").type(MediaType.APPLICATION_JSON).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.core.exceptionmapper;
|
||||||
|
|
||||||
|
import static org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.IdempotentCommandProcessUnderProcessingException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class IdempotentCommandProcessUnderProcessingExceptionMapper
|
||||||
|
implements ExceptionMapper<IdempotentCommandProcessUnderProcessingException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response toResponse(final IdempotentCommandProcessUnderProcessingException exception) {
|
||||||
|
log.debug("Idempotent under processing request: {}", exception.getMessage());
|
||||||
|
return Response.status(Status.CONFLICT).entity(exception.getResponse()).header(IDEMPOTENT_CACHE_HEADER, "true")
|
||||||
|
.type(MediaType.APPLICATION_JSON).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.core.filters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.mutable.Mutable;
|
||||||
|
import org.apache.commons.lang3.mutable.MutableObject;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
|
import org.apache.fineract.commands.service.CommandSourceService;
|
||||||
|
import org.apache.fineract.commands.service.SynchronousCommandProcessingService;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class IdempotencyStoreFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final CommandSourceRepository commandSourceRepository;
|
||||||
|
private final CommandSourceService commandSourceService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
|
||||||
|
@NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
Mutable<ContentCachingResponseWrapper> wrapper = new MutableObject<>();
|
||||||
|
if (isAllowedContentTypeRequest(request)) {
|
||||||
|
wrapper.setValue(new ContentCachingResponseWrapper(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, wrapper.getValue() != null ? wrapper.getValue() : response);
|
||||||
|
Optional<Long> commandId = getCommandId(request);
|
||||||
|
boolean isSuccessWithoutStored = isStoreIdempotencyKey(request) && commandId.isPresent() && isAllowedContentTypeResponse(response)
|
||||||
|
&& wrapper.getValue() != null;
|
||||||
|
if (isSuccessWithoutStored) {
|
||||||
|
commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> {
|
||||||
|
commandSource.setResultStatusCode(response.getStatus());
|
||||||
|
commandSource.setResult(new String(wrapper.getValue().getContentAsByteArray(), StandardCharsets.UTF_8));
|
||||||
|
commandSourceService.saveResult(commandSource);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (wrapper.getValue() != null) {
|
||||||
|
wrapper.getValue().copyBodyToResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAllowedContentTypeResponse(HttpServletResponse response) {
|
||||||
|
return Optional.ofNullable(response.getContentType()).map(String::toLowerCase).map(ct -> ct.contains("application/json"))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAllowedContentTypeRequest(HttpServletRequest request) {
|
||||||
|
return Optional.ofNullable(request.getContentType()).map(String::toLowerCase).map(ct -> ct.contains("application/json"))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStoreIdempotencyKey(HttpServletRequest request) {
|
||||||
|
return Optional.ofNullable(request.getAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_STORE_FLAG))
|
||||||
|
.filter(Boolean.class::isInstance).map(Boolean.class::cast).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Long> getCommandId(HttpServletRequest request) {
|
||||||
|
return Optional.ofNullable(request.getAttribute(SynchronousCommandProcessingService.COMMAND_SOURCE_ID))
|
||||||
|
.filter(Long.class::isInstance).map(Long.class::cast);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -29,6 +29,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
|||||||
|
|
||||||
public class ExtendedJpaTransactionManager extends JpaTransactionManager {
|
public class ExtendedJpaTransactionManager extends JpaTransactionManager {
|
||||||
|
|
||||||
|
public ExtendedJpaTransactionManager() {
|
||||||
|
setValidateExistingTransaction(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doBegin(Object transaction, TransactionDefinition definition) {
|
protected void doBegin(Object transaction, TransactionDefinition definition) {
|
||||||
super.doBegin(transaction, definition);
|
super.doBegin(transaction, definition);
|
||||||
|
|||||||
+1
-1
@@ -98,7 +98,7 @@ public final class GoogleGsonSerializerHelper {
|
|||||||
return serializer.toJson(singleDataObject);
|
return serializer.toJson(singleDataObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Gson createSimpleGson() {
|
public static Gson createSimpleGson() {
|
||||||
return createGsonBuilder().create();
|
return createGsonBuilder().create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -41,7 +41,8 @@ public class ScheduledJobRunnerConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public PlatformTransactionManager transactionManager(ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
|
public PlatformTransactionManager transactionManager(ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
|
||||||
ExtendedJpaTransactionManager transactionManager = new ExtendedJpaTransactionManager();
|
ExtendedJpaTransactionManager transactionManager = new ExtendedJpaTransactionManager();
|
||||||
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
|
transactionManager.setValidateExistingTransaction(true);
|
||||||
|
transactionManagerCustomizers.ifAvailable(customizers -> customizers.customize(transactionManager));
|
||||||
return transactionManager;
|
return transactionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.delinquency.service;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import javax.transaction.Transactional;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
@@ -30,13 +29,14 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainService {
|
public class LoanDelinquencyDomainServiceImpl implements LoanDelinquencyDomainService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional(readOnly = true)
|
||||||
public CollectionData getOverdueCollectionData(final Loan loan) {
|
public CollectionData getOverdueCollectionData(final Loan loan) {
|
||||||
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ spring.datasource.hikari.idleTimeout=${FINERACT_HIKARI_IDLE_TIMEOUT:60000}
|
|||||||
spring.datasource.hikari.connectionTimeout=${FINERACT_HIKARI_CONNECTION_TIMEOUT:20000}
|
spring.datasource.hikari.connectionTimeout=${FINERACT_HIKARI_CONNECTION_TIMEOUT:20000}
|
||||||
spring.datasource.hikari.connectionTestquery=${FINERACT_HIKARI_TEST_QUERY:SELECT 1}
|
spring.datasource.hikari.connectionTestquery=${FINERACT_HIKARI_TEST_QUERY:SELECT 1}
|
||||||
spring.datasource.hikari.autoCommit=${FINERACT_HIKARI_AUTO_COMMIT:true}
|
spring.datasource.hikari.autoCommit=${FINERACT_HIKARI_AUTO_COMMIT:true}
|
||||||
|
spring.datasource.hikari.transactionIsolation=${FINERACT_HIKARI_TRANSACTION_ISOLATION:TRANSACTION_REPEATABLE_READ}
|
||||||
spring.datasource.hikari.dataSourceProperties['cachePrepStmts']=${FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS:true}
|
spring.datasource.hikari.dataSourceProperties['cachePrepStmts']=${FINERACT_HIKARI_DS_PROPERTIES_CACHE_PREP_STMTS:true}
|
||||||
spring.datasource.hikari.dataSourceProperties['prepStmtCacheSize']=${FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE:250}
|
spring.datasource.hikari.dataSourceProperties['prepStmtCacheSize']=${FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SIZE:250}
|
||||||
spring.datasource.hikari.dataSourceProperties['prepStmtCacheSqlLimit']=${FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT:2048}
|
spring.datasource.hikari.dataSourceProperties['prepStmtCacheSqlLimit']=${FINERACT_HIKARI_DS_PROPERTIES_PREP_STMT_CACHE_SQL_LIMIT:2048}
|
||||||
|
|||||||
@@ -91,4 +91,7 @@
|
|||||||
<include file="parts/0069_add_unique_constraint_for_reversal_external_id_of_loan_transactions.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0069_add_unique_constraint_for_reversal_external_id_of_loan_transactions.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0070_add_event_configuration_for_delinquency_range_change_event.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0070_add_event_configuration_for_delinquency_range_change_event.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0071_add_external_id_support_for_loan_transaction.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0071_add_external_id_support_for_loan_transaction.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0072_add_result_and status_to_command_source.xml" relativeToChangelogFile="true" />
|
||||||
|
<include file="parts/0073_add_result_status_code_to_command_source.xml" relativeToChangelogFile="true" />
|
||||||
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
|
||||||
|
<changeSet id="1" author="fineract" context="postgresql">
|
||||||
|
<renameColumn newColumnName="status"
|
||||||
|
oldColumnName="processing_result_enum"
|
||||||
|
tableName="m_portfolio_command_source"/>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="2" author="fineract" context="mysql">
|
||||||
|
<renameColumn newColumnName="status"
|
||||||
|
oldColumnName="processing_result_enum"
|
||||||
|
tableName="m_portfolio_command_source"
|
||||||
|
columnDataType="smallint(6)" />
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="3" author="fineract">
|
||||||
|
<addColumn tableName="m_portfolio_command_source">
|
||||||
|
<column name="result" type="TEXT"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
|
||||||
|
<changeSet id="1" author="fineract" context="postgresql">
|
||||||
|
<addColumn tableName="m_portfolio_command_source">
|
||||||
|
<column name="result_status_code" type="int"></column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="2" author="fineract" context="mysql">
|
||||||
|
<addColumn tableName="m_portfolio_command_source">
|
||||||
|
<column name="result_status_code" type="int"></column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -3882,7 +3882,7 @@ CREATE TABLE IF NOT EXISTS `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
`product_id` BIGINT DEFAULT NULL,
|
`product_id` BIGINT DEFAULT NULL,
|
||||||
`transaction_id` varchar(100) DEFAULT NULL,
|
`transaction_id` varchar(100) DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
@@ -3892,7 +3892,7 @@ CREATE TABLE IF NOT EXISTS `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
@@ -5632,10 +5632,12 @@ INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `en
|
|||||||
('portfolio_account_type_enum', 2, 'SAVING', 'EXPENSE', 0),
|
('portfolio_account_type_enum', 2, 'SAVING', 'EXPENSE', 0),
|
||||||
('portfolio_account_type_enum', 3, 'PROVISIONING', 'PROVISIONING', 0),
|
('portfolio_account_type_enum', 3, 'PROVISIONING', 'PROVISIONING', 0),
|
||||||
('portfolio_account_type_enum', 4, 'SHARES', 'SHARES', 0),
|
('portfolio_account_type_enum', 4, 'SHARES', 'SHARES', 0),
|
||||||
('processing_result_enum', 0, 'invalid', 'Invalid', 0),
|
('status', 0, 'invalid', 'Invalid', 0),
|
||||||
('processing_result_enum', 1, 'processed', 'Processed', 0),
|
('status', 1, 'processed', 'Processed', 0),
|
||||||
('processing_result_enum', 2, 'awaiting.approval', 'Awaiting Approval', 0),
|
('status', 2, 'awaiting.approval', 'Awaiting Approval', 0),
|
||||||
('processing_result_enum', 3, 'rejected', 'Rejected', 0),
|
('status', 3, 'rejected', 'Rejected', 0),
|
||||||
|
('status', 4, 'underProcessing', 'Under Processing', 0),
|
||||||
|
('status', 5, 'error', 'Error', 0),
|
||||||
('repayment_period_frequency_enum', 0, 'Days', 'Days', 0),
|
('repayment_period_frequency_enum', 0, 'Days', 'Days', 0),
|
||||||
('repayment_period_frequency_enum', 1, 'Weeks', 'Weeks', 0),
|
('repayment_period_frequency_enum', 1, 'Weeks', 'Weeks', 0),
|
||||||
('repayment_period_frequency_enum', 2, 'Months', 'Months', 0),
|
('repayment_period_frequency_enum', 2, 'Months', 'Months', 0),
|
||||||
|
|||||||
+7
-7
@@ -4054,7 +4054,7 @@ CREATE TABLE IF NOT EXISTS `m_portfolio_command_source` (
|
|||||||
`made_on_date` datetime NOT NULL,
|
`made_on_date` datetime NOT NULL,
|
||||||
`checker_id` BIGINT DEFAULT NULL,
|
`checker_id` BIGINT DEFAULT NULL,
|
||||||
`checked_on_date` datetime DEFAULT NULL,
|
`checked_on_date` datetime DEFAULT NULL,
|
||||||
`processing_result_enum` SMALLINT NOT NULL,
|
`status` SMALLINT NOT NULL,
|
||||||
`product_id` BIGINT DEFAULT NULL,
|
`product_id` BIGINT DEFAULT NULL,
|
||||||
`transaction_id` varchar(100) DEFAULT NULL,
|
`transaction_id` varchar(100) DEFAULT NULL,
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
@@ -4064,7 +4064,7 @@ CREATE TABLE IF NOT EXISTS `m_portfolio_command_source` (
|
|||||||
KEY `entity_name` (`entity_name`,`resource_id`),
|
KEY `entity_name` (`entity_name`,`resource_id`),
|
||||||
KEY `made_on_date` (`made_on_date`),
|
KEY `made_on_date` (`made_on_date`),
|
||||||
KEY `checked_on_date` (`checked_on_date`),
|
KEY `checked_on_date` (`checked_on_date`),
|
||||||
KEY `processing_result_enum` (`processing_result_enum`),
|
KEY `status` (`status`),
|
||||||
KEY `office_id` (`office_id`),
|
KEY `office_id` (`office_id`),
|
||||||
KEY `group_id` (`office_id`),
|
KEY `group_id` (`office_id`),
|
||||||
KEY `client_id` (`office_id`),
|
KEY `client_id` (`office_id`),
|
||||||
@@ -4075,7 +4075,7 @@ CREATE TABLE IF NOT EXISTS `m_portfolio_command_source` (
|
|||||||
|
|
||||||
-- Dumping data for table mifostenant-reference.m_portfolio_command_source: ~72 rows (approximately)
|
-- Dumping data for table mifostenant-reference.m_portfolio_command_source: ~72 rows (approximately)
|
||||||
/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
|
/*!40000 ALTER TABLE `m_portfolio_command_source` DISABLE KEYS */;
|
||||||
INSERT INTO `m_portfolio_command_source` (`id`, `action_name`, `entity_name`, `office_id`, `group_id`, `client_id`, `loan_id`, `savings_account_id`, `api_get_url`, `resource_id`, `subresource_id`, `command_as_json`, `maker_id`, `made_on_date`, `checker_id`, `checked_on_date`, `processing_result_enum`, `product_id`, `transaction_id`) VALUES
|
INSERT INTO `m_portfolio_command_source` (`id`, `action_name`, `entity_name`, `office_id`, `group_id`, `client_id`, `loan_id`, `savings_account_id`, `api_get_url`, `resource_id`, `subresource_id`, `command_as_json`, `maker_id`, `made_on_date`, `checker_id`, `checked_on_date`, `status`, `product_id`, `transaction_id`) VALUES
|
||||||
(1, 'CREATE', 'STAFF', 1, NULL, NULL, NULL, NULL, '/staff/template', 1, NULL, '{"isLoanOfficer":true,"officeId":1,"firstname":"Aliya","lastname":"A"}', 1, '2014-03-07 19:10:05', NULL, NULL, 1, NULL, NULL),
|
(1, 'CREATE', 'STAFF', 1, NULL, NULL, NULL, NULL, '/staff/template', 1, NULL, '{"isLoanOfficer":true,"officeId":1,"firstname":"Aliya","lastname":"A"}', 1, '2014-03-07 19:10:05', NULL, NULL, 1, NULL, NULL),
|
||||||
(2, 'CREATE', 'USER', 1, NULL, NULL, NULL, NULL, '/users/template', 2, NULL, '{"sendPasswordToEmail":true,"officeId":1,"username":"adama","firstname":"Adam","lastname":"A","email":"adama@123.com","roles":["1"]}', 1, '2014-03-07 19:19:31', NULL, NULL, 1, NULL, NULL),
|
(2, 'CREATE', 'USER', 1, NULL, NULL, NULL, NULL, '/users/template', 2, NULL, '{"sendPasswordToEmail":true,"officeId":1,"username":"adama","firstname":"Adam","lastname":"A","email":"adama@123.com","roles":["1"]}', 1, '2014-03-07 19:19:31', NULL, NULL, 1, NULL, NULL),
|
||||||
(3, 'CREATE', 'CLIENT', 1, NULL, 1, NULL, NULL, '/clients/template', 1, NULL, '{"officeId":1,"staffId":1,"firstname":"Smith","lastname":"R","active":true,"locale":"en","dateFormat":"dd MMMM yyyy","activationDate":"07 March 2014","submittedOnDate":"01 January 2010","savingsProductId":null}', 1, '2014-03-07 19:23:36', NULL, NULL, 1, NULL, NULL),
|
(3, 'CREATE', 'CLIENT', 1, NULL, 1, NULL, NULL, '/clients/template', 1, NULL, '{"officeId":1,"staffId":1,"firstname":"Smith","lastname":"R","active":true,"locale":"en","dateFormat":"dd MMMM yyyy","activationDate":"07 March 2014","submittedOnDate":"01 January 2010","savingsProductId":null}', 1, '2014-03-07 19:23:36', NULL, NULL, 1, NULL, NULL),
|
||||||
@@ -5889,10 +5889,10 @@ INSERT INTO `r_enum_value` (`enum_name`, `enum_id`, `enum_message_property`, `en
|
|||||||
('portfolio_account_type_enum', 2, 'SAVING', 'EXPENSE', 0),
|
('portfolio_account_type_enum', 2, 'SAVING', 'EXPENSE', 0),
|
||||||
('portfolio_account_type_enum', 3, 'PROVISIONING', 'PROVISIONING', 0),
|
('portfolio_account_type_enum', 3, 'PROVISIONING', 'PROVISIONING', 0),
|
||||||
('portfolio_account_type_enum', 4, 'SHARES', 'SHARES', 0),
|
('portfolio_account_type_enum', 4, 'SHARES', 'SHARES', 0),
|
||||||
('processing_result_enum', 0, 'invalid', 'Invalid', 0),
|
('status', 0, 'invalid', 'Invalid', 0),
|
||||||
('processing_result_enum', 1, 'processed', 'Processed', 0),
|
('status', 1, 'processed', 'Processed', 0),
|
||||||
('processing_result_enum', 2, 'awaiting.approval', 'Awaiting Approval', 0),
|
('status', 2, 'awaiting.approval', 'Awaiting Approval', 0),
|
||||||
('processing_result_enum', 3, 'rejected', 'Rejected', 0),
|
('status', 3, 'rejected', 'Rejected', 0),
|
||||||
('repayment_period_frequency_enum', 0, 'Days', 'Days', 0),
|
('repayment_period_frequency_enum', 0, 'Days', 'Days', 0),
|
||||||
('repayment_period_frequency_enum', 1, 'Weeks', 'Weeks', 0),
|
('repayment_period_frequency_enum', 1, 'Weeks', 'Weeks', 0),
|
||||||
('repayment_period_frequency_enum', 2, 'Months', 'Months', 0),
|
('repayment_period_frequency_enum', 2, 'Months', 'Months', 0),
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 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.commands.service;
|
||||||
|
|
||||||
|
import static org.apache.fineract.commands.domain.CommandProcessingResultType.UNDER_PROCESSING;
|
||||||
|
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSource;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
import org.apache.fineract.infrastructure.codes.exception.CodeNotFoundException;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
public class CommandSourceServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CommandSourceRepository commandSourceRepository;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CommandSourceService underTest;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
ThreadLocalContextUtil.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateFromWrapper() {
|
||||||
|
CommandWrapper wrapper = CommandWrapper.wrap("act", "ent", 1L, 1L);
|
||||||
|
JsonCommand jsonCommand = JsonCommand.from("{}");
|
||||||
|
AppUser appUser = Mockito.mock(AppUser.class);
|
||||||
|
|
||||||
|
FineractPlatformTenant ft = new FineractPlatformTenant(1L, "t1", "n1", ZoneId.systemDefault().toString(), null);
|
||||||
|
ThreadLocalContextUtil.setTenant(ft);
|
||||||
|
|
||||||
|
String idk = "idk";
|
||||||
|
underTest.saveInitial(wrapper, jsonCommand, appUser, idk);
|
||||||
|
|
||||||
|
ArgumentCaptor<CommandSource> commandSourceArgumentCaptor = ArgumentCaptor.forClass(CommandSource.class);
|
||||||
|
Mockito.verify(commandSourceRepository).saveAndFlush(commandSourceArgumentCaptor.capture());
|
||||||
|
|
||||||
|
CommandSource captured = commandSourceArgumentCaptor.getValue();
|
||||||
|
Assertions.assertEquals(idk, captured.getIdempotencyKey());
|
||||||
|
Assertions.assertEquals(UNDER_PROCESSING.getValue(), captured.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateFromExisting() {
|
||||||
|
CommandWrapper wrapper = CommandWrapper.wrap("act", "ent", 1L, 1L);
|
||||||
|
long commandId = 1L;
|
||||||
|
JsonCommand jsonCommand = JsonCommand.fromExistingCommand(commandId, "", null, null, null, 1L, null, null, null, null, null, null,
|
||||||
|
null, null, null, null, null);
|
||||||
|
CommandSource commandMock = Mockito.mock(CommandSource.class);
|
||||||
|
Mockito.when(commandSourceRepository.saveAndFlush(commandMock)).thenReturn(commandMock);
|
||||||
|
Mockito.when(commandSourceRepository.findById(commandId)).thenReturn(Optional.of(commandMock));
|
||||||
|
AppUser appUser = Mockito.mock(AppUser.class);
|
||||||
|
|
||||||
|
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "t1", "n1", ZoneId.systemDefault().toString(), null));
|
||||||
|
|
||||||
|
CommandSource actual = underTest.saveInitial(wrapper, jsonCommand, appUser, "idk");
|
||||||
|
|
||||||
|
ArgumentCaptor<CommandSource> commandSourceArgumentCaptor = ArgumentCaptor.forClass(CommandSource.class);
|
||||||
|
Mockito.verify(commandSourceRepository).saveAndFlush(commandSourceArgumentCaptor.capture());
|
||||||
|
|
||||||
|
CommandSource captured = commandSourceArgumentCaptor.getValue();
|
||||||
|
Assertions.assertEquals(actual, captured);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateErrorException() {
|
||||||
|
ErrorInfo result = underTest.generateErrorException(new CodeNotFoundException("foo"));
|
||||||
|
Assertions.assertEquals(404, result.getStatusCode());
|
||||||
|
Assertions.assertEquals(1001, result.getErrorCode());
|
||||||
|
Assertions.assertTrue(result.getMessage().contains("Code with name `foo` does not exist"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* 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.commands.service;
|
||||||
|
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
public class IdempotencyKeyResolverTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IdempotencyKeyGenerator idempotencyKeyGenerator;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FineractProperties fineractProperties;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private IdempotencyKeyResolver underTest;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIPKResolveFromRequest() {
|
||||||
|
String idkh = "foo";
|
||||||
|
String idk = "bar";
|
||||||
|
Mockito.when(fineractProperties.getIdempotencyKeyHeaderName()).thenReturn(idkh);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader(idkh, idk);
|
||||||
|
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
|
||||||
|
RequestContextHolder.setRequestAttributes(requestAttributes);
|
||||||
|
CommandWrapper wrapper = CommandWrapper.wrap("act", "ent", 1L, 1L);
|
||||||
|
String resolvedIdk = underTest.resolve(wrapper);
|
||||||
|
Assertions.assertEquals(idk, resolvedIdk);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIPKResolveFromGenerate() {
|
||||||
|
String idk = "idk";
|
||||||
|
Mockito.when(idempotencyKeyGenerator.create()).thenReturn(idk);
|
||||||
|
RequestContextHolder.setRequestAttributes(null);
|
||||||
|
CommandWrapper wrapper = CommandWrapper.wrap("act", "ent", 1L, 1L);
|
||||||
|
String resolvedIdk = underTest.resolve(wrapper);
|
||||||
|
Assertions.assertEquals(idk, resolvedIdk);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIPKResolveFromWrapper() {
|
||||||
|
RequestContextHolder.setRequestAttributes(null);
|
||||||
|
String idk = "idk";
|
||||||
|
CommandWrapper wrapper = new CommandWrapper(null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||||
|
null, null, null, idk);
|
||||||
|
String resolvedIdk = underTest.resolve(wrapper);
|
||||||
|
Assertions.assertEquals(idk, resolvedIdk);
|
||||||
|
}
|
||||||
|
}
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* 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.commands.service;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.fineract.commands.domain.CommandProcessingResultType;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSource;
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.apache.fineract.commands.provider.CommandHandlerProvider;
|
||||||
|
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
public class SynchronousCommandProcessingServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PlatformSecurityContext context;
|
||||||
|
@Mock
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
@Mock
|
||||||
|
private ToApiJsonSerializer<Map<String, Object>> toApiJsonSerializer;
|
||||||
|
@Mock
|
||||||
|
private ToApiJsonSerializer<CommandProcessingResult> toApiResultJsonSerializer;
|
||||||
|
@Mock
|
||||||
|
private ConfigurationDomainService configurationDomainService;
|
||||||
|
@Mock
|
||||||
|
private CommandHandlerProvider commandHandlerProvider;
|
||||||
|
@Mock
|
||||||
|
private IdempotencyKeyResolver idempotencyKeyResolver;
|
||||||
|
@Mock
|
||||||
|
private IdempotencyKeyGenerator idempotencyKeyGenerator;
|
||||||
|
@Mock
|
||||||
|
private CommandSourceService commandSourceService;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private SynchronousCommandProcessingService underTest;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteCommandSuccess() {
|
||||||
|
|
||||||
|
CommandWrapper commandWrapper = Mockito.mock(CommandWrapper.class);
|
||||||
|
when(commandWrapper.isDatatableResource()).thenReturn(false);
|
||||||
|
when(commandWrapper.isNoteResource()).thenReturn(false);
|
||||||
|
when(commandWrapper.isSurveyResource()).thenReturn(false);
|
||||||
|
when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false);
|
||||||
|
JsonCommand jsonCommand = Mockito.mock(JsonCommand.class);
|
||||||
|
|
||||||
|
NewCommandSourceHandler newCommandSourceHandler = Mockito.mock(NewCommandSourceHandler.class);
|
||||||
|
CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class);
|
||||||
|
when(commandProcessingResult.isRollbackTransaction()).thenReturn(false);
|
||||||
|
when(newCommandSourceHandler.processCommand(jsonCommand)).thenReturn(commandProcessingResult);
|
||||||
|
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(newCommandSourceHandler);
|
||||||
|
|
||||||
|
when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false);
|
||||||
|
String idk = "idk";
|
||||||
|
when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk);
|
||||||
|
CommandSource commandSource = Mockito.mock(CommandSource.class);
|
||||||
|
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null).thenReturn(commandSource);
|
||||||
|
|
||||||
|
AppUser appUser = Mockito.mock(AppUser.class);
|
||||||
|
when(commandSourceService.saveInitial(commandWrapper, jsonCommand, appUser, idk)).thenReturn(commandSource);
|
||||||
|
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser);
|
||||||
|
|
||||||
|
CommandProcessingResult actualCommandProcessingResult = underTest.executeCommand(commandWrapper, jsonCommand, false);
|
||||||
|
|
||||||
|
verify(commandSourceService).saveInitial(commandWrapper, jsonCommand, appUser, idk);
|
||||||
|
verify(commandSource).setStatus(CommandProcessingResultType.PROCESSED.getValue());
|
||||||
|
verify(commandSourceService).saveResult(commandSource);
|
||||||
|
|
||||||
|
Assertions.assertEquals(commandProcessingResult, actualCommandProcessingResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteCommandFails() {
|
||||||
|
CommandWrapper commandWrapper = Mockito.mock(CommandWrapper.class);
|
||||||
|
when(commandWrapper.isDatatableResource()).thenReturn(false);
|
||||||
|
when(commandWrapper.isNoteResource()).thenReturn(false);
|
||||||
|
when(commandWrapper.isSurveyResource()).thenReturn(false);
|
||||||
|
when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false);
|
||||||
|
JsonCommand jsonCommand = Mockito.mock(JsonCommand.class);
|
||||||
|
|
||||||
|
NewCommandSourceHandler newCommandSourceHandler = Mockito.mock(NewCommandSourceHandler.class);
|
||||||
|
CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class);
|
||||||
|
CommandSource commandSource = Mockito.mock(CommandSource.class);
|
||||||
|
when(commandProcessingResult.isRollbackTransaction()).thenReturn(false);
|
||||||
|
RuntimeException runtimeException = new RuntimeException("foo");
|
||||||
|
when(newCommandSourceHandler.processCommand(jsonCommand)).thenThrow(runtimeException);
|
||||||
|
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(newCommandSourceHandler);
|
||||||
|
|
||||||
|
when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false);
|
||||||
|
String idk = "idk";
|
||||||
|
when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk);
|
||||||
|
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null);
|
||||||
|
|
||||||
|
AppUser appUser = Mockito.mock(AppUser.class);
|
||||||
|
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser);
|
||||||
|
when(commandSourceService.saveInitial(commandWrapper, jsonCommand, appUser, idk)).thenReturn(commandSource);
|
||||||
|
|
||||||
|
CommandSource initialCommandSource = Mockito.mock(CommandSource.class);
|
||||||
|
|
||||||
|
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(initialCommandSource);
|
||||||
|
|
||||||
|
Assertions.assertThrows(RuntimeException.class, () -> {
|
||||||
|
underTest.executeCommand(commandWrapper, jsonCommand, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(commandSourceService).saveInitial(commandWrapper, jsonCommand, appUser, idk);
|
||||||
|
verify(commandSourceService).generateErrorException(runtimeException);
|
||||||
|
}
|
||||||
|
}
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* 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.integrationtests;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
|
import io.restassured.builder.ResponseSpecBuilder;
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import io.restassured.response.Response;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.apache.fineract.cob.data.BusinessStep;
|
||||||
|
import org.apache.fineract.cob.data.JobBusinessStepConfigData;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractIdempotentCommandException;
|
||||||
|
import org.apache.fineract.integrationtests.common.IdempotencyHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class IdempotencyTest {
|
||||||
|
|
||||||
|
private ResponseSpecification responseSpec;
|
||||||
|
private RequestSpecification requestSpec;
|
||||||
|
public static final String LOAN_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS";
|
||||||
|
public static final String LOAN_CATEGORY_NAME = "loan";
|
||||||
|
public static final String APPLY_CHARGE_TO_OVERDUE_LOANS = "APPLY_CHARGE_TO_OVERDUE_LOANS";
|
||||||
|
public static final String NOT_BELONGING_BUSINESS_STEP_NAME = "APPLY_CHARGE_TO_OVERDUE_LOANS_2";
|
||||||
|
public static final String LOAN_DELINQUENCY_CLASSIFICATION = "LOAN_DELINQUENCY_CLASSIFICATION";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
Utils.initializeRESTAssured();
|
||||||
|
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
|
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
|
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldUpdateStepOrder() {
|
||||||
|
JobBusinessStepConfigData originalStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec,
|
||||||
|
LOAN_JOB_NAME);
|
||||||
|
|
||||||
|
String idempotencyKeyHeader = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
List<BusinessStep> requestBody = new ArrayList<>();
|
||||||
|
requestBody.add(getBusinessSteps(1L, APPLY_CHARGE_TO_OVERDUE_LOANS));
|
||||||
|
Response response = IdempotencyHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
|
||||||
|
Response responseSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
|
||||||
|
Assertions.assertEquals(response.getBody().asString(), responseSecond.getBody().asString());
|
||||||
|
Assertions.assertNull(response.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
Assertions.assertNotNull(responseSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
|
||||||
|
idempotencyKeyHeader = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
JobBusinessStepConfigData newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec,
|
||||||
|
LOAN_JOB_NAME);
|
||||||
|
BusinessStep applyChargeStep = newStepConfig.getBusinessSteps().stream()
|
||||||
|
.filter(businessStep -> APPLY_CHARGE_TO_OVERDUE_LOANS.equals(businessStep.getStepName())).findFirst().get();
|
||||||
|
assertEquals(1, newStepConfig.getBusinessSteps().size());
|
||||||
|
assertEquals(1L, applyChargeStep.getOrder());
|
||||||
|
|
||||||
|
requestBody.add(getBusinessSteps(2L, LOAN_DELINQUENCY_CLASSIFICATION));
|
||||||
|
|
||||||
|
Response update = IdempotencyHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
|
||||||
|
Response updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
|
||||||
|
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
|
||||||
|
|
||||||
|
newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME);
|
||||||
|
applyChargeStep = newStepConfig.getBusinessSteps().stream()
|
||||||
|
.filter(businessStep -> APPLY_CHARGE_TO_OVERDUE_LOANS.equals(businessStep.getStepName())).findFirst().get();
|
||||||
|
BusinessStep loanDelinquencyStep = newStepConfig.getBusinessSteps().stream()
|
||||||
|
.filter(businessStep -> LOAN_DELINQUENCY_CLASSIFICATION.equals(businessStep.getStepName())).findFirst().get();
|
||||||
|
assertEquals(2, newStepConfig.getBusinessSteps().size());
|
||||||
|
assertEquals(1L, applyChargeStep.getOrder());
|
||||||
|
assertEquals(2L, loanDelinquencyStep.getOrder());
|
||||||
|
|
||||||
|
requestBody.remove(1);
|
||||||
|
idempotencyKeyHeader = UUID.randomUUID().toString();
|
||||||
|
update = IdempotencyHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
|
||||||
|
updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(requestBody), idempotencyKeyHeader);
|
||||||
|
|
||||||
|
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
|
||||||
|
|
||||||
|
newStepConfig = IdempotencyHelper.getConfiguredBusinessStepsByJobName(requestSpec, responseSpec, LOAN_JOB_NAME);
|
||||||
|
applyChargeStep = newStepConfig.getBusinessSteps().stream()
|
||||||
|
.filter(businessStep -> APPLY_CHARGE_TO_OVERDUE_LOANS.equals(businessStep.getStepName())).findFirst().get();
|
||||||
|
assertEquals(1, newStepConfig.getBusinessSteps().size());
|
||||||
|
assertEquals(1L, applyChargeStep.getOrder());
|
||||||
|
|
||||||
|
idempotencyKeyHeader = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
update = IdempotencyHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(originalStepConfig.getBusinessSteps()), idempotencyKeyHeader);
|
||||||
|
updateSecond = IdempotencyHelper.updateBusinessStepOrder(requestSpec, responseSpec, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(originalStepConfig.getBusinessSteps()), idempotencyKeyHeader);
|
||||||
|
|
||||||
|
Assertions.assertNull(update.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
Assertions.assertNotNull(updateSecond.header(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
Assertions.assertEquals(update.getBody().asString(), updateSecond.getBody().asString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shoudTheSecondRequestWithSameIdempotencyKeyWillFailureToo() {
|
||||||
|
ResponseSpecification responseSpecForError = new ResponseSpecBuilder().expectStatusCode(400).build();
|
||||||
|
List<BusinessStep> requestBody = new ArrayList<>();
|
||||||
|
String idempotencyKey = UUID.randomUUID().toString();
|
||||||
|
// IdempotencyHelper.configuredApiParameterErrorFromJsonString(response.getBody().asString())
|
||||||
|
|
||||||
|
Response response1 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(requestBody), idempotencyKey);
|
||||||
|
Assertions.assertNull(response1.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
String originalBody = response1.getBody().asString();
|
||||||
|
|
||||||
|
Response response2 = IdempotencyHelper.updateBusinessStepOrderWithError(requestSpec, responseSpecForError, LOAN_JOB_NAME,
|
||||||
|
IdempotencyHelper.toJsonString(requestBody), idempotencyKey);
|
||||||
|
Assertions.assertNotNull(response2.getHeader(AbstractIdempotentCommandException.IDEMPOTENT_CACHE_HEADER));
|
||||||
|
Assertions.assertEquals(originalBody, response2.getBody().asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BusinessStep getBusinessSteps(Long order, String stepName) {
|
||||||
|
BusinessStep businessStep = new BusinessStep();
|
||||||
|
businessStep.setStepName(stepName);
|
||||||
|
businessStep.setOrder(order);
|
||||||
|
return businessStep;
|
||||||
|
}
|
||||||
|
}
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* 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.integrationtests.common;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import io.restassured.response.Response;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import io.restassured.specification.ResponseSpecification;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.cob.data.BusinessStep;
|
||||||
|
import org.apache.fineract.cob.data.JobBusinessStepConfigData;
|
||||||
|
import org.apache.fineract.cob.data.JobBusinessStepDetail;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public final class IdempotencyHelper {
|
||||||
|
|
||||||
|
private static final String BUSINESS_STEPS_API_URL_START = "/fineract-provider/api/v1/jobs/";
|
||||||
|
private static final String BUSINESS_STEPS_API_URL_END = "/steps?" + Utils.TENANT_IDENTIFIER;
|
||||||
|
private static final String GET_AVAILABLE_BUSINESS_STEPS_API_URL_END = "/available-steps?" + Utils.TENANT_IDENTIFIER;
|
||||||
|
|
||||||
|
private IdempotencyHelper() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJsonString(final List<BusinessStep> batchRequests) {
|
||||||
|
return new Gson().toJson(new BusinessStepWrapper(batchRequests));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JobBusinessStepConfigData configuredBusinessStepFromJsonString(final String json) {
|
||||||
|
return new Gson().fromJson(json, new TypeToken<JobBusinessStepConfigData>() {}.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JobBusinessStepDetail availableBusinessStepFromJsonString(final String json) {
|
||||||
|
return new Gson().fromJson(json, new TypeToken<JobBusinessStepDetail>() {}.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JobBusinessStepConfigData getConfiguredBusinessStepsByJobName(final RequestSpecification requestSpec,
|
||||||
|
final ResponseSpecification responseSpec, String jobName) {
|
||||||
|
final String response = Utils.performServerGet(requestSpec, responseSpec,
|
||||||
|
BUSINESS_STEPS_API_URL_START + jobName + BUSINESS_STEPS_API_URL_END);
|
||||||
|
log.info("BusinessStepConfigurationHelper Response: {}", response);
|
||||||
|
return configuredBusinessStepFromJsonString(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JobBusinessStepDetail getAvailableBusinessStepsByJobName(final RequestSpecification requestSpec,
|
||||||
|
final ResponseSpecification responseSpec, String jobName) {
|
||||||
|
final String response = Utils.performServerGet(requestSpec, responseSpec,
|
||||||
|
BUSINESS_STEPS_API_URL_START + jobName + GET_AVAILABLE_BUSINESS_STEPS_API_URL_END);
|
||||||
|
log.info("BusinessStepConfigurationHelper Response: {}", response);
|
||||||
|
return availableBusinessStepFromJsonString(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Response updateBusinessStepOrder(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||||
|
String jobName, String jsonBodyToSend, String idempotencyKey) {
|
||||||
|
Response response = Utils.performServerPutRaw(requestSpec, responseSpec,
|
||||||
|
BUSINESS_STEPS_API_URL_START + jobName + BUSINESS_STEPS_API_URL_END,
|
||||||
|
request -> request.header("Idempotency-Key", idempotencyKey).body(jsonBodyToSend));
|
||||||
|
log.info("BusinessStepConfigurationHelper Response: {}", response.getBody().asString());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Response updateBusinessStepOrderWithError(final RequestSpecification requestSpec,
|
||||||
|
final ResponseSpecification responseSpec, String jobName, String jsonBodyToSend, String idempotencyKey) {
|
||||||
|
String url = BUSINESS_STEPS_API_URL_START + jobName + BUSINESS_STEPS_API_URL_END;
|
||||||
|
return Utils.performServerPutRaw(requestSpec, responseSpec, url,
|
||||||
|
request -> request.header("Idempotency-Key", idempotencyKey).body(jsonBodyToSend));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class BusinessStepWrapper {
|
||||||
|
|
||||||
|
private List<BusinessStep> businessSteps;
|
||||||
|
|
||||||
|
private BusinessStepWrapper(List<BusinessStep> businessSteps) {
|
||||||
|
this.businessSteps = businessSteps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -53,6 +53,7 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
import java.util.function.Function;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.http.conn.HttpHostConnectException;
|
import org.apache.http.conn.HttpHostConnectException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -164,6 +165,11 @@ public final class Utils {
|
|||||||
return performServerGet(requestSpec, responseSpec, url, null);
|
return performServerGet(requestSpec, responseSpec, url, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Response performServerGetRaw(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||||
|
final String getURL, Function<RequestSpecification, RequestSpecification> requestMapper) {
|
||||||
|
return requestMapper.apply(given().spec(requestSpec)).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn();
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
public static <T> T performServerGet(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||||
final String getURL, final String jsonAttributeToGetBack) {
|
final String getURL, final String jsonAttributeToGetBack) {
|
||||||
final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
|
final String json = given().spec(requestSpec).expect().spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
|
||||||
@@ -212,6 +218,11 @@ public final class Utils {
|
|||||||
return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
|
return (T) JsonPath.from(json).get(jsonAttributeToGetBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Response performServerPutRaw(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||||
|
final String putURL, Function<RequestSpecification, RequestSpecification> bodyMapper) {
|
||||||
|
return bodyMapper.apply(given().spec(requestSpec)).expect().spec(responseSpec).log().ifError().when().put(putURL).andReturn();
|
||||||
|
}
|
||||||
|
|
||||||
public static String performServerPut(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
public static String performServerPut(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||||
final String putURL, final String jsonBodyToSend) {
|
final String putURL, final String jsonBodyToSend) {
|
||||||
return performServerPut(requestSpec, responseSpec, putURL, jsonBodyToSend, null);
|
return performServerPut(requestSpec, responseSpec, putURL, jsonBodyToSend, null);
|
||||||
|
|||||||
Reference in New Issue
Block a user