From 232426fd42eb417d120c2d51b76a2f684ee9ffeb Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Mon, 10 Jun 2013 15:24:57 +0100 Subject: [PATCH 01/33] update version of develop branch. --- api-docs/apiLive.htm | 2 +- mifosng-provider/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 175a8f51b..1a1018ee8 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -90,7 +90,7 @@
- +

Overview

diff --git a/mifosng-provider/gradle.properties b/mifosng-provider/gradle.properties index 657b57df9..a174b664a 100644 --- a/mifosng-provider/gradle.properties +++ b/mifosng-provider/gradle.properties @@ -1 +1 @@ -releaseVersion=1.3.0.RELEASE \ No newline at end of file +releaseVersion=1.4.0.DEVELOP \ No newline at end of file From 44d864e7aaf55c991ccddac0c3dd5ab12633b162 Mon Sep 17 00:00:00 2001 From: Vishwas Babu AJ Date: Mon, 10 Jun 2013 21:16:09 +0530 Subject: [PATCH 02/33] minor changes to api docs for accounting rules --- api-docs/apiLive.htm | 45 ++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 175a8f51b..615b327e9 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -9455,11 +9455,30 @@ Content-Type: application/json

Accounting Rules

- Its additional option for MFIs to define their own accounting rules. - Its typically used to perform daily accounting postings of journal entries. + It is typical scenario in MFI's that non accountants pass journal entries on a regular basis. + For Ex: A branch office might deposit their entire cash at hand to their Bank account at the end of a + working day. The branch office users might not understand enough of accounting to figure out which + account needs to get credited and which account needs to be debited to represent this transaction.

-

For an accounting rule debit account and credit account is fixed, so that we can avoid - the selection of credit and debit accounts every time for default journal entries

+

Enter accounting rules, an abstraction on top of manual Journal entires for enabling + simpler data entry. + An accounting rule can define any of the following abstractions +

    +
  • A Simple journal entry where both the credit and debit account have been preselected
  • +
  • A Simple journal entry where either credit or debit accounts have been limited to a + pre-selected list of accounts (Ex: Debit account should be one of "Bank of America" of "JP Morgan" + and credit account should be "Cash") +
  • +
  • A Compound journal entry where multiple debits and / or multiple credits may be made + amongst a set of preselected list of accounts (Ex: Credit account should be either "Bank Of America" + or "Cash" and debit account can be "Employee Salary" and/or "Miscellenous Expenses") +
  • +
+ +

+ +

An accounting rule can also be optionally associated with a branch, so that only a particular Branch's + users have access to the rule

@@ -9474,49 +9493,51 @@ Content-Type: application/json - + - + - + - + - + - + - + - +
Field Descriptions
description
The description about the accounting rule.A description of the accounting rule.
officeId
The officeId of the office/branch for this accounting rule is applicable.An Optional Office for this accounting rule is applicable.
debitAccountHead
Debit account for creating accounting ruleID of the target account to be Debited
creditAccountHead
Credit account for creating accounting ruleID of the target account to be Credited
debitTags
Debit tags for creating accounting rule, These tags fetches the all chart of accounts that are tagged with these tagIdsA list of accounting Tags. Any Ledger account with this tag can serve as the + account to be debited
creditTags
Credit tags for creating accounting rule, These tags fetches the all chart of accounts that are tagged with these tagIdsA list of accounting Tags. Any Ledger account with this tag can serve as the + account to be credited
allowMultipleDebitEntries
Allows to add multiple debits if the value is true, while creating journal entries with this ruleAllows passing multiple debit entries for the accounting rule
allowMultipleCreditEntries
Allows to add multiple credits if the value is true, while creating journal entries with this ruleAllows passing multiple credit entries for the accounting rule
From 106fc2d56d12f269d0548b06974e1f713dfa5a7d Mon Sep 17 00:00:00 2001 From: Lech Rozanski Date: Tue, 11 Jun 2013 15:00:01 +0200 Subject: [PATCH 03/33] MIFOSX-365: Simplify 'Data Tables' functionality --- .../core/data/DataValidatorBuilder.java | 2 +- ...tatableCommandFromApiJsonDeserializer.java | 34 ++++++-- .../ReadWriteNonCoreDataServiceImpl.java | 84 +++++++++++++++---- 3 files changed, 93 insertions(+), 27 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 313377e10..02c9f8d00 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -79,7 +79,7 @@ public class DataValidatorBuilder { public void failWithCode(final String errorCode) { StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(this.parameter) - .append(errorCode); + .append(".").append(errorCode); StringBuilder defaultEnglishMessage = new StringBuilder("Failed data validation due to: ").append(errorCode).append("."); ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), this.parameter, this.value); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java index 1a214ca61..3ea531505 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java @@ -43,7 +43,7 @@ public class DatatableCommandFromApiJsonDeserializer { private final Set supportedParametersForAddColumns = new HashSet(Arrays.asList( "name", "type", "length", "mandatory", "after", "code")); private final Set supportedParametersForChangeColumns = new HashSet(Arrays.asList( - "name", "newName", "mandatory", "after", "code", "newCode")); + "name", "newName", "length", "mandatory", "after", "code", "newCode")); private final Set supportedParametersForDropColumns = new HashSet(Arrays.asList( "name")); private final Object[] supportedColumnTypes = { "String", "Number", "Decimal", "Date", "Text", "Dropdown" }; @@ -65,7 +65,7 @@ public class DatatableCommandFromApiJsonDeserializer { if (type != null && type.equals("String")) { if (length != null) { baseDataValidator.reset().parameter("length").value(length).notBlank() - .zeroOrPositiveAmount(); + .positiveAmount(); } else { baseDataValidator.reset().parameter("length").value(length) .cantBeBlankWhenParameterProvidedIs("type", type); @@ -78,8 +78,13 @@ public class DatatableCommandFromApiJsonDeserializer { final String code = fromApiJsonHelper.extractStringNamed("code", column); if (type != null && type.equals("Dropdown")) { - baseDataValidator.reset().parameter("code").value(code).notBlank() - .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + if (code != null) { + baseDataValidator.reset().parameter("code").value(code).notBlank() + .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + } else { + baseDataValidator.reset().parameter("code").value(code) + .cantBeBlankWhenParameterProvidedIs("type", type); + } } else { baseDataValidator.reset().parameter("code").value(code).mustBeBlankWhenParameterProvided("type", type); } @@ -95,15 +100,16 @@ public class DatatableCommandFromApiJsonDeserializer { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable"); final JsonElement element = fromApiJsonHelper.parse(json); + + final String datatableName = fromApiJsonHelper.extractStringNamed("datatableName", element); + baseDataValidator.reset().parameter("datatableName").value(datatableName).notBlank().notExceedingLengthOf(50) + .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + final String apptableName = fromApiJsonHelper.extractStringNamed("apptableName", element); baseDataValidator.reset().parameter("apptableName").value(apptableName).notBlank().notExceedingLengthOf(50) .isOneOfTheseValues(supportedApptableNames); final String fkColumnName = (apptableName != null) ? apptableName.substring(2) + "_id" : ""; - final String datatableName = fromApiJsonHelper.extractStringNamed("datatableName", element); - baseDataValidator.reset().parameter("datatableName").value(datatableName).notBlank().notExceedingLengthOf(50) - .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); - final Boolean multiRow = fromApiJsonHelper.extractBooleanNamed("multiRow", element); baseDataValidator.reset().parameter("multiRow").value(multiRow).ignoreIfNull().notBlank() .isOneOfTheseValues(true, false); @@ -170,6 +176,18 @@ public class DatatableCommandFromApiJsonDeserializer { .notExceedingLengthOf(50).isNotOneOfTheseValues("id", fkColumnName) .matchesRegularExpression(DATATABLE_NAME_REGEX_PATTERN); + if (fromApiJsonHelper.parameterExists("length", column)) { + final String lengthStr = fromApiJsonHelper.extractStringNamed("length", column); + if (StringUtils.isWhitespace(lengthStr) || !StringUtils.isNumeric(lengthStr) + || StringUtils.isBlank(lengthStr)) { + baseDataValidator.reset().parameter("length").failWithCode("not.greater.than.zero"); + } else { + Integer length = Integer.parseInt(lengthStr); + baseDataValidator.reset().parameter("length").value(length).ignoreIfNull().notBlank() + .positiveAmount(); + } + } + final String code = fromApiJsonHelper.extractStringNamed("code", column); baseDataValidator.reset().parameter("code").value(code).ignoreIfNull().notBlank() .notExceedingLengthOf(100).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index a46f1b5bf..d19c35c46 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -24,6 +24,7 @@ import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer; @@ -297,6 +298,11 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ return (code + "_cd_" + columnName); } + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + } + private void parseDatatableColumnObjectForCreate(final JsonObject column, StringBuilder sqlBuilder) { String name = (column.has("name")) ? column.get("name").getAsString() : null; @@ -357,7 +363,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ validateAppTable(apptableName); String fkColumnName = apptableName.substring(2) + "_id"; - String fkName = datatableName.toLowerCase().replaceAll("\\s", "_"); + String fkName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + fkColumnName; StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder = sqlBuilder.append("CREATE TABLE `" + datatableName + "` ("); @@ -380,7 +386,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ if (multiRow) { sqlBuilder = sqlBuilder .append(", PRIMARY KEY (`id`)") - .append(", KEY `fk_" + apptableName + "` (`" + fkColumnName + "`)") + .append(", KEY `fk_" + apptableName.substring(2) + "_id` (`" + fkColumnName + "`)") .append(", CONSTRAINT `fk_" + fkName + "` ") .append("FOREIGN KEY (`" + fkColumnName + "`) ") .append("REFERENCES `" + apptableName + "` (`id`)"); @@ -398,11 +404,17 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ registerDatatable(datatableName, apptableName); } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); - - if (realCause.getMessage().contains("Table") && realCause.getMessage().contains("already exists")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.already.exists", realCause.getMessage()); - } + List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable"); + + if (realCause.getMessage().toLowerCase().contains("duplicate column name")) { + baseDataValidator.reset().parameter("name").failWithCode("duplicate.column.name"); + } else if (realCause.getMessage().contains("Table") && realCause.getMessage().contains("already exists")) { + baseDataValidator.reset().parameter("datatableName").value(datatableName) + .failWithCode("datatable.already.exists"); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); } return new CommandProcessingResultBuilder() @@ -415,6 +427,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ final Map mapColumnNameDefinition, StringBuilder sqlBuilder) { String name = (column.has("name")) ? column.get("name").getAsString() : null; + String lengthStr = (column.has("length")) ? column.get("length").getAsString() : null; + Integer length = (StringUtils.isNotBlank(lengthStr)) ? Integer.parseInt(lengthStr) : null; String newName = (column.has("newName")) ? column.get("newName").getAsString() : name; Boolean mandatory = (column.has("mandatory")) ? column.get("mandatory").getAsBoolean() : false; String after = (column.has("after")) ? column.get("after").getAsString() : null; @@ -437,13 +451,15 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } String type = mapColumnNameDefinition.get(name).getColumnType(); - Long length = mapColumnNameDefinition.get(name).getColumnLength(); + if (length == null && type.toLowerCase().equals("varchar")) { + length = mapColumnNameDefinition.get(name).getColumnLength().intValue(); + } sqlBuilder = sqlBuilder.append(", CHANGE `" + name + "` `" + newName + "` " + type); if (length != null && length > 0) { - if (type.equals("DECIMAL")) { + if (type.toLowerCase().equals("decimal")) { sqlBuilder.append("(19,6)"); - } else if (type.equals("VARCHAR")) { + } else if (type.toLowerCase().equals("varchar")) { sqlBuilder.append("(" + length + ")"); } } @@ -531,6 +547,34 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ String oldApptableName = this.queryForApplicationTableName(datatableName); if (!StringUtils.equals(oldApptableName, apptableName)) { + String oldFKName = oldApptableName.substring(2) + "_id"; + String newFKName = apptableName.substring(2) + "_id"; + String oldConstraintName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + oldFKName; + String newConstraintName = datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + newFKName; + StringBuilder sqlBuilder = new StringBuilder(); + + if (mapColumnNameDefinition.containsKey("id")) { + sqlBuilder = sqlBuilder + .append("ALTER TABLE `" + datatableName + "` ") + .append("DROP KEY `fk_" + oldFKName + "`,") + .append("DROP FOREIGN KEY `fk_" + oldConstraintName + "`,") + .append("CHANGE COLUMN `" + oldFKName + "` `" + newFKName + "` BIGINT(20) NOT NULL,") + .append("ADD KEY `fk_" + newFKName + "` (`" + newFKName + "`),") + .append("ADD CONSTRAINT `fk_" + newConstraintName + "` ") + .append("FOREIGN KEY (`" + newFKName + "`) ") + .append("REFERENCES `" + apptableName + "` (`id`)"); + } else { + sqlBuilder = sqlBuilder + .append("ALTER TABLE `" + datatableName + "` ") + .append("DROP FOREIGN KEY `fk_" + oldConstraintName + "`,") + .append("CHANGE COLUMN `" + oldFKName + "` `" + newFKName + "` BIGINT(20) NOT NULL,") + .append("ADD CONSTRAINT `fk_" + newConstraintName + "` ") + .append("FOREIGN KEY (`" + newFKName + "`) ") + .append("REFERENCES `" + apptableName + "` (`id`)"); + } + + this.jdbcTemplate.execute(sqlBuilder.toString()); + deregisterDatatable(datatableName); registerDatatable(datatableName, apptableName); } @@ -590,17 +634,18 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); + List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable"); if (realCause.getMessage().toLowerCase().contains("unknown column")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.column.missing.update", realCause.getMessage()); + baseDataValidator.reset().parameter("name").failWithCode("does.not.exist"); } else if (realCause.getMessage().toLowerCase().contains("can't drop")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.column.missing.drop", realCause.getMessage()); + baseDataValidator.reset().parameter("name").failWithCode("does.not.exist"); } else if (realCause.getMessage().toLowerCase().contains("duplicate column")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.duplicate.column", realCause.getMessage()); + baseDataValidator.reset().parameter("name").failWithCode("column.already.exists"); } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); } } @@ -618,11 +663,14 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ this.jdbcTemplate.execute(sql); } catch (SQLGrammarException e) { Throwable realCause = e.getCause(); + List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("datatable"); if (realCause.getMessage().contains("Unknown table")) { - throw new PlatformDataIntegrityException( - "error.msg.datatable.missing", realCause.getMessage()); + baseDataValidator.reset().parameter("datatableName").failWithCode("does.not.exist"); } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); } } From f0ee8920508790f27720f81addf727b402fde948 Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Tue, 11 Jun 2013 13:44:40 +0100 Subject: [PATCH 04/33] MIFOSX-411: fix interest period determination for annual posting periods. --- .../portfolio/savings/data/SavingsAccountDataValidator.java | 2 +- .../portfolio/savings/domain/SavingsAccount.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java index 4841a1d6c..6ebc77d60 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/data/SavingsAccountDataValidator.java @@ -183,7 +183,7 @@ public class SavingsAccountDataValidator { if (this.fromApiJsonHelper.parameterExists(annualFeeAmountParamName, element)) { final BigDecimal annualFeeAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualFeeAmountParamName, element); - baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).notNull().positiveAmount(); + baseDataValidator.reset().parameter(annualFeeAmountParamName).value(annualFeeAmount).positiveAmount(); if (annualFeeAmount != null) { MonthDay monthDayOfAnnualFee = fromApiJsonHelper.extractMonthDayNamed(annualFeeOnMonthDayParamName, element); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java index ec8d1cf10..b5ae0460f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccount.java @@ -621,7 +621,7 @@ public class SavingsAccount extends AbstractPersistable { break; case ANNUAL: periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); - periodEndDate = periodStartDate.dayOfMonth().withMaximumValue(); + periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); break; } @@ -680,11 +680,11 @@ public class SavingsAccount extends AbstractPersistable { break; case ANNUAL: periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); - periodEndDate = periodStartDate.dayOfMonth().withMaximumValue(); + periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); break; case NO_COMPOUNDING_SIMPLE_INTEREST: periodEndDate = periodStartDate.monthOfYear().withMaximumValue(); - periodEndDate = periodStartDate.dayOfMonth().withMaximumValue(); + periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); break; } From 8c81fe7fc10a3d6725ce6e81ef8771978b58bc98 Mon Sep 17 00:00:00 2001 From: mifoscontributer Date: Tue, 11 Jun 2013 19:26:28 +0530 Subject: [PATCH 05/33] Update missing database scripts for MIFOSX-391 --- ...values_to_debit_and_credit_accounts_acc_accounting_rule.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mifosng-db/migrations/core_db/V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql diff --git a/mifosng-db/migrations/core_db/V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql b/mifosng-db/migrations/core_db/V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql new file mode 100644 index 000000000..bb52bebe4 --- /dev/null +++ b/mifosng-db/migrations/core_db/V57__add_default_values_to_debit_and_credit_accounts_acc_accounting_rule.sql @@ -0,0 +1,3 @@ +ALTER TABLE `acc_accounting_rule` + CHANGE COLUMN `debit_account_id` `debit_account_id` BIGINT(20) NULL DEFAULT NULL AFTER `office_id`, + CHANGE COLUMN `credit_account_id` `credit_account_id` BIGINT(20) NULL DEFAULT NULL AFTER `allow_multiple_debits`; \ No newline at end of file From 8690bbbc47a985991d4bfe06fce92212ef247ab3 Mon Sep 17 00:00:00 2001 From: Lech Rozanski Date: Tue, 11 Jun 2013 16:24:34 +0200 Subject: [PATCH 06/33] MIFOSX-365: Simplify 'Data Tables' functionality --- api-docs/apiLive.htm | 259 ++++++++++++++++++++++++++++++++++++++++--- api-docs/apidocs.css | 13 +++ 2 files changed, 257 insertions(+), 15 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 2e1f0e2b9..9143d3ce0 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -11454,6 +11454,83 @@ No Request Body:

Create Data Table

Create a new data table and registers it with the Mifos X Core application table.

+ + + + + + + + + + + + + + + + + + + + + + +
Field Descriptions
Mandatory - datatableName
The name of the Data Table.
Mandatory - apptableName
+ Application table name. Must be one of the following: +
    +
  • m_client
  • +
  • m_group
  • +
  • m_loan
  • +
  • m_office
  • +
  • m_saving_account
  • +
+
Mandatory - columns
An array of columns in the new Data Table.
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field Descriptions - columns
Mandatory - name
Name of the created column. Can contain only alphanumeric characters, underscores and spaces, but cannot start with a number. Cannot start or end with an underscore or space.
Mandatory - type
+ Column type. Must be one of the following: +
    +
  • Date
  • +
  • Decimal
  • +
  • Dropdown
  • +
  • Number
  • +
  • String
  • +
  • Text
  • +
+
Mandatory [type = Dropdown] - code
Used in Code Value fields. Column name becomes: code_cd_name. Mandatory if using type Dropdown, otherwise an error is returned.
Optional - mandatory
Determines whether this column must have a value in every entry. Optional, defaults to false.
Mandatory [type = String] - length
Length of the text field. Mandatory if type String is used, otherwise an error is returned.
@@ -11640,6 +11717,153 @@ GET https://DomainName/api/v1/datatables/{datatable}

Update Data Table

Modifies fields of a data table. If the apptableName parameter is passed, data table is deregistered and registered with the new application table.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field Descriptions
Optional - apptableName
+ Application table name. Only necessary if changing the application table this Data Table is registered to. Must be one of the following: +
    +
  • m_client
  • +
  • m_group
  • +
  • m_loan
  • +
  • m_office
  • +
  • m_saving_account
  • +
+
Optional - dropColumns
An array of columns to be deleted from the Data Table.
Optional - addColumns
An array of columns to be added to the Data Table.
Optional - changeColumns
An array of columns to be changed in the Data Table.
+ + + + + + + + + + +
Field Descriptions - dropColumns
Mandatory - name
Requires a full name of the deleted column to be provided.
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field Descriptions - addColumns
Mandatory - name
Name of the created column. Can contain only alphanumeric characters, underscores and spaces, but cannot start with a number. Cannot start or end with an underscore or space.
Mandatory - type
+ Column type. Must be one of the following: +
    +
  • Date
  • +
  • Decimal
  • +
  • Dropdown
  • +
  • Number
  • +
  • String
  • +
  • Text
  • +
+
Mandatory [type = Dropdown] - code
Used in Code Value fields. Column name becomes: code_cd_name. Mandatory if using type Dropdown, otherwise an error is returned.
Optional - mandatory
Determines whether this column must have a value in every entry. Optional, defaults to false.
Mandatory [type = String] - length
Length of the text field. Mandatory if type String is used, otherwise an error is returned.
Optional - after
Only used when re-ordering Data Table columns. Requires a full column name to be provided.
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field Descriptions - changeColumns
Mandatory - name
Name of the created column. Can contain only alphanumeric characters, underscores and spaces, but cannot start with a number. Cannot start or end with an underscore or space.
Optional - newName
New name of the created column. Can contain only alphanumeric characters, underscores and spaces, but cannot start with a number. Cannot start or end with an underscore or space.
Mandatory [type = Dropdown] - code
Used in Code Value fields. Column name becomes: code_cd_name. Mandatory if using type Dropdown, otherwise an error is returned.
Optional [type = Dropdown] - newCode
Used in Code Value fields. Column name becomes: code_cd_name. Optional if using type Dropdown, otherwise an error is returned.
Optional - mandatory
Determines whether this column must have a value in every entry. Optional, defaults to false.
Mandatory [type = String] - length
Length of the text field. Mandatory if type String is used, otherwise an error is returned.
Optional - after
Only used when re-ordering Data Table columns. Requires a full column name to be provided.
@@ -11652,26 +11876,31 @@ Content-Type: application/json Request Body: { "apptableName": "m_client", - "columns": [ + "dropColumns": [ { - "name": "Gender", + "name": "Gender_cd_Question" + } + ], + "addColumns": [ + { + "name": "Question", "type": "Dropdown", - "code": "Gender" - }, - { - "name": "Some Decimal", - "type": "Decimal", + "code": "Gender", "mandatory": true }, { - "name": "Birth Date", - "type": "Date" - }, + "name": "Some Number", + "type": "Number", + "after": "Some Field" + } + ], + "changeColumns": [ { - "name": "Question 2", - "type": "String", - "length": 100, - "mandatory": false + "name": "Question", + "newName": "Question 2", + "mandatory": true, + "code": "Gender", + "newCode": "Gender2" } ] } @@ -11689,7 +11918,7 @@ Request Body:

Delete Data Table

-

Deletes a data table and unregisters it from the Mifos X Core +

Deletes a data table and deregisters it from the Mifos X Core application table.

diff --git a/api-docs/apidocs.css b/api-docs/apidocs.css index 4397e3b2c..31ad69dfd 100644 --- a/api-docs/apidocs.css +++ b/api-docs/apidocs.css @@ -724,3 +724,16 @@ strong { font-weight: bold; } .stripe-version { float: left; } + +ul.field { + margin: 5px 10px; +} + +ul.field li { + list-style: square inside none; + font-style: italic; +} + +tt { + font-size: 9.5pt; +} \ No newline at end of file From b64ec8addbf32f0566de43617c28920a6a5e944c Mon Sep 17 00:00:00 2001 From: Vishwas Babu AJ Date: Tue, 11 Jun 2013 20:46:39 +0530 Subject: [PATCH 07/33] updating api-docs with examples of mapping fees and penalties to income heads --- api-docs/apiLive.htm | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 615b327e9..d86d021e2 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -7590,7 +7590,7 @@ GET https://DomainName/api/v1/loanproducts/{productId}
Optional Fields
- inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, paymentChannelToFundSourceMappings + inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings @@ -7673,14 +7673,27 @@ Request Body: "writeOffAccountId":"41", "paymentChannelToFundSourceMappings": [ { - "paymentTypeId": "14", - "fundSourceAccountId": "2" + "paymentTypeId": "11", + "fundSourceAccountId": "11" + } + ], + "feeToIncomeAccountMappings": [ + { + "chargeId": "1", + "incomeAccountId": "5" }, { - "paymentTypeId": "14", - "fundSourceAccountId": "2" + "chargeId": "2", + "incomeAccountId": "8" + } + ], + "penaltyToIncomeAccountMappings": [ + { + "chargeId": "6", + "incomeAccountId": "9" } ] + }
From b3d7b98f087b915e5995c948a39cfa6f3a489068 Mon Sep 17 00:00:00 2001 From: Lech Rozanski Date: Wed, 12 Jun 2013 14:44:19 +0200 Subject: [PATCH 08/33] MIFOSX-365: Simplify 'Data Tables' functionality --- ...tatableCommandFromApiJsonDeserializer.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java index 3ea531505..b67d61e1f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/serialization/DatatableCommandFromApiJsonDeserializer.java @@ -61,22 +61,26 @@ public class DatatableCommandFromApiJsonDeserializer { baseDataValidator.reset().parameter("type").value(type).notBlank() .isOneOfTheseValues(supportedColumnTypes); - final Integer length = fromApiJsonHelper.extractIntegerSansLocaleNamed("length", column); if (type != null && type.equals("String")) { - if (length != null) { - baseDataValidator.reset().parameter("length").value(length).notBlank() - .positiveAmount(); + if (fromApiJsonHelper.parameterExists("length", column)) { + final String lengthStr = fromApiJsonHelper.extractStringNamed("length", column); + if (lengthStr != null && !StringUtils.isWhitespace(lengthStr) + && StringUtils.isNumeric(lengthStr) && StringUtils.isNotBlank(lengthStr)) { + Integer length = Integer.parseInt(lengthStr); + baseDataValidator.reset().parameter("length").value(length).positiveAmount(); + } else if (StringUtils.isBlank(lengthStr) || StringUtils.isWhitespace(lengthStr)) { + baseDataValidator.reset().parameter("length").failWithCode("must.be.provided.when.type.is.String"); + } else if (!StringUtils.isNumeric(lengthStr)) { + baseDataValidator.reset().parameter("length").failWithCode("not.greater.than.zero"); + } } else { - baseDataValidator.reset().parameter("length").value(length) - .cantBeBlankWhenParameterProvidedIs("type", type); + baseDataValidator.reset().parameter("length").failWithCode("must.be.provided.when.type.is.String"); } } else { - baseDataValidator.reset().parameter("length").value(length) - .mustBeBlankWhenParameterProvidedIs("type", type); + baseDataValidator.reset().parameter("length").mustBeBlankWhenParameterProvidedIs("type", type); } - + final String code = fromApiJsonHelper.extractStringNamed("code", column); - if (type != null && type.equals("Dropdown")) { if (code != null) { baseDataValidator.reset().parameter("code").value(code).notBlank() @@ -125,9 +129,6 @@ public class DatatableCommandFromApiJsonDeserializer { baseDataValidator.reset().parameter("name").value(name).notBlank() .isNotOneOfTheseValues("id", fkColumnName).matchesRegularExpression(DATATABLE_COLUMN_NAME_REGEX_PATTERN); - final String type = fromApiJsonHelper.extractStringNamed("type", column); - baseDataValidator.reset().parameter("type").value(type).notBlank().isOneOfTheseValues(supportedColumnTypes); - validateType(baseDataValidator, column); final Boolean mandatory = fromApiJsonHelper.extractBooleanNamed("mandatory", column); From 59f1c0ce8ab2a5c1d6a77dc64b113090e096d94b Mon Sep 17 00:00:00 2001 From: goutham-M Date: Wed, 12 Jun 2013 16:31:22 +0530 Subject: [PATCH 09/33] holiday functionality to apply holidays for offices --- .../core_db/v58__create-holiday-tables.sql | 22 ++++ .../commands/domain/CommandWrapper.java | 4 + .../service/CommandWrapperBuilder.java | 8 ++ .../SynchronousCommandProcessingService.java | 7 +- .../core/data/CommandProcessingResult.java | 10 +- .../data/CommandProcessingResultBuilder.java | 8 +- .../holiday/api/HolidayApiConstants.java | 24 ++++ .../holiday/api/HolidaysApiResource.java | 45 +++++++ .../holiday/data/HolidayDataValidator.java | 85 +++++++++++++ .../organisation/holiday/domain/Holiday.java | 88 +++++++++++++ .../holiday/domain/HolidayRepository.java | 15 +++ .../handler/CreateHolidayCommandHandler.java | 27 ++++ .../HolidayReadPlatformServiceImpl.java | 5 + .../service/HolidayWritePlatformService.java | 9 ++ ...WritePlatformServiceJpaRepositoryImpl.java | 120 ++++++++++++++++++ .../office/api/OfficesApiResource.java | 2 +- 16 files changed, 473 insertions(+), 6 deletions(-) create mode 100644 mifosng-db/migrations/core_db/v58__create-holiday-tables.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/api/HolidayApiConstants.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/api/HolidaysApiResource.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/data/HolidayDataValidator.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/Holiday.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/HolidayRepository.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/handler/CreateHolidayCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayReadPlatformServiceImpl.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java diff --git a/mifosng-db/migrations/core_db/v58__create-holiday-tables.sql b/mifosng-db/migrations/core_db/v58__create-holiday-tables.sql new file mode 100644 index 000000000..35d832626 --- /dev/null +++ b/mifosng-db/migrations/core_db/v58__create-holiday-tables.sql @@ -0,0 +1,22 @@ + +create table `m_holiday`( +`id` BIGINT(20) NOT NULL AUTO_INCREMENT, +`name` varchar(100) NOT NULL, +`from_date` DATETIME NOT NULL, +`to_date` DATETIME NOT NULL, +`description` varchar(100) NULL DEFAULT NULL, +PRIMARY KEY (`id`), +UNIQUE INDEX `holiday_name` (`name`) +); + +CREATE TABLE `m_holiday_office` ( + `holiday_id` BIGINT(20) NOT NULL, + `office_id` BIGINT(20) NOT NULL, + PRIMARY KEY (`holiday_id`, `office_id`), + INDEX `m_holiday_id_ibfk_1` (`holiday_id`), + INDEX `m_office_id_ibfk_2` (`office_id`), + CONSTRAINT `m_holiday_id_ibfk_1` FOREIGN KEY (`holiday_id`) REFERENCES `m_holiday` (`id`), + CONSTRAINT `m_office_id_ibfk_2` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`) +); + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('organisation', 'CREATE_HOLIDAY', 'HOLIDAY', 'CREATE', 0); \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java index 613188160..dc7b6a09c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java @@ -531,4 +531,8 @@ public class CommandWrapper { public boolean isAccountingRuleResource() { return this.entityName.equalsIgnoreCase("ACCOUNTINGRULE"); } + + public boolean isHolidayResource() { + return this.entityName.equalsIgnoreCase("HOLIDAY"); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index e275acbe6..132a79d8e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -1018,4 +1018,12 @@ public class CommandWrapperBuilder { this.href = "/accountingrules/" + accountingRuleId; return this; } + + public CommandWrapperBuilder createHoliday() { + this.actionName = "CREATE"; + this.entityName = "HOLIDAY"; + this.entityId = null; + this.href = "/holidays/template"; + return this; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java index 6fd1d3d66..692c12bcf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java @@ -461,7 +461,12 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer throw new UnsupportedCommandException(wrapper.commandName()); } - }else { + } else if(wrapper.isHolidayResource()) { + + if (wrapper.isCreate()) { + handler = applicationContext.getBean("createHolidayCommandHandler", NewCommandSourceHandler.class); + } + } else { throw new UnsupportedCommandException(wrapper.commandName()); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/CommandProcessingResult.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/CommandProcessingResult.java index c0a1bcc60..d1c23560f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/CommandProcessingResult.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/CommandProcessingResult.java @@ -22,15 +22,16 @@ public class CommandProcessingResult { private final Long savingsId; private final Long resourceId; private final String transactionId; + private final Long holidayId; private final Map changes; @SuppressWarnings("unused") private final String resourceIdentifier; 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 changes) { + final Long holidayId, final Map changes) { return new CommandProcessingResult(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId, - transactionId, changes); + transactionId, holidayId, changes); } public static CommandProcessingResult commandOnlyResult(final Long commandId) { @@ -78,11 +79,12 @@ public class CommandProcessingResult { this.loanId = null; this.savingsId = null; this.transactionId = null; + this.holidayId = null; this.changes = new HashMap(); } 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 Long savingsId, final String resourceIdentifier, final Long resourceId, final String transactionId, final Long holidayId, final Map changesOnly) { this.commandId = commandId; this.officeId = officeId; @@ -94,6 +96,7 @@ public class CommandProcessingResult { this.resourceId = resourceId; this.changes = changesOnly; this.transactionId = transactionId; + this.holidayId = holidayId; } private CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map changesOnly) { @@ -109,6 +112,7 @@ public class CommandProcessingResult { this.loanId = null; this.savingsId = null; this.transactionId = null; + this.holidayId = null; this.commandId = commandId; this.changes = changesOnly; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/CommandProcessingResultBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/CommandProcessingResultBuilder.java index f55f44325..20d977079 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/CommandProcessingResultBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/CommandProcessingResultBuilder.java @@ -22,11 +22,12 @@ public class CommandProcessingResultBuilder { private String resourceIdentifier; private Long entityId; private String transactionId; + private Long holidayId; private Map changes; public CommandProcessingResult build() { return CommandProcessingResult.fromDetails(commandId, officeId, groupId, clientId, loanId, savingsId, resourceIdentifier, entityId, - transactionId, changes); + transactionId, holidayId, changes); } public CommandProcessingResultBuilder withCommandId(final Long withCommandId) { @@ -78,4 +79,9 @@ public class CommandProcessingResultBuilder { this.transactionId = withTransactionId; return this; } + + public CommandProcessingResultBuilder withHolidayId(final Long withHolidayId) { + this.holidayId = withHolidayId; + return this; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/api/HolidayApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/api/HolidayApiConstants.java new file mode 100644 index 000000000..463e0d367 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/api/HolidayApiConstants.java @@ -0,0 +1,24 @@ +package org.mifosplatform.organisation.holiday.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class HolidayApiConstants { + + public static final String HOLIDAY_RESOURCE_NAME = "holiday"; + + // general + public static final String localeParamName = "locale"; + public static final String dateFormatParamName = "dateFormat"; + + //request parameters + public static final String name = "name"; + public static final String fromDate = "fromDate"; + public static final String toDate = "toDate"; + public static final String description = "description"; + public static final String offices = "offices"; + + public static final Set HOLIDAY_CREATE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, + dateFormatParamName, name, fromDate, toDate, description, offices)); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/api/HolidaysApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/api/HolidaysApiResource.java new file mode 100644 index 000000000..da3394da4 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/api/HolidaysApiResource.java @@ -0,0 +1,45 @@ +package org.mifosplatform.organisation.holiday.api; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.commands.service.CommandWrapperBuilder; +import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/holidays") +@Component +@Scope("singleton") +public class HolidaysApiResource { + + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + + @Autowired + public HolidaysApiResource(final DefaultToApiJsonSerializer toApiJsonSerializer, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + this.toApiJsonSerializer = toApiJsonSerializer; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String createNewHoliday(final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().createHoliday().withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/data/HolidayDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/data/HolidayDataValidator.java new file mode 100644 index 000000000..0076353e0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/data/HolidayDataValidator.java @@ -0,0 +1,85 @@ + package org.mifosplatform.organisation.holiday.data; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +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.mifosplatform.organisation.holiday.api.HolidayApiConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; + +@Component +public class HolidayDataValidator { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public HolidayDataValidator(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForCreate(final String json) { + + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, HolidayApiConstants.HOLIDAY_CREATE_REQUEST_DATA_PARAMETERS); + final JsonElement element = fromApiJsonHelper.parse(json); + + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(HolidayApiConstants.HOLIDAY_RESOURCE_NAME); + + final String name = this.fromApiJsonHelper.extractStringNamed(HolidayApiConstants.name, element); + baseDataValidator.reset().parameter(HolidayApiConstants.name).value(name).notBlank().notNull().notExceedingLengthOf(100); + + final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.fromDate, element); + baseDataValidator.reset().parameter(HolidayApiConstants.fromDate).value(fromDate).notNull(); + + final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDate, element); + baseDataValidator.reset().parameter(HolidayApiConstants.toDate).value(toDate).notNull(); + + + Set offices = null; + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + + if (topLevelJsonElement.has(HolidayApiConstants.offices)&&topLevelJsonElement.get(HolidayApiConstants.offices).isJsonArray()) { + + final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.offices).getAsJsonArray(); + if(array.size()>0) { + offices = new HashSet(array.size()); + for (int i = 0; i < array.size(); i++) { + final JsonObject officeElement = array.get(i).getAsJsonObject(); + final Long officeId = this.fromApiJsonHelper.extractLongNamed("officeId", officeElement); + baseDataValidator.reset().parameter(HolidayApiConstants.offices).value(officeId).notNull(); + offices.add(officeId); + } + } + } + baseDataValidator.reset().parameter(HolidayApiConstants.offices).value(offices).notNull(); + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { + // + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/Holiday.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/Holiday.java new file mode 100644 index 000000000..3fe735b76 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/Holiday.java @@ -0,0 +1,88 @@ +/** + * 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.organisation.holiday.domain; + +import java.util.Date; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.organisation.holiday.api.HolidayApiConstants; +import org.mifosplatform.organisation.office.domain.Office; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_holiday", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "holiday_name") }) +public class Holiday extends AbstractPersistable { + + @SuppressWarnings("unused") + @Column(name = "name", unique = true, nullable = false, length = 100) + private String name; + + @SuppressWarnings("unused") + @Column(name = "from_date", nullable = false) + @Temporal(TemporalType.DATE) + private Date fromDate; + + @SuppressWarnings("unused") + @Column(name = "to_date", nullable = false) + @Temporal(TemporalType.DATE) + private Date toDate; + + @SuppressWarnings("unused") + @Column(name = "description", length = 100) + private String description; + + @SuppressWarnings("unused") + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "m_holiday_office", joinColumns = @JoinColumn(name = "holiday_id"), inverseJoinColumns = @JoinColumn(name = "office_id")) + private Set offices; + + public static Holiday createNew(final Set offices, final JsonCommand command) { + final String name = command.stringValueOfParameterNamed(HolidayApiConstants.name); + final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDate); + final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDate); + final String description = command.stringValueOfParameterNamed(HolidayApiConstants.description); + return new Holiday(name, fromDate, toDate, description, offices); + } + + private Holiday(final String name, final LocalDate fromDate, final LocalDate toDate, final String description, final Set offices) { + if (StringUtils.isNotBlank(name)) { + this.name = name.trim(); + } + + if (fromDate != null) { + this.fromDate = fromDate.toDate(); + } + + if (toDate != null) { + this.toDate = toDate.toDate(); + } + + if (StringUtils.isNotBlank(name)) { + this.description = description.trim(); + } else { + this.description = null; + } + + if (offices != null) { + this.offices = offices; + } + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/HolidayRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/HolidayRepository.java new file mode 100644 index 000000000..985351c1c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/domain/HolidayRepository.java @@ -0,0 +1,15 @@ +/** + * 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.organisation.holiday.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface HolidayRepository extends JpaRepository, + JpaSpecificationExecutor { + // no added behaviour +} + diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/handler/CreateHolidayCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/handler/CreateHolidayCommandHandler.java new file mode 100644 index 000000000..bbbc93e5f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/handler/CreateHolidayCommandHandler.java @@ -0,0 +1,27 @@ +package org.mifosplatform.organisation.holiday.handler; + +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.organisation.holiday.service.HolidayWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CreateHolidayCommandHandler implements NewCommandSourceHandler { + + private final HolidayWritePlatformService holidayWritePlatformService; + + @Autowired + public CreateHolidayCommandHandler(final HolidayWritePlatformService holidayWritePlatformService) { + this.holidayWritePlatformService = holidayWritePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.holidayWritePlatformService.createHoliday(command); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayReadPlatformServiceImpl.java new file mode 100644 index 000000000..9cbbb708c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayReadPlatformServiceImpl.java @@ -0,0 +1,5 @@ +package org.mifosplatform.organisation.holiday.service; + +public class HolidayReadPlatformServiceImpl { + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java new file mode 100644 index 000000000..8becdc951 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformService.java @@ -0,0 +1,9 @@ +package org.mifosplatform.organisation.holiday.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public interface HolidayWritePlatformService { + + CommandProcessingResult createHoliday(JsonCommand command); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java new file mode 100644 index 000000000..18fe2b2de --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java @@ -0,0 +1,120 @@ +package org.mifosplatform.organisation.holiday.service; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.organisation.holiday.api.HolidayApiConstants; +import org.mifosplatform.organisation.holiday.data.HolidayDataValidator; +import org.mifosplatform.organisation.holiday.domain.Holiday; +import org.mifosplatform.organisation.holiday.domain.HolidayRepository; +import org.mifosplatform.organisation.office.domain.Office; +import org.mifosplatform.organisation.office.domain.OfficeRepository; +import org.mifosplatform.organisation.office.exception.OfficeNotFoundException; +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 com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +@Service +public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWritePlatformService { + + private final static Logger logger = LoggerFactory.getLogger(HolidayWritePlatformServiceJpaRepositoryImpl.class); + + private final HolidayDataValidator fromApiJsonDeserializer; + private final HolidayRepository holidayRepository; + private final PlatformSecurityContext context; + private final OfficeRepository officeRepository; + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public HolidayWritePlatformServiceJpaRepositoryImpl(final HolidayDataValidator fromApiJsonDeserializer, + final HolidayRepository holidayRepository, final PlatformSecurityContext context, final OfficeRepository officeRepository, + final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.holidayRepository = holidayRepository; + this.context = context; + this.officeRepository = officeRepository; + this.fromApiJsonHelper = fromApiJsonHelper; + } + + @Transactional + @Override + public CommandProcessingResult createHoliday(final JsonCommand command) { + + try { + this.context.authenticatedUser(); + this.fromApiJsonDeserializer.validateForCreate(command.json()); + + validateToDateBeforeFromDate(command); + + Set offices = null; + final JsonObject topLevelJsonElement = this.fromApiJsonHelper.parse(command.json()).getAsJsonObject(); + if (topLevelJsonElement.has(HolidayApiConstants.offices) && topLevelJsonElement.get(HolidayApiConstants.offices).isJsonArray()) { + + final JsonArray array = topLevelJsonElement.get(HolidayApiConstants.offices).getAsJsonArray(); + offices = new HashSet(array.size()); + for (int i = 0; i < array.size(); i++) { + final JsonObject officeElement = array.get(i).getAsJsonObject(); + final Long officeId = this.fromApiJsonHelper.extractLongNamed("officeId", officeElement); + final Office office = this.officeRepository.findOne(officeId); + if (office == null) { throw new OfficeNotFoundException(officeId); } + offices.add(office); + } + } + + final Holiday holiday = Holiday.createNew(offices, command); + + this.holidayRepository.save(holiday); + + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(holiday.getId()) + .withHolidayId(holiday.getId()).build(); + } catch (final DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } + } + + private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) { + final Throwable realCause = dve.getMostSpecificCause(); + if (realCause.getMessage().contains("holiday_name")) { + final String name = command.stringValueOfParameterNamed("name"); + throw new PlatformDataIntegrityException("error.msg.holiday.duplicate.name", "Holiday with name `" + name + "` already exists", + "name", name); + } + + logger.error(dve.getMessage(), dve); + throw new PlatformDataIntegrityException("error.msg.office.unknown.data.integrity.issue", + "Unknown data integrity issue with resource."); + } + + private void validateToDateBeforeFromDate(final JsonCommand command) { + final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDate); + final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDate); + if (toDate.isBefore(fromDate)) { + + final String defaultUserMessage = "ToDate date cannot be before the FromDate."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.holiday.toDate.cannot be.before.the.FromDate", + defaultUserMessage, "ToDate"); + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/api/OfficesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/api/OfficesApiResource.java index a5a1f09c0..6e9c6c249 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/api/OfficesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/api/OfficesApiResource.java @@ -53,7 +53,7 @@ public class OfficesApiResource { private final OfficeReadPlatformService readPlatformService; private final DefaultToApiJsonSerializer toApiJsonSerializer; private final ApiRequestParameterHelper apiRequestParameterHelper; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; @Autowired public OfficesApiResource(final PlatformSecurityContext context, final OfficeReadPlatformService readPlatformService, From 7fc4fd12e4a9537dcf7cfebd2546ad107fd347e9 Mon Sep 17 00:00:00 2001 From: mifoscontributer Date: Wed, 12 Jun 2013 19:09:17 +0530 Subject: [PATCH 10/33] Handle Min Max constraint exceptions for crete loan --- .../LoanApplicationWritePlatformServiceJpaRepositoryImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 06a634447..95ff8eb3d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -20,6 +20,7 @@ import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; @@ -152,6 +153,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, loanProduct); + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } // validate dates against calendar recurring dates // get calendar From c58d5412799de9cd8d303e853940abd9da2df92d Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Thu, 13 Jun 2013 00:13:02 +0100 Subject: [PATCH 11/33] MIFOSX-445: default the list of loan officer options for new loan application to pick up on loan officers in offices higher in its office hierarchy. add optional parameter to allow people to restrict this list to only the selected branch. --- api-docs/apiLive.htm | 15 ++- .../staff/api/StaffApiResource.java | 22 +---- .../service/StaffReadPlatformService.java | 12 ++- .../service/StaffReadPlatformServiceImpl.java | 96 ++++++++++++++++--- .../loanaccount/api/BulkLoansApiResource.java | 2 +- .../loanaccount/api/LoansApiResource.java | 34 ++++++- 6 files changed, 136 insertions(+), 45 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 2c7a1d70e..c30f91868 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -3901,12 +3901,14 @@ GET https://DomainName/mifosng-provider/api/v1/groups The type of transaction processing strategy to be used.
At present some MFI specific ones exist. This is an important point of change for many MFIs.
Responsible for things like payment order and how early or late repayments should be processed.
- Out of the box, four available: + Out of the box options available are:
  1. id=1, code=mifos-standard-strategy, name=Mifos style
  2. id=2, code=heavensfamily-strategy, name=Heavensfamily
  3. id=3, code=creocore-strategy, name=Creocore
  4. id=4, code=rbi-india-strategy, name=RBI (India)
  5. +
  6. id=5, code=principal-interest-penalties-fees-order-strategy, name=Principal Interest Penalties Fees Order
  7. +
  8. id=6, code=interest-principal-penalties-fees-order-strategy, name=Interest Principal Penalties Fees Order
@@ -3973,6 +3975,13 @@ GET https://DomainName/mifosng-provider/api/v1/groups
If entered, productId, productName and selectedProduct fields are returned.
+ +
loanOfficersInSelectedOfficeOnly
+
+ Boolean optional +
+
Defaults to false if not provided. If loanOfficersInSelectedOfficeOnly=true only loan officers + who are associated with the select branch are returned.

Example Requests:

loans/template?templateType=individual&clientId=1
@@ -6995,12 +7004,14 @@ Request Body: The type of transaction processing strategy to be used.
At present some MFI specific ones exist. This is an important point of change for many MFIs.
Responsible for things like payment order and how early or late repayments should be processed.
- Out of the box, four available: + Out of the box options available are:
  1. id=1, code=mifos-standard-strategy, name=Mifos style
  2. id=2, code=heavensfamily-strategy, name=Heavensfamily
  3. id=3, code=creocore-strategy, name=Creocore
  4. id=4, code=rbi-india-strategy, name=RBI (India)
  5. +
  6. id=5, code=principal-interest-penalties-fees-order-strategy, name=Principal Interest Penalties Fees Order
  7. +
  8. id=6, code=interest-principal-penalties-fees-order-strategy, name=Interest Principal Penalties Fees Order
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/api/StaffApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/api/StaffApiResource.java index 1138c4e18..22009a5e7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/api/StaffApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/api/StaffApiResource.java @@ -22,7 +22,6 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; -import org.apache.commons.lang.StringUtils; import org.mifosplatform.commands.domain.CommandWrapper; import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; @@ -81,8 +80,7 @@ public class StaffApiResource { context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); - final String extraCriteria = getStaffCriteria(sqlSearch, officeId); - final Collection staff = this.readPlatformService.retrieveAllStaff(extraCriteria); + final Collection staff = this.readPlatformService.retrieveAllStaff(sqlSearch, officeId); final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, staff, RESPONSE_DATA_PARAMETERS); @@ -131,22 +129,4 @@ public class StaffApiResource { return this.toApiJsonSerializer.serialize(result); } - - private String getStaffCriteria(final String sqlSearch, final Long officeId) { - - String extraCriteria = ""; - - if (sqlSearch != null) { - extraCriteria = " and (" + sqlSearch + ")"; - } - if (officeId != null) { - extraCriteria += " and office_id = " + officeId; - } - - if (StringUtils.isNotBlank(extraCriteria)) { - extraCriteria = extraCriteria.substring(4); - } - - return extraCriteria; - } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformService.java index d9fb62873..3d5e42ef0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformService.java @@ -13,9 +13,15 @@ public interface StaffReadPlatformService { StaffData retrieveStaff(Long staffId); - Collection retrieveAllStaff(final String extraCriteria); - Collection retrieveAllStaffForDropdown(Long officeId); - Collection retrieveAllLoanOfficersByOffice(final Long officeId); + Collection retrieveAllLoanOfficersInOfficeById(final Long officeId); + + /** + * returns all loan officers in offices that are above the provided + * officeId. + */ + Collection retrieveAllLoanOfficersInOfficeAndItsParentOfficeHierarchy(Long officeId); + + Collection retrieveAllStaff(String sqlSearch, Long officeId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformServiceImpl.java index 18c3d381c..677e46cd1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/staff/service/StaffReadPlatformServiceImpl.java @@ -26,6 +26,7 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; private final StaffLookupMapper lookupMapper = new StaffLookupMapper(); + private final StaffInOfficeHierarchyMapper staffInOfficeHierarchyMapper = new StaffInOfficeHierarchyMapper(); @Autowired public StaffReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource) { @@ -56,6 +57,43 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService { } } + private static final class StaffInOfficeHierarchyMapper implements RowMapper { + + final String schema; + + public StaffInOfficeHierarchyMapper() { + + StringBuilder sqlBuilder = new StringBuilder(200); + sqlBuilder.append("s.id as id, s.office_id as officeId, o.name as officeName,"); + sqlBuilder.append("s.firstname as firstname, s.lastname as lastname,"); + sqlBuilder.append("s.display_name as displayName, s.is_loan_officer as isLoanOfficer "); + sqlBuilder.append("from m_office o "); + sqlBuilder.append("join m_office ohierarchy on o.hierarchy like concat(ohierarchy.hierarchy, '%') "); + sqlBuilder.append("join m_staff s on s.office_id = ohierarchy.id and s.is_loan_officer is true "); + sqlBuilder.append("where o.id = ? "); + + this.schema = sqlBuilder.toString(); + } + + public String schema() { + return this.schema; + } + + @Override + public StaffData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + + final Long id = rs.getLong("id"); + final String firstname = rs.getString("firstname"); + final String lastname = rs.getString("lastname"); + final String displayName = rs.getString("displayName"); + final Long officeId = rs.getLong("officeId"); + final String officeName = rs.getString("officeName"); + final boolean isLoanOfficer = rs.getBoolean("isLoanOfficer"); + + return StaffData.instance(id, firstname, lastname, displayName, officeId, officeName, isLoanOfficer); + } + } + private static final class StaffLookupMapper implements RowMapper { private final String schemaSql; @@ -84,19 +122,7 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService { } @Override - public Collection retrieveAllStaff(final String extraCriteria) { - - final StaffMapper rm = new StaffMapper(); - String sql = "select " + rm.schema(); - if (StringUtils.isNotBlank(extraCriteria)) { - sql += " where " + extraCriteria; - } - sql = sql + " order by s.lastname"; - return this.jdbcTemplate.query(sql, rm, new Object[] {}); - } - - @Override - public Collection retrieveAllLoanOfficersByOffice(final Long officeId) { + public Collection retrieveAllLoanOfficersInOfficeById(final Long officeId) { return retrieveAllStaff(" office_id=" + officeId + " and is_loan_officer=1"); } @@ -132,4 +158,48 @@ public class StaffReadPlatformServiceImpl implements StaffReadPlatformService { throw new StaffNotFoundException(staffId); } } + + @Override + public Collection retrieveAllStaff(final String sqlSearch, final Long officeId) { + final String extraCriteria = getStaffCriteria(sqlSearch, officeId); + return retrieveAllStaff(extraCriteria); + } + + private Collection retrieveAllStaff(final String extraCriteria) { + + final StaffMapper rm = new StaffMapper(); + String sql = "select " + rm.schema(); + if (StringUtils.isNotBlank(extraCriteria)) { + sql += " where " + extraCriteria; + } + sql = sql + " order by s.lastname"; + return this.jdbcTemplate.query(sql, rm, new Object[] {}); + } + + private String getStaffCriteria(final String sqlSearch, final Long officeId) { + + String extraCriteria = ""; + + if (sqlSearch != null) { + extraCriteria = " and (" + sqlSearch + ")"; + } + if (officeId != null) { + extraCriteria += " and office_id = " + officeId; + } + + if (StringUtils.isNotBlank(extraCriteria)) { + extraCriteria = extraCriteria.substring(4); + } + + return extraCriteria; + } + + @Override + public Collection retrieveAllLoanOfficersInOfficeAndItsParentOfficeHierarchy(final Long officeId) { + + String sql = "select " + this.staffInOfficeHierarchyMapper.schema(); + sql = sql + " order by s.lastname"; + + return this.jdbcTemplate.query(sql, this.staffInOfficeHierarchyMapper, new Object[] { officeId }); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/BulkLoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/BulkLoansApiResource.java index 7e9b2db4b..3350bf505 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/BulkLoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/BulkLoansApiResource.java @@ -88,7 +88,7 @@ public class BulkLoansApiResource { StaffAccountSummaryCollectionData staffAccountSummaryCollectionData = null; if (officeId != null) { - loanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(officeId); + loanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersInOfficeById(officeId); } if (loanOfficerId != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index 3e9437e05..19536882e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.loanaccount.api; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -12,6 +13,7 @@ import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -162,6 +164,7 @@ public class LoansApiResource { @Produces({ MediaType.APPLICATION_JSON }) public String template(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId, @QueryParam("productId") final Long productId, @QueryParam("templateType") final String templateType, + @DefaultValue("false") @QueryParam("loanOfficersInSelectedOfficeOnly") final boolean loanOfficersInSelectedOfficeOnly, @Context final UriInfo uriInfo) { context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions); @@ -223,7 +226,28 @@ public class LoansApiResource { throw new NotSupportedLoanTemplateTypeException(errorMsg, templateType); } - if (officeId != null) allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(officeId); + if (officeId != null) { + if (loanOfficersInSelectedOfficeOnly) { + // only bring back loan officers in selected branch/office + Collection loanOfficersInBranch = this.staffReadPlatformService + .retrieveAllLoanOfficersInOfficeById(officeId); + + if (!CollectionUtils.isEmpty(loanOfficersInBranch)) { + allowedLoanOfficers = new ArrayList(loanOfficersInBranch); + } + } else { + // by default bring back all loan officers in selected + // branch/office as well as loan officers in officer above + // this office + Collection loanOfficersInHierarchy = this.staffReadPlatformService + .retrieveAllLoanOfficersInOfficeAndItsParentOfficeHierarchy(officeId); + + if (!CollectionUtils.isEmpty(loanOfficersInHierarchy)) { + allowedLoanOfficers = new ArrayList(loanOfficersInHierarchy); + } + } + } + // add product options, allowed loan officers and calendar options // (calendar options will be null in individual loan) newLoanAccount = LoanAccountData.associationsAndTemplate(newLoanAccount, productOptions, allowedLoanOfficers, calendarOptions); @@ -336,9 +360,9 @@ public class LoansApiResource { chargeTemplate = this.loanChargeReadPlatformService.retrieveLoanChargeTemplate(); if (loanBasicDetails.officeId() != null) { - allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(loanBasicDetails.officeId()); + allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersInOfficeById(loanBasicDetails.officeId()); } else if (loanBasicDetails.groupOfficeId() != null) { - allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(loanBasicDetails.groupOfficeId()); + allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersInOfficeById(loanBasicDetails.groupOfficeId()); } loanPurposeOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanPurpose"); @@ -492,8 +516,8 @@ public class LoansApiResource { // for officeId and loanOfficerId in current way! final LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId); - final Collection allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(loanBasicDetails - .officeId()); + final Collection allowedLoanOfficers = this.staffReadPlatformService + .retrieveAllLoanOfficersInOfficeById(loanBasicDetails.officeId()); final Long fromLoanOfficerId = loanBasicDetails.loanOfficerId(); final BulkTransferLoanOfficerData loanReassignmentData = BulkTransferLoanOfficerData.template(fromLoanOfficerId, From fa80413d5e7b4ab3a923df646f531eccb1a084d6 Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Thu, 13 Jun 2013 00:24:01 +0100 Subject: [PATCH 12/33] tidyup usage of PlatformApiDataValidationException constructor. --- .../LoanApplicationWritePlatformServiceJpaRepositoryImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 95ff8eb3d..eab9804bb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -154,8 +154,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, loanProduct); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", - "Validation errors exist.", dataValidationErrors); } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } // validate dates against calendar recurring dates // get calendar From 256cdd3c4725d389ab621ecea20974174fea6554 Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Thu, 13 Jun 2013 11:50:51 +0530 Subject: [PATCH 13/33] Move Group API documentation from Beta release to General availibility --- api-docs/apiLive.htm | 196 ++++++++++++++++++++++++++++++++----------- 1 file changed, 148 insertions(+), 48 deletions(-) diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index c30f91868..27b0588df 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -449,6 +449,106 @@ +
+

Group

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
RESOURCES
+
+ + + + + + +
POST
create/complex + update
+ + + + + + +
GET
read
+ + + + + + +
PUT
update
+ + + + + + +
DELETE
delete
GroupgroupsCreate a GroupList Groups
groups/templateRetrieve Group Template
groups/{groupId}Retrieve a GroupUpdate a GroupDelete a Group
groups/{groupId}?command=activateActivate a Group
groups/{groupId}?command=associateClientsAssociate Clients
groups/{groupId}?command=disassociateClientsDisassociate Clients
+
+
+
+