Merge branch 'MIFOSX-478' of git://github.com/mifoscontributer/mifosx into mifoscontributer-MIFOSX-478

This commit is contained in:
Vishwas Babu A J
2013-07-19 13:52:16 +05:30
26 changed files with 1403 additions and 26 deletions
+393
View File
@@ -890,6 +890,22 @@
<td><a href="#charges_update">Update a Charge</a></td>
<td><a href="#charges_delete">Delete a Charge</a></td>
</tr>
<tr>
<td><a href="#loanproductmix">Loan Product Mix</a></td>
<td>loanproducts?associations=productMixes</td>
<td></td>
<td><a href="#loanproductmix_list">List Loan Products Mix</a></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loanproducts/{productId}/productmix</td>
<td><a href="#loanproductmix_create">Create a Loan Product Mix</a></td>
<td><a href="#loanproductmix_retrieve">Retrieve Loan Products Mix</a></td>
<td><a href="#loanproductmix_update">Update Loan Product Mix</a></td>
<td><a href="#loanproductmix_delete">Delete Loan Products Mix</a></td>
</tr>
</table>
</div>
</div>
@@ -1403,6 +1419,10 @@
<td>loanproducts/template</td>
<td><a href="#loanproducts_template">Retrieve Loan Product Template</a></td>
</tr>
<tr >
<td>loanproducts/template?isProductMixTemplate=true</td>
<td><a href="#loanproductmix_template">Retrieve Loan Product Mix Template</a></td>
</tr>
<tr class="alt">
<td>loans/template?clientId={clientId}</td>
<td><a href="#loans_template">Retrieve Loan Template</a></td>
@@ -8043,6 +8063,355 @@ Request Body:
</div>
<!-- end of loan products api docs -->
<!-- start of loan products mix api docs -->
<a id="loanproductmix" name="loanproductmix" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Loan Products Mix</h3>
<p>If you have the appropriate permissions, you can decide which types of products a given client or group can mix. In this way, you can restrict clients from having active loans accounts of different products at the same time.</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>restrictedProducts</td>
</tr>
<tr>
<td class=fielddesc>List of productIds to create a product mix</td>
</tr>
</table>
</div>
</div>
<a id="loanproductmix_list" name="loanproductmix_list"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>List Loan Product Mix</h4>
<p>Example Requests:</p>
<div class=apiClick>loanproducts?associations=productMixes</div>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/loanproducts?associations=productMixes
</code>
<code class="method-response">
[
{
"productId": 1,
"productName": "Personal Loan",
"restrictedProducts": [
{
"id": 2,
"name": "Joint Loan",
"includeInBorrowerCycle": false
},
{
"id": 5,
"name": "Primary Loan",
"includeInBorrowerCycle": false
},
{
"id": 3,
"name": "Daily Loan",
"includeInBorrowerCycle": false
}
],
"allowedProducts": [
{
"id": 1,
"name": "Personal Loan",
"includeInBorrowerCycle": false
},
{
"id": 6,
"name": "Personal Loan -2",
"includeInBorrowerCycle": false
},
{
"id": 4,
"name": "Weekly Loan",
"includeInBorrowerCycle": false
}
]
},
{
"productId": 2,
"productName": "Joint Loan",
"restrictedProducts": [
{
"id": 1,
"name": "Personal Loan",
"includeInBorrowerCycle": false
},
{
"id": 6,
"name": "Personal Loan -2",
"includeInBorrowerCycle": false
}
],
"allowedProducts": [
{
"id": 3,
"name": "Daily Loan",
"includeInBorrowerCycle": false
},
{
"id": 2,
"name": "Joint Loan",
"includeInBorrowerCycle": false
},
{
"id": 5,
"name": "Primary Loan",
"includeInBorrowerCycle": false
},
{
"id": 4,
"name": "Weekly Loan",
"includeInBorrowerCycle": false
}
]
}
]
</code>
</div>
</div>
<a id="loanproductmix_template" name="loanproductmix_template"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Retrieve Product Mix Details Template</h4>
<p>This is a convenience resource. It can be useful when
building maintenance user interface screens for client
applications. The template data returned consists of any or all
of:
<ul>
<li class=normalli>Field Defaults</li>
<li class=normalli>Allowed Value Lists</li>
</ul>
</p>
<p>Example Request:</p>
<p>This request gets all the products details for which productmix is not defined.</p>
<br/>
<div class=apiClick>loanproducts/template?isProductMixTemplate=true</div>
<br/><br/>
<p>This request gets the productmix details for the specific loanproduct.</p>
<br/>
<div class=apiClick>loanproducts/5/productmix</div>
<br/>
<div class=apiClick>loanproducts/5/productmix?template=true</div>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/loanproducts/template?isProductMixTemplate=true
</code>
<code class="method-response">
{
"productOptions": [
{
"id": 5,
"name": "Primary Loan"
},
{
"id": 4,
"name": "Weekly Loan"
}
]
}
</code>
<code class="method-declaration">
GET https://DomainName/api/v1/loanproducts/5/productmix
</code>
<code class="method-response">
{
"restrictedProducts": [
{
"id": 1,
"name": "Personal Loan",
"includeInBorrowerCycle": false
}
],
"allowedProducts": [
{
"id": 3,
"name": "Daily Loan",
"includeInBorrowerCycle": false
},
{
"id": 2,
"name": "Joint Loan",
"includeInBorrowerCycle": false
},
{
"id": 6,
"name": "Personal Loan -2",
"includeInBorrowerCycle": false
},
{
"id": 5,
"name": "Primary Loan",
"includeInBorrowerCycle": false
},
{
"id": 4,
"name": "Weekly Loan",
"includeInBorrowerCycle": false
}
]
}
</code>
</div>
</div>
<a id="loanproductmix_retrieve" name="loanproductmix_retrieve"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Retrieve Loan Product Mix</h4>
<p>Example Requests:</p>
<div class=apiClick>loanproducts/5/productmix</div>
</div>
<div class="method-example">
<code class="method-declaration">
GET https://DomainName/api/v1/loanproducts/5/productmix
</code>
<code class="method-response">
{
"restrictedProducts": [
{
"id": 1,
"name": "Personal Loan",
"includeInBorrowerCycle": false
}
],
"allowedProducts": [
{
"id": 3,
"name": "Daily Loan",
"includeInBorrowerCycle": false
},
{
"id": 2,
"name": "Joint Loan",
"includeInBorrowerCycle": false
},
{
"id": 6,
"name": "Personal Loan -2",
"includeInBorrowerCycle": false
},
{
"id": 5,
"name": "Primary Loan",
"includeInBorrowerCycle": false
},
{
"id": 4,
"name": "Weekly Loan",
"includeInBorrowerCycle": false
}
]
}
</code>
</div>
</div>
<a id="loanproductmix_create" name="loanproductmix_create"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Create a Loan Product Mix</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>restrictedProducts</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loanproducts/5/productmix
</code>
<code class="method-request">
POST loanproducts/{productId}/productmix
Content-Type: application/json
Request Body:
{"restrictedProducts":["1"]}
</code>
<code class="method-response">
{
"changes":{
"restrictedProductsForMix":[1],
"removedProductsForMix":[]
},
"productId":5
}
</code>
</div>
</div>
<a id="loanproductmix_update" name="loanproductmix_update"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Update a Loan Product Mix</h4>
</div>
<div class="method-example">
<code class="method-declaration">
PUT https://DomainName/api/v1/loanproducts/{productId}/productmix
</code>
<code class="method-request">
PUT loanproducts/5/productmix
Content-Type: application/json
Request Body:
{
"restrictedProducts":["3"]
}
</code>
<code class="method-response">
{
"changes": {
"restrictedProductsForMix": [3],
"removedProductsForMix": [1]
},
"productId": 5
}
</code>
</div>
</div>
<a id="loanproductmix_delete" name="loanproductmix_delete"
class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Delete a Loan Product Mix</h4>
</div>
<div class="method-example">
<code class="method-declaration">
DELETE https://DomainName/api/v1/loanproducts/{productId}/productmix
</code>
<code class="method-request">
DELETE loanproducts/5/productmix
</code>
<code class="method-response">
{
"changes": {
"removedProductsForMix": [3]
},
"productId": 5
}
</code>
</div>
</div>
<!-- end of loan products mix api docs -->
<!-- start of configurations api docs -->
<a id="configs" name="configs" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
@@ -16390,6 +16759,30 @@ GET https://DomainName/api/v1/search?query=000000111
<td><a href="#loanproducts_update">Update a Loan Product</a></td>
<td></td>
</tr>
<tr class="alt">
<td><a href="#loanproductmix">Loan Product Mix</a></td>
<td>loanproducts/template?isProductMixTemplate=true</td>
<td></td>
<td><a href="#loanproductmix_template">Retrieve Loan Product Mix Template</a></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>loanproducts?associations=productMixes</td>
<td></td>
<td><a href="#loanproductmix_list">List Loan Products Mix</a></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>loanproducts/{productId}/productmix</td>
<td><a href="#loanproductmix_create">Create a Loan Product Mix</a></td>
<td><a href="#loanproductmix_retrieve">Retrieve Loan Products Mix</a></td>
<td><a href="#loanproductmix_update">Update Loan Product Mix</a></td>
<td><a href="#loanproductmix_delete">Delete Loan Products Mix</a></td>
</tr>
<tr>
<td><a href="#loans">Loans</a></td>
<td>loans?calculateLoanSchedule</td>
@@ -0,0 +1,19 @@
ALTER TABLE `m_portfolio_command_source`
ADD COLUMN `product_id` BIGINT NULL DEFAULT NULL AFTER `processing_result_enum`;
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATE_PRODUCTMIX', 'PRODUCTMIX', 'CREATE', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'UPDATE_PRODUCTMIX', 'PRODUCTMIX', 'UPDATE', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DELETE_PRODUCTMIX', 'PRODUCTMIX', 'DELETE', 0);
CREATE TABLE `m_product_mix` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`product_id` BIGINT(20) NOT NULL,
`restricted_product_id` BIGINT(20) NOT NULL,
PRIMARY KEY (`id`),
INDEX `FK_m_product_mix_product_id_to_m_product_loan` (`product_id`),
INDEX `FK_m_product_mix_restricted_product_id_to_m_product_loan` (`restricted_product_id`),
CONSTRAINT `FK_m_product_mix_restricted_product_id_to_m_product_loan` FOREIGN KEY (`restricted_product_id`) REFERENCES `m_product_loan` (`id`),
CONSTRAINT `FK_m_product_mix_product_id_to_m_product_loan` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB;
@@ -85,6 +85,9 @@ public class CommandSource extends AbstractPersistable<Long> {
@Column(name = "processing_result_enum", nullable = false)
private Integer processingResult;
@Column(name = "product_id")
private Long productId;
public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) {
return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(),
@@ -172,15 +175,21 @@ public class CommandSource extends AbstractPersistable<Long> {
return false;
}
public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId) {
public void updateForAudit(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId,
final Long productId) {
this.officeId = officeId;
this.groupId = groupId;
this.clientId = clientId;
this.loanId = loanId;
this.savingsId = savingsId;
this.productId = productId;
}
public String getResourceGetUrl() {
return this.resourceGetUrl;
}
public Long getProductId() {
return this.productId;
}
}
@@ -25,18 +25,19 @@ public class CommandWrapper {
private final String transactionId;
private final String supportedEntityType;
private final Long supportedEntityId;
private final Long productId;
public static CommandWrapper wrap(final String actionName, final String entityName, final Long resourceId, final Long subresourceId) {
return new CommandWrapper(null, actionName, entityName, resourceId, subresourceId, null);
return new CommandWrapper(null, actionName, entityName, resourceId, subresourceId, null, null);
}
public static CommandWrapper fromExistingCommand(final Long commandId, final String actionName, final String entityName,
final Long resourceId, final Long subresourceId, final String resourceGetUrl) {
return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl);
final Long resourceId, final Long subresourceId, final String resourceGetUrl, final Long productId) {
return new CommandWrapper(commandId, actionName, entityName, resourceId, subresourceId, resourceGetUrl, productId);
}
private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId,
final Long subresourceId, final String resourceGetUrl) {
final Long subresourceId, final String resourceGetUrl, final Long productId) {
this.commandId = commandId;
this.officeId = null;
this.groupId = null;
@@ -54,12 +55,14 @@ public class CommandWrapper {
this.href = resourceGetUrl;
this.json = null;
this.transactionId = null;
this.productId = productId;
}
public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId,
final String actionName, final String entityName, final Long entityId, final Long subentityId, final Long codeId,
final String supportedEntityType, final Long supportedEntityId, final String href, final String json, final String transactionId) {
final String supportedEntityType, final Long supportedEntityId, final String href, final String json,
final String transactionId, final Long productId) {
this.commandId = null;
this.officeId = officeId;
this.groupId = groupId;
@@ -77,7 +80,7 @@ public class CommandWrapper {
this.href = href;
this.json = json;
this.transactionId = transactionId;
this.productId = productId;
}
public Long commandId() {
@@ -176,6 +179,10 @@ public class CommandWrapper {
return this.supportedEntityType;
}
public Long getProductId() {
return this.productId;
}
public boolean isUpdate() {
// permissions resource has special update which involves no resource.
return (isPermissionResource() && isUpdateOperation()) || (isCurrencyResource() && isUpdateOperation())
@@ -190,7 +197,7 @@ public class CommandWrapper {
return isDeleteOperation() && this.entityId != null;
}
private boolean isDeleteOperation() {
public boolean isDeleteOperation() {
return this.actionName.equalsIgnoreCase("DELETE");
}
@@ -540,4 +547,8 @@ public class CommandWrapper {
public boolean isClientClose() {
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT");
}
public boolean isProductMixResource() {
return this.entityName.equalsIgnoreCase("PRODUCTMIX");
}
}
@@ -24,11 +24,12 @@ public class CommandWrapperBuilder {
private String transactionId;
private String supportedEntityType;
private Long supportedEntityId;
private Long productId;
public CommandWrapper build() {
return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName,
this.entityName, this.entityId, this.subentityId, this.codeId, this.supportedEntityType, this.supportedEntityId, this.href,
this.json, this.transactionId);
this.json, this.transactionId, this.productId);
}
public CommandWrapperBuilder withLoanId(final Long withLoanId) {
@@ -1127,4 +1128,36 @@ public class CommandWrapperBuilder {
this.href = "/clients/" + clientId + "?command=assignStaff";
return this;
}
public CommandWrapperBuilder createProductMix(final Long productId) {
this.actionName = "CREATE";
this.entityName = "PRODUCTMIX";
this.entityId = null;
this.productId = productId;
this.href = "/loanproducts/" + productId + "/productmix";
return this;
}
public CommandWrapperBuilder updateProductMix(final Long productId) {
this.actionName = "UPDATE";
this.entityName = "PRODUCTMIX";
this.entityId = null;
this.productId = productId;
this.href = "/loanproducts/" + productId + "/productmix";
return this;
}
public CommandWrapperBuilder deleteProductMix(final Long productId) {
this.actionName = "DELETE";
this.entityName = "PRODUCTMIX";
this.entityId = null;
this.productId = productId;
this.href = "/loanproducts/" + productId + "/productmix";
return this;
}
public CommandWrapperBuilder withProduct(final Long productId) {
this.productId = productId;
return this;
}
}
@@ -63,7 +63,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
final JsonCommand command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(),
wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(),
wrapper.getSavingsId(), wrapper.getCodeId(), wrapper.getSupportedEntityType(), wrapper.getSupportedEntityId(),
wrapper.getTransactionId(), wrapper.getHref());
wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId());
result = this.processAndLogCommandService.processAndLogCommand(wrapper, command, isApprovedByChecker);
} catch (RollbackTransactionAsCommandIsNotApprovedByCheckerException e) {
@@ -81,12 +81,12 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio
final CommandWrapper wrapper = CommandWrapper.fromExistingCommand(makerCheckerId, commandSourceInput.getActionName(),
commandSourceInput.getEntityName(), commandSourceInput.resourceId(), commandSourceInput.subresourceId(),
commandSourceInput.getResourceGetUrl());
commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId());
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(commandSourceInput.json());
final JsonCommand command = JsonCommand.fromExistingCommand(makerCheckerId, commandSourceInput.json(), parsedCommand,
this.fromApiJsonHelper, commandSourceInput.getEntityName(), commandSourceInput.resourceId(),
commandSourceInput.subresourceId(), commandSourceInput.getResourceGetUrl());
commandSourceInput.subresourceId(), commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId());
final boolean makerCheckerApproval = true;
return this.processAndLogCommandService.processAndLogCommand(wrapper, command, makerCheckerApproval);
@@ -70,7 +70,7 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
}
commandSourceResult.updateResourceId(result.resourceId());
commandSourceResult.updateForAudit(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(),
result.getSavingsId());
result.getSavingsId(), result.getProductId());
String changesOnlyJson = null;
if (result.hasChanges()) {
@@ -493,6 +493,16 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createHolidayCommandHandler", NewCommandSourceHandler.class);
}
} else if (wrapper.isProductMixResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createProductMixCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateOperation()) {
handler = this.applicationContext.getBean("updateProductMixCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDeleteOperation()) {
handler = this.applicationContext.getBean("deleteProductMixCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
@@ -46,26 +46,27 @@ public final class JsonCommand {
private final Long supportedEntityId;
private final String transactionId;
private final String url;
private final Long productId;
public static JsonCommand from(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper,
final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId,
final Long loanId, final Long savingsId, final Long codeId, final String supportedEntityType, final Long supportedEntityId,
final String transactionId, final String url) {
final String transactionId, final String url, final Long productId) {
return new JsonCommand(null, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId,
clientId, loanId, savingsId, codeId, supportedEntityType, supportedEntityId, transactionId, url);
clientId, loanId, savingsId, codeId, supportedEntityType, supportedEntityId, transactionId, url, productId);
}
public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
final String url) {
final String url, final Long productId) {
return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, null, null,
null, null, null, null, null, null, url);
null, null, null, null, null, null, url, productId);
}
public JsonCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand,
final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId,
final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final Long codeId,
final String supportedEntityType, final Long supportedEntityId, final String transactionId, final String url) {
final String supportedEntityType, final Long supportedEntityId, final String transactionId, final String url, final Long productId) {
this.commandId = commandId;
this.jsonCommand = jsonCommand;
this.parsedCommand = parsedCommand;
@@ -82,6 +83,7 @@ public final class JsonCommand {
this.supportedEntityId = supportedEntityId;
this.transactionId = transactionId;
this.url = url;
this.productId = productId;
}
public String json() {
@@ -153,6 +155,10 @@ public final class JsonCommand {
return this.url;
}
public Long getProductId() {
return this.productId;
}
private boolean differenceExists(final LocalDate baseValue, final LocalDate workingCopyValue) {
boolean differenceExists = false;
@@ -25,12 +25,13 @@ public class CommandProcessingResult {
private final Map<String, Object> changes;
@SuppressWarnings("unused")
private final String resourceIdentifier;
private final Long productId;
public static CommandProcessingResult fromDetails(final Long commandId, final Long officeId, final Long groupId, final Long clientId,
final Long loanId, final Long savingsId, final String resourceIdentifier, final Long entityId, final String transactionId,
final Map<String, Object> changes) {
final Map<String, Object> changes, final Long productId) {
return new CommandProcessingResult(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId,
transactionId, changes);
transactionId, changes, productId);
}
public static CommandProcessingResult commandOnlyResult(final Long commandId) {
@@ -79,11 +80,12 @@ public class CommandProcessingResult {
this.savingsId = null;
this.transactionId = null;
this.changes = new HashMap<String, Object>();
this.productId = null;
}
private CommandProcessingResult(final Long commandId, final Long officeId, final Long groupId, final Long clientId, final Long loanId,
final Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId,
final Map<String, Object> changesOnly) {
final Map<String, Object> changesOnly, final Long productId) {
this.commandId = commandId;
this.officeId = officeId;
this.groupId = groupId;
@@ -94,6 +96,7 @@ public class CommandProcessingResult {
this.resourceId = resourceId;
this.changes = changesOnly;
this.transactionId = transactionId;
this.productId = productId;
}
private CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map<String, Object> changesOnly) {
@@ -111,6 +114,7 @@ public class CommandProcessingResult {
this.transactionId = null;
this.commandId = commandId;
this.changes = changesOnly;
this.productId = null;
}
public Long commandId() {
@@ -161,4 +165,8 @@ public class CommandProcessingResult {
final boolean noChanges = this.changes == null || this.changes.isEmpty();
return !noChanges;
}
public Long getProductId() {
return this.productId;
}
}
@@ -23,10 +23,11 @@ public class CommandProcessingResultBuilder {
private Long entityId;
private String transactionId;
private Map<String, Object> changes;
private Long productId;
public CommandProcessingResult build() {
return CommandProcessingResult.fromDetails(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId,
transactionId, changes);
transactionId, changes, productId);
}
public CommandProcessingResultBuilder withCommandId(final Long withCommandId) {
@@ -78,5 +79,10 @@ public class CommandProcessingResultBuilder {
this.transactionId = withTransactionId;
return this;
}
public CommandProcessingResultBuilder withProductId(final Long productId) {
this.productId = productId;
return this;
}
}
@@ -19,6 +19,7 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
@@ -33,6 +34,7 @@ import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.codes.service.CodeValueReadPlatformService;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
@@ -47,6 +49,8 @@ import org.mifosplatform.portfolio.fund.data.FundData;
import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.mifosplatform.portfolio.loanproduct.productmix.data.ProductMixData;
import org.mifosplatform.portfolio.loanproduct.productmix.service.ProductMixReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants;
@@ -69,6 +73,9 @@ public class LoanProductsApiResource {
"currencyOptions", "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "amortizationTypeOptions",
"interestTypeOptions", "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions",
"accountingOptions", "accountingRuleOptions", "accountingMappingOptions"));
private final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("productId", "productName",
"restrictedProducts", "allowedProducts", "productOptions"));
private final String resourceNameForPermissions = "LOANPRODUCT";
@@ -84,6 +91,8 @@ public class LoanProductsApiResource {
private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
private final DefaultToApiJsonSerializer<ProductMixData> productMixDataApiJsonSerializer;
private final ProductMixReadPlatformService productMixReadPlatformService;
@Autowired
public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService,
@@ -94,7 +103,9 @@ public class LoanProductsApiResource {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService,
final CodeValueReadPlatformService codeValueReadPlatformService,
final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService) {
final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService,
final DefaultToApiJsonSerializer<ProductMixData> productMixDataApiJsonSerializer,
final ProductMixReadPlatformService productMixReadPlatformService) {
this.context = context;
this.loanProductReadPlatformService = readPlatformService;
this.chargeReadPlatformService = chargeReadPlatformService;
@@ -107,6 +118,8 @@ public class LoanProductsApiResource {
this.accountMappingReadPlatformService = accountMappingReadPlatformService;
this.codeValueReadPlatformService = codeValueReadPlatformService;
this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
this.productMixDataApiJsonSerializer = productMixDataApiJsonSerializer;
this.productMixReadPlatformService = productMixReadPlatformService;
}
@POST
@@ -127,10 +140,19 @@ public class LoanProductsApiResource {
public String retrieveAllLoanProducts(@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions);
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("productMixes")) {
context.authenticatedUser().validateHasReadPermission("PRODUCTMIX");
Collection<ProductMixData> productMixes = this.productMixReadPlatformService.retrieveAllProductMixes();
return this.productMixDataApiJsonSerializer.serialize(settings, productMixes, PRODUCT_MIX_DATA_PARAMETERS);
}
}
final Collection<LoanProductData> products = this.loanProductReadPlatformService.retrieveAllLoanProducts();
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, products, LOAN_PRODUCT_DATA_PARAMETERS);
}
@@ -138,14 +160,22 @@ public class LoanProductsApiResource {
@Path("template")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTemplate(@Context final UriInfo uriInfo) {
public String retrieveTemplate(@Context final UriInfo uriInfo, @QueryParam("isProductMixTemplate") final boolean isProductMixTemplate) {
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (isProductMixTemplate) {
context.authenticatedUser().validateHasReadPermission("PRODUCTMIX");
Collection<LoanProductData> productOptions = this.loanProductReadPlatformService.retrieveAvailableLoanProductsForMix();
ProductMixData productMixData = ProductMixData.template(productOptions);
return this.productMixDataApiJsonSerializer.serialize(settings, productMixData, PRODUCT_MIX_DATA_PARAMETERS);
}
LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveNewLoanProductDetails();
loanProduct = handleTemplate(loanProduct);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanProduct, LOAN_PRODUCT_DATA_PARAMETERS);
}
@@ -237,4 +267,5 @@ public class LoanProductsApiResource {
interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions,
accountingRuleTypeOptions);
}
}
@@ -0,0 +1,129 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanproduct.productmix.api;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.productmix.data.ProductMixData;
import org.mifosplatform.portfolio.loanproduct.productmix.service.ProductMixReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/loanproducts/{productId}/productmix")
@Component
@Scope("singleton")
public class ProductMixApiResource {
private final String resourceNameForPermissions = "PRODUCTMIX";
private final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("productId", "productName",
"restrictedProducts", "allowedProducts", "productOptions"));
private final PlatformSecurityContext context;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final DefaultToApiJsonSerializer<ProductMixData> toApiJsonSerializer;
private final ProductMixReadPlatformService productMixReadPlatformService;
private final LoanProductReadPlatformService loanProductReadPlatformService;
@Autowired
public ProductMixApiResource(final PlatformSecurityContext context,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final ApiRequestParameterHelper apiRequestParameterHelper,
final DefaultToApiJsonSerializer<ProductMixData> toApiJsonSerializer,
final ProductMixReadPlatformService productMixReadPlatformService,
final LoanProductReadPlatformService loanProductReadPlatformService) {
this.context = context;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.toApiJsonSerializer = toApiJsonSerializer;
this.productMixReadPlatformService = productMixReadPlatformService;
this.loanProductReadPlatformService = loanProductReadPlatformService;
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveTemplate(@PathParam("productId") final Long productId, @Context final UriInfo uriInfo) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
ProductMixData productMixData = this.productMixReadPlatformService.retrieveLoanProductMixDetails(productId);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (settings.isTemplate()) {
final Collection<LoanProductData> productOptions = this.loanProductReadPlatformService.retrieveAvailableLoanProductsForMix();
productMixData = ProductMixData.withTemplateOptions(productMixData, productOptions);
}
return this.toApiJsonSerializer.serialize(settings, productMixData, this.PRODUCT_MIX_DATA_PARAMETERS);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String createProductMix(@PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createProductMix(productId).withJson(apiRequestBodyAsJson)
.build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@PUT
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String updateProductMix(@PathParam("productId") final Long productId, final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateProductMix(productId).withJson(apiRequestBodyAsJson)
.build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
@DELETE
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String deleteProductMix(@PathParam("productId") final Long productId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteProductMix(productId).build();
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
}
@@ -0,0 +1,44 @@
package org.mifosplatform.portfolio.loanproduct.productmix.data;
import java.util.Collection;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
public class ProductMixData {
private final Long productId;
private final String productName;
private final Collection<LoanProductData> restrictedProducts;
private final Collection<LoanProductData> allowedProducts;
@SuppressWarnings("unused")
private final Collection<LoanProductData> productOptions;
public ProductMixData(final Long productId, final String productName, final Collection<LoanProductData> restrictedProducts,
final Collection<LoanProductData> allowedProducts, final Collection<LoanProductData> productOptions) {
this.productId = productId;
this.productName = productName;
this.restrictedProducts = restrictedProducts;
this.allowedProducts = allowedProducts;
this.productOptions = productOptions;
}
public static ProductMixData template(final Collection<LoanProductData> productOptions) {
return new ProductMixData(null, null, null, null, productOptions);
}
public static ProductMixData withTemplateOptions(final ProductMixData productMixData, final Collection<LoanProductData> productOptions) {
return new ProductMixData(productMixData.productId, productMixData.productName, productMixData.restrictedProducts,
productMixData.allowedProducts, productOptions);
}
public static ProductMixData withDetails(Long productId, String productName, Collection<LoanProductData> restrictedProducts,
Collection<LoanProductData> allowedProducts) {
return new ProductMixData(productId, productName, restrictedProducts, allowedProducts, null);
}
public static ProductMixData withRestrictedOptions(Collection<LoanProductData> restrictedProducts,
Collection<LoanProductData> allowedProducts) {
return new ProductMixData(null, null, restrictedProducts, allowedProducts, null);
}
}
@@ -0,0 +1,44 @@
package org.mifosplatform.portfolio.loanproduct.productmix.domain;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_product_mix")
public class ProductMix extends AbstractPersistable<Long> {
@ManyToOne
@JoinColumn(name = "product_id", nullable = false)
private LoanProduct product;
@ManyToOne
@JoinColumn(name = "restricted_product_id", nullable = false)
private LoanProduct restrictedProduct;
public ProductMix() {
//
}
private ProductMix(final LoanProduct product, final LoanProduct restrictedProduct) {
this.product = product;
this.restrictedProduct = restrictedProduct;
}
public static ProductMix createNew(final LoanProduct product, final LoanProduct restrictedProduct) {
return new ProductMix(product, restrictedProduct);
}
public Long getRestrictedProductId() {
return this.restrictedProduct.getId();
}
public Long getProductId() {
return this.product.getId();
}
}
@@ -0,0 +1,25 @@
package org.mifosplatform.portfolio.loanproduct.productmix.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ProductMixRepository extends JpaRepository<ProductMix, Long>, JpaSpecificationExecutor<ProductMix> {
public static final String GET_PRODUCTMIXES_BY_PRODUCTID_SCHEMA = "from ProductMix pm where pm.product.id = :productId";
public static final String GET_RESTRICTED_PRODUCTIDS_SCHEMA = "Select pm.restrictedProduct.id from ProductMix pm where pm.product.id = :productId";
public static final String GET_RESTRICTED_PRODUCTS_SCHEMA = "from ProductMix pm where pm.restrictedProduct.id = :restrictedProductId";
@Query(GET_PRODUCTMIXES_BY_PRODUCTID_SCHEMA)
List<ProductMix> findByProductId(@Param("productId") Long productId);
@Query(GET_RESTRICTED_PRODUCTIDS_SCHEMA)
List<Long> findRestrictedProductIdsByProductId(@Param("productId") Long productId);
@Query(GET_RESTRICTED_PRODUCTS_SCHEMA)
List<ProductMix> findRestrictedProducts(@Param("restrictedProductId") Long restrictedProductId);
}
@@ -0,0 +1,15 @@
package org.mifosplatform.portfolio.loanproduct.productmix.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when no product mixes found with the
* productId.
*/
public class ProductMixNotFoundException extends AbstractPlatformResourceNotFoundException {
public ProductMixNotFoundException(final Long productId) {
super("error.msg.no.product.mixes.exists", "No product mixes are defined with the productId `" + productId + "`.", productId);
}
}
@@ -0,0 +1,29 @@
package org.mifosplatform.portfolio.loanproduct.productmix.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CreateProductMixCommandHandler implements NewCommandSourceHandler {
private final ProductMixWritePlatformService productMixWritePlatformService;
@Autowired
public CreateProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) {
this.productMixWritePlatformService = productMixWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.productMixWritePlatformService.createProductMix(command.getProductId(), command);
}
}
@@ -0,0 +1,29 @@
package org.mifosplatform.portfolio.loanproduct.productmix.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DeleteProductMixCommandHandler implements NewCommandSourceHandler {
private final ProductMixWritePlatformService productMixWritePlatformService;
@Autowired
public DeleteProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) {
this.productMixWritePlatformService = productMixWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.productMixWritePlatformService.deleteProductMix(command.getProductId());
}
}
@@ -0,0 +1,29 @@
package org.mifosplatform.portfolio.loanproduct.productmix.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanproduct.productmix.service.ProductMixWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UpdateProductMixCommandHandler implements NewCommandSourceHandler {
private final ProductMixWritePlatformService productMixWritePlatformService;
@Autowired
public UpdateProductMixCommandHandler(final ProductMixWritePlatformService productMixWritePlatformService) {
this.productMixWritePlatformService = productMixWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.productMixWritePlatformService.updateProductMix(command.getProductId(), command);
}
}
@@ -0,0 +1,87 @@
package org.mifosplatform.portfolio.loanproduct.productmix.serialization;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
@Component
public final class ProductMixDataValidator {
/**
* The parameters supported for this command.
*/
private final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("restrictedProducts"));
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public ProductMixDataValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateForCreate(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("productmix");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String[] restrictedProducts = this.fromApiJsonHelper.extractArrayNamed("restrictedProducts", element);
baseDataValidator.reset().parameter("restrictedProducts").value(restrictedProducts).arrayNotEmpty();
if (restrictedProducts != null) {
validateRestrictedProducts(restrictedProducts, baseDataValidator);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateRestrictedProducts(final String[] restrictedProducts, final DataValidatorBuilder baseDataValidator) {
for (final String restrictedId : restrictedProducts) {
baseDataValidator.reset().parameter("restrictedProduct").value(restrictedId).notBlank().longGreaterThanZero();
}
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
public void validateForUpdate(String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("productmix");
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String[] restrictedProducts = this.fromApiJsonHelper.extractArrayNamed("restrictedProducts", element);
validateRestrictedProducts(restrictedProducts, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
}
@@ -0,0 +1,13 @@
package org.mifosplatform.portfolio.loanproduct.productmix.service;
import java.util.Collection;
import org.mifosplatform.portfolio.loanproduct.productmix.data.ProductMixData;
public interface ProductMixReadPlatformService {
ProductMixData retrieveLoanProductMixDetails(Long productId);
Collection<ProductMixData> retrieveAllProductMixes();
}
@@ -0,0 +1,109 @@
package org.mifosplatform.portfolio.loanproduct.productmix.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.productmix.data.ProductMixData;
import org.mifosplatform.portfolio.loanproduct.productmix.exception.ProductMixNotFoundException;
import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.stereotype.Service;
@Service
public class ProductMixReadPlatformServiceImpl implements ProductMixReadPlatformService {
private final PlatformSecurityContext context;
private final JdbcTemplate jdbcTemplate;
private final LoanProductReadPlatformService loanProductReadPlatformService;
@Autowired
public ProductMixReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
final LoanProductReadPlatformService loanProductReadPlatformService) {
this.context = context;
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.loanProductReadPlatformService = loanProductReadPlatformService;
}
@Override
public ProductMixData retrieveLoanProductMixDetails(final Long productId) {
try {
this.context.authenticatedUser();
final ProductMixDataExtractor extractor = new ProductMixDataExtractor(this.loanProductReadPlatformService, productId);
final String sql = "Select " + extractor.schema() + " where pm.product_id=? group by pm.product_id";
final Map<Long, ProductMixData> productMixData = this.jdbcTemplate.query(sql, extractor, new Object[] { productId });
return productMixData.get(productId);
} catch (final EmptyResultDataAccessException e) {
throw new ProductMixNotFoundException(productId);
}
}
@Override
public Collection<ProductMixData> retrieveAllProductMixes() {
this.context.authenticatedUser();
final ProductMixDataExtractor extractor = new ProductMixDataExtractor(this.loanProductReadPlatformService, null);
final String sql = "Select " + extractor.schema() + " group by pm.product_id";
final Map<Long, ProductMixData> productMixData = this.jdbcTemplate.query(sql, extractor, new Object[] {});
return productMixData.values();
}
private static final class ProductMixDataExtractor implements ResultSetExtractor<Map<Long, ProductMixData>> {
private final LoanProductReadPlatformService loanProductReadPlatformService;
private final Long productId;
public String schema() {
return "pm.product_id as productId, lp.name as name from m_product_mix pm join m_product_loan lp on lp.id=pm.product_id";
}
public ProductMixDataExtractor(final LoanProductReadPlatformService loanProductReadPlatformService, final Long productId) {
this.loanProductReadPlatformService = loanProductReadPlatformService;
this.productId = productId;
}
@Override
public Map<Long, ProductMixData> extractData(final ResultSet rs) throws SQLException, DataAccessException {
final Map<Long, ProductMixData> extractedData = new HashMap<Long, ProductMixData>();
if (!rs.next()) {
final Collection<LoanProductData> restrictedProducts = this.loanProductReadPlatformService.retrieveRestrictedProductsForMix(this.productId);
final Collection<LoanProductData> allowedProducts = this.loanProductReadPlatformService.retrieveAllowedProductsForMix(this.productId);
ProductMixData productMixData = ProductMixData.withRestrictedOptions(restrictedProducts, allowedProducts);
extractedData.put(this.productId, productMixData);
return extractedData;
}
/* move the cursor to starting of resultset */
rs.beforeFirst();
while (rs.next()) {
final Long productId = rs.getLong("productId");
final String name = rs.getString("name");
final Collection<LoanProductData> restrictedProducts = this.loanProductReadPlatformService.retrieveRestrictedProductsForMix(productId);
final Collection<LoanProductData> allowedProducts = this.loanProductReadPlatformService.retrieveAllowedProductsForMix(productId);
final ProductMixData productMixData = ProductMixData.withDetails(productId, name, restrictedProducts, allowedProducts);
extractedData.put(productId, productMixData);
}
return extractedData;
}
}
}
@@ -0,0 +1,14 @@
package org.mifosplatform.portfolio.loanproduct.productmix.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface ProductMixWritePlatformService {
CommandProcessingResult createProductMix(Long productId, JsonCommand command);
CommandProcessingResult updateProductMix(Long productId, JsonCommand command);
CommandProcessingResult deleteProductMix(Long productId);
}
@@ -0,0 +1,225 @@
package org.mifosplatform.portfolio.loanproduct.productmix.service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.mifosplatform.portfolio.loanproduct.productmix.domain.ProductMix;
import org.mifosplatform.portfolio.loanproduct.productmix.domain.ProductMixRepository;
import org.mifosplatform.portfolio.loanproduct.productmix.exception.ProductMixNotFoundException;
import org.mifosplatform.portfolio.loanproduct.productmix.serialization.ProductMixDataValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@Service
public class ProductMixWritePlatformServiceJpaRepositoryImpl implements ProductMixWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(ProductMixWritePlatformServiceJpaRepositoryImpl.class);
private final PlatformSecurityContext context;
private final ProductMixDataValidator fromApiJsonDeserializer;
private final ProductMixRepository productMixRepository;
private final LoanProductRepository productRepository;
@Autowired
public ProductMixWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final ProductMixDataValidator fromApiJsonDeserializer, final ProductMixRepository productMixRepository,
final LoanProductRepository productRepository) {
this.context = context;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.productMixRepository = productMixRepository;
this.productRepository = productRepository;
}
@Transactional
@Override
public CommandProcessingResult createProductMix(final Long productId, final JsonCommand command) {
try {
this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForCreate(command.json());
final Set<String> restrictedIds = new HashSet<String>(Arrays.asList(command.arrayValueOfParameterNamed("restrictedProducts")));
// remove the existed restriction if it is not exists in restrictedIds.
final List<Long> removedRestrictions = updateRestrictionsForProduct(productId, restrictedIds);
final Map<Long, LoanProduct> restrictedProductsAsMap = getRestrictedProducts(restrictedIds);
final List<ProductMix> productMixes = new ArrayList<ProductMix>();
createNewProductMix(restrictedProductsAsMap, productId, productMixes);
this.productMixRepository.save(productMixes);
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("restrictedProductsForMix", restrictedProductsAsMap.keySet());
changes.put("removedProductsForMix", removedRestrictions);
return new CommandProcessingResultBuilder().withProductId(productId).with(changes).withCommandId(command.commandId()).build();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(dve);
return CommandProcessingResult.empty();
}
}
private List<Long> updateRestrictionsForProduct(final Long productId, final Set<String> restrictedIds) {
final List<Long> removedRestrictions = new ArrayList<Long>();
final List<ProductMix> mixesToRemove = new ArrayList<ProductMix>();
final List<ProductMix> existedProductMixes = this.productMixRepository.findRestrictedProducts(productId);
for (final ProductMix productMix : existedProductMixes) {
if (!restrictedIds.contains(productMix.getProductId().toString())) {
mixesToRemove.add(productMix);
removedRestrictions.add(productMix.getId());
}
}
if (!CollectionUtils.isEmpty(mixesToRemove)) {
this.productMixRepository.delete(mixesToRemove);
}
return removedRestrictions;
}
private void createNewProductMix(final Map<Long, LoanProduct> restrictedProductsAsMap, final Long productId,
final List<ProductMix> productMixes) {
final LoanProduct productMixInstance = findByProductIdIfProvided(productId);
for (final LoanProduct restrictedProduct : restrictedProductsAsMap.values()) {
final ProductMix productMix = ProductMix.createNew(productMixInstance, restrictedProduct);
productMixes.add(productMix);
}
}
@Override
public CommandProcessingResult updateProductMix(final Long productId, final JsonCommand command) {
try {
this.context.authenticatedUser();
this.fromApiJsonDeserializer.validateForUpdate(command.json());
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
final List<ProductMix> existedProductMixes = this.productMixRepository.findByProductId(productId);
if (CollectionUtils.isEmpty(existedProductMixes)) { throw new ProductMixNotFoundException(productId); }
final Set<String> restrictedIds = new HashSet<String>(Arrays.asList(command.arrayValueOfParameterNamed("restrictedProducts")));
// updating with empty array means deleting the existed records.
if (restrictedIds.isEmpty()) {
final List<Long> removedRestrictedProductIds = this.productMixRepository.findRestrictedProductIdsByProductId(productId);
this.productMixRepository.delete(existedProductMixes);
changes.put("removedProductsForMix", removedRestrictedProductIds);
return new CommandProcessingResultBuilder().with(changes).withProductId(productId).withCommandId(command.commandId())
.build();
}
/*
* if restrictedProducts array is not empty delete the duplicate ids
* which are already exists and update existedProductMixes
*/
final List<ProductMix> productMixesToRemove = updateRestrictedIds(restrictedIds, existedProductMixes);
final Map<Long, LoanProduct> restrictedProductsAsMap = getRestrictedProducts(restrictedIds);
createNewProductMix(restrictedProductsAsMap, productId, existedProductMixes);
this.productMixRepository.save(existedProductMixes);
changes.put("restrictedProductsForMix", getProductIdsFromCollection(existedProductMixes));
if (!CollectionUtils.isEmpty(productMixesToRemove)) {
this.productMixRepository.delete(productMixesToRemove);
changes.put("removedProductsForMix", getProductIdsFromCollection(productMixesToRemove));
}
return new CommandProcessingResultBuilder().with(changes).withProductId(productId).withCommandId(command.commandId()).build();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(dve);
return CommandProcessingResult.empty();
}
}
private LoanProduct findByProductIdIfProvided(final Long productId) {
final LoanProduct product = this.productRepository.findOne(productId);
if (product == null) { throw new LoanProductNotFoundException(productId); }
return product;
}
private Map<Long, LoanProduct> getRestrictedProducts(final Set<String> restrictedIds) {
final Map<Long, LoanProduct> restricrtedProducts = new HashMap<Long, LoanProduct>();
for (final String restrictedId : restrictedIds) {
final Long restrictedIdAsLong = Long.valueOf(restrictedId);
final LoanProduct restrictedProduct = findByProductIdIfProvided(Long.valueOf(restrictedId));
restricrtedProducts.put(restrictedIdAsLong, restrictedProduct);
}
return restricrtedProducts;
}
private void handleDataIntegrityIssues(final DataIntegrityViolationException dve) {
logAsErrorUnexpectedDataIntegrityException(dve);
throw new PlatformDataIntegrityException("error.msg.product.loan.unknown.data.integrity.issue",
"Unknown data integrity issue with resource.");
}
private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
logger.error(dve.getMessage(), dve);
}
private List<ProductMix> updateRestrictedIds(final Set<String> restrictedIds, final List<ProductMix> existedProductMixes) {
final List<ProductMix> productMixesToRemove = new ArrayList<ProductMix>();
for (final ProductMix productMix : existedProductMixes) {
final String currentMixId = productMix.getRestrictedProductId().toString();
if (restrictedIds.contains(currentMixId)) {
restrictedIds.remove(currentMixId);
} else {
productMixesToRemove.add(productMix);
}
}
existedProductMixes.removeAll(productMixesToRemove);
return productMixesToRemove;
}
@Override
public CommandProcessingResult deleteProductMix(final Long productId) {
try {
this.context.authenticatedUser();
final Map<String, Object> changes = new LinkedHashMap<String, Object>();
final List<ProductMix> existedProductMixes = this.productMixRepository.findByProductId(productId);
if (CollectionUtils.isEmpty(existedProductMixes)) { throw new ProductMixNotFoundException(productId); }
this.productMixRepository.delete(existedProductMixes);
changes.put("removedProductsForMix", getProductIdsFromCollection(existedProductMixes));
return new CommandProcessingResultBuilder().with(changes).withProductId(productId).build();
} catch (final DataIntegrityViolationException dve) {
handleDataIntegrityIssues(dve);
return CommandProcessingResult.empty();
}
}
private List<Long> getProductIdsFromCollection(final List<ProductMix> collection) {
final List<Long> productIds = new ArrayList<Long>();
for (ProductMix productMix : collection) {
productIds.add(productMix.getRestrictedProductId());
}
return productIds;
}
}
@@ -18,4 +18,10 @@ public interface LoanProductReadPlatformService {
LoanProductData retrieveLoanProduct(Long productId);
LoanProductData retrieveNewLoanProductDetails();
Collection<LoanProductData> retrieveAvailableLoanProductsForMix();
Collection<LoanProductData> retrieveRestrictedProductsForMix(Long productId);
Collection<LoanProductData> retrieveAllowedProductsForMix(Long productId);
}
@@ -197,6 +197,19 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
return "lp.id as id, lp.name as name from m_product_loan lp";
}
public String productMixSchema() {
return "lp.id as id, lp.name as name FROM m_product_loan lp left join m_product_mix pm on pm.product_id=lp.id where lp.id not IN("
+ "select lp.id from m_product_loan lp inner join m_product_mix pm on pm.product_id=lp.id)";
}
public String restrictedProductsSchema() {
return "pm.restricted_product_id as id, rp.name as name from m_product_mix pm join m_product_loan rp on rp.id = pm.restricted_product_id ";
}
public String derivedRestrictedProductsSchema() {
return "pm.product_id as id, lp.name as name from m_product_mix pm join m_product_loan lp on lp.id=pm.product_id";
}
@Override
public LoanProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
@@ -206,4 +219,44 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
return LoanProductData.lookup(id, name);
}
}
@Override
public Collection<LoanProductData> retrieveAvailableLoanProductsForMix() {
this.context.authenticatedUser();
final LoanProductLookupMapper rm = new LoanProductLookupMapper();
final String sql = "Select " + rm.productMixSchema();
return this.jdbcTemplate.query(sql, rm, new Object[] {});
}
@Override
public Collection<LoanProductData> retrieveRestrictedProductsForMix(final Long productId) {
this.context.authenticatedUser();
final LoanProductLookupMapper rm = new LoanProductLookupMapper();
final String sql = "Select " + rm.restrictedProductsSchema() + " where pm.product_id=? UNION Select "
+ rm.derivedRestrictedProductsSchema() + " where pm.restricted_product_id=?";
return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
}
@Override
public Collection<LoanProductData> retrieveAllowedProductsForMix(final Long productId) {
this.context.authenticatedUser();
final LoanProductLookupMapper rm = new LoanProductLookupMapper();
final String sql = "Select " + rm.schema() + " where lp.id not in ("
+ "Select pm.restricted_product_id from m_product_mix pm where pm.product_id=? " + "UNION "
+ "Select pm.product_id from m_product_mix pm where pm.restricted_product_id=?)";
return this.jdbcTemplate.query(sql, rm, new Object[] { productId, productId });
}
}